使用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;
答案 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.