当结果来自DLL时,如何确定DLL调用的缓冲区大小

时间:2018-04-04 19:35:00

标签: delphi delphi-xe2 delphi-10.2-tokyo

使用Delphi 10.2 Tokyo和Delphi XE2。

我有一个将XML数据发布到网站的DLL。 DLL是使用Delphi 10构建的,以便使用TLS 1.2,这是Delphi XE2所不具备的。

对DLL的调用来自Delphi XE2 EXE,但我不认为这是相关的,但我仍然注意到它。

将数据发布到网站的调用通常会返回文本数据。有时非常大量的文本数据。超过150K字符。

我原来的DLL约定基本上不正确,因为我将返回的文本数据的内容作为PChar返回。在我和其他地方的阅读中,这是一个很大的禁忌。

这种“糟糕”的方法很有效,直到我开始收到大量的数据。我对它进行了测试,但是对于超过132,365个字符的任何内容都失败了。

我重构了我的DLL并调用代码以传递缓冲区作为PChar来填写,但是我在尝试填充输出值时遇到错误!

其次,由于我从来不知道返回的数据有多大,如何指定从调用方法中填充多大的缓冲区?

我的DLL代码,我收到错误:

library TestDLL;

uses
  SysUtils,
  Classes,
  Windows,
  Messages,
  vcl.Dialogs,
  IdSSLOpenSSL, IdHTTP, IdIOHandlerStack, IdURI,
  IdCompressorZLib;

{$R *.res}

function PostAdminDataViaDll(body, method, url: PChar; OutData : PChar; OutLen : integer): integer; stdcall
var HTTPReq : TIdHTTP;
var Response: TStringStream;
var SendStream : TStringStream;
var IdSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL;
var Uri : TIdURI;
var s : string;
begin
  Result := -1;
  try
    HTTPReq := TIdHTTP.Create(nil);
    IdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    IdSSLIOHandler.SSLOptions.Mode := sslmClient;
    IdSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2, sslvTLSv1_1];
    if Assigned(HTTPReq) then begin
      HTTPReq.Compressor := TIdCompressorZLib.Create(HTTPReq);
      HTTPReq.IOHandler := IdSSLIOHandler;
      HTTPReq.ReadTimeout := 180000;//set read timeout to 3 minutes
      HTTPReq.Request.ContentType := 'text/xml;charset=UTF-8';
      HTTPReq.Request.Accept := 'text/xml';
      HTTPReq.Request.CustomHeaders.AddValue('SOAPAction', 'http://tempuri.org/Administration/' + method);
      HTTPReq.HTTPOptions := [];
    end;
    SendStream := TStringStream.Create(Body);
    Response := TStringStream.Create(EmptyStr);
    try
      HTTPReq.Request.ContentLength := Length(Body);

      Uri := TiDUri.Create(url);
      try
        HTTPReq.Request.Host := Uri.Host;
      finally
        Uri.Free;
      end;

      HTTPReq.Post(url + 'admin.asmx', SendStream,Response);

      if Response.Size > 0 then begin
        if assigned(OutData) then begin
          s := Response.DataString;// Redundant? Probably can just use Response.DataString?
          StrPLCopy(OutData, s, OutLen);// <- ACCESS VIOLATION HERE
          //StrPLCopy(OutData, s, Response.Size);// <- ACCESS VIOLATION HERE
          Result := 0;
        end;
      end
      else begin
        Result := -2;
      end;
    finally
      Response.Free;
      SendStream.Free;
      IdSSLIOHandler.Free;
      HTTPReq.Free;
    end;
  except
    on E:Exception do begin
      ShowMessage(E.Message);
      Result := 1;
    end;
  end;
end;

exports
  PostAdminDataViaDll;

begin
end.

我的通话方法代码:

function PostAdminData(body, method, url : string): IXMLDOMDocument;
type
   TMyPost = function (body, method, url: PChar; OutData : PChar; OutLen : integer): integer; stdcall;
var Handle : THandle;
var MyPost : TMyPost;
var dataString : string;
var returnData : string;
begin
  if not (FileExists(ExtractFilePath(Application.ExeName) + 'TestDLL.DLL')) then begin
    Application.MessageBox(pchar('Unable to find TestDLL.DLL.'), pchar('Error posting'),MB_ICONERROR + MB_OK);
    Exit;
  end;

  dataString := EmptyStr;
  returnData := '';

  Handle := LoadLibrary(PChar(ExtractFilePath(Application.ExeName) + 'TestDLL.DLL'));
  if Handle <> 0 then begin
    try
      try
        MyPost := GetProcAddress(Handle, 'PostAdminDataViaDll');
        if @MyPost <> nil then begin
          // NOTE 32767 is not big enough for the returned data! Help!
          if MyPost(PChar(body), PChar(method), PChar(url), PChar(returnData), 32767) = 0 then begin
            dataString := returnData;
          end;
        end;
      except
      end;
    finally
      FreeLibrary(Handle);
    end;
  end
  else begin
    Application.MessageBox(pchar('Unable to find TestDLL.DLL.'), pchar('Error posting'),MB_ICONERROR + MB_OK);
  end;

  if not sametext(dataString, EmptyStr) then begin
    try
      Result := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
      Result.async := False;
      Result.loadXML(dataString);
    except
    end;
  end;
end;

1 个答案:

答案 0 :(得分:3)

  

我有一个将XML数据发布到网站的DLL。 DLL是使用Delphi 10构建的,以便使用TLS 1.2,这是Delphi XE2所不具备的。

为什么不简单地将XE2中的Indy更新为支持TLS 1.2的新版本?然后你根本不需要DLL。

  

我原来的DLL约定基本上不正确,因为我将返回的文本数据的内容作为PChar返回。在我和其他地方的阅读中,这是一个很大的禁忌。

这不是一个“大禁忌”,特别是如果响应数据本质上是动态的。返回指向动态分配数据的指针是完全正常的。你只需要输出一个额外的函数来在调用者使用它时释放数据,就是这样。如果调用者忘记调用第二个函数,那么“大禁忌”就会引入潜在的内存泄漏。但这就是try..finally的好处。

  

这种“糟糕”的方法很有效,直到我开始收到大量的数据。我对它进行了测试,但是对于超过132,365个字符的任何内容都失败了。

这不是很多记忆。你遇到的任何失败都可能是因为你只是误用了记忆。

  

我重构了我的DLL并调用代码以传递缓冲区作为PChar来填写,但是我在尝试填充输出值时遇到错误!

那是因为你没有正确填写内存。

  

其次,由于我从来不知道返回的数据有多大,如何指定从调用方法中填充多大的缓冲区?

使用POST时,您不能。您必须将响应数据缓存到一边,然后公开让调用者在之后查询缓存的大小和数据的方法。

  

我的DLL代码,我收到错误:

     

我的通话方法代码:

我在该代码中看到了许多逻辑错误。

但是,访问冲突错误的最重要原因是您的EXE根本没有为其returnData变量分配任何内存。

string投射到PChar 从不会产生nil指针。如果输入string不为空,则返回指向字符串的第一个Char的指针。否则,将返回指向静态#0 Char的指针。这样可以确保string转换为PChar 总是会产生非零,以null结尾的C样式字符串。

你的EXE告诉DLL returnData最多可以容纳32767个字符,但实际上它根本不能容纳任何字符!在DLL中,OutData不是nil,而OutLen是错误的。

此外,StrPLCopy() 总是 null - 终止输出,但MaxLen参数不包含空终止符,因此调用者必须为{{1}分配空间字符。这在StrPLCopy() documentation中说明。

说完所有这些,尝试更像这样的事情:

MaxLen+1

library TestDLL;

uses
  SysUtils,
  Classes,
  Windows,
  Messages,
  Vcl.Dialogs,
  IdIOHandlerStack, IdSSLOpenSSL, IdHTTP, IdCompressorZLib;

{$R *.res}

function PostAdminDataViaDll(body, method, url: PChar;
  var OutData : PChar): integer; stdcall;
var
  HTTPReq : TIdHTTP;
  SendStream : TStringStream;
  IdSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL;
  s : string;
begin
  OutData := nil;

  try
    HTTPReq := TIdHTTP.Create(nil);
    try
      IdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTPReq);
      IdSSLIOHandler.SSLOptions.Mode := sslmClient;
      IdSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
      HTTPReq.IOHandler := IdSSLIOHandler;

      HTTPReq.Compressor := TIdCompressorZLib.Create(HTTPReq);
      HTTPReq.ReadTimeout := 180000;//set read timeout to 3 minutes
      HTTPReq.HTTPOptions := [];

      HTTPReq.Request.ContentType := 'text/xml';
      HTTPReq.Request.Charset := 'UTF-8';
      HTTPReq.Request.Accept := 'text/xml';
      HTTPReq.Request.CustomHeaders.AddValue('SOAPAction', 'http://tempuri.org/Administration/' + method);

      SendStream := TStringStream.Create(Body, TEncoding.UTF8);
      try
        s := HTTPReq.Post(string(url) + 'admin.asmx', SendStream);
      finally
        SendStream.Free;
      end;

      Result := Length(s);
      if Result > 0 then begin
        GetMem(OutData, (Result + 1) * Sizeof(Char));
        Move(PChar(s)^, OutData^, (Result + 1) * Sizeof(Char));
      end;
    finally
      HTTPReq.Free;
    end;
  except
    on E: Exception do begin
      ShowMessage(E.Message);
      Result := -1;
    end;
  end;
end;

function FreeDataViaDll(Data : Pointer): integer; stdcall;
begin
  try
    FreeMem(Data);
    Result := 0;
  except
    on E: Exception do begin
      ShowMessage(E.Message);
      Result := -1;
    end;
  end;
end;

exports
  PostAdminDataToCenPosViaDll,
  FreeDataViaDll;

begin
end.