我有一个应用程序,它加载使用Delphi GDI+ Library的DLL。当卸载该DLL(调用FreeLibrary)时,该应用程序会挂起。
我追踪了问题,发现是GdiPlus.pas单元的finalization部分调用了GdiPlusShutdown,但该函数从未返回。
如何避免这种死锁?
我追踪了问题,发现是GdiPlus.pas单元的finalization部分调用了GdiPlusShutdown,但该函数从未返回。
如何避免这种死锁?
GdiplusStartup
函数的文档如下:
通过将Delphi GdiPlus库编译为DLL,您违反了不要在
DllMain
或任何被DllMain
调用的函数中调用GdiplusStartup
或GdiplusShutdown
。如果您想创建一个使用GDI+的DLL,您应该使用以下技术之一来初始化GDI+:
- 要求客户在调用您的DLL函数之前调用
GdiplusStartup
并在使用完毕后调用GdiplusShutdown
。- 导出自己的启动函数,该函数调用
GdiplusStartup
和自己的关闭函数,该函数调用GdiplusShutdown
。要求客户在调用您的DLL中的其他函数之前调用您的启动函数,并在使用完毕后调用您的关闭函数。- 在每个调用GDI+的函数中调用
GdiplusStartup
和GdiplusShutdown
。
GdiplusStartup
和GdiplusShutdown
的规则。这些函数在initialization
和finalization
部分中调用。对于库项目,单元的initialization
和finalization
部分中的代码从DllMain
执行。DllMain
周围的限制,并确保您放置在initialization
和finalization
部分中的代码遵守这些限制。我认为这个GdiPlus库在这方面失败了。WinApi.GDIPOBJ
单元中的代码:initialization
if not IsLibrary then
begin
// Initialize StartupInput structure
StartupInput.DebugEventCallback := nil;
StartupInput.SuppressBackgroundThread := False;
StartupInput.SuppressExternalCodecs := False;
StartupInput.GdiplusVersion := 1;
GdiplusStartup(gdiplusToken, @StartupInput, nil);
end;
finalization
if not IsLibrary then
begin
if Assigned(GenericSansSerifFontFamily) then
GenericSansSerifFontFamily.Free;
if Assigned(GenericSerifFontFamily) then
GenericSerifFontFamily.Free;
if Assigned(GenericMonospaceFontFamily) then
GenericMonospaceFontFamily.Free;
if Assigned(GenericTypographicStringFormatBuffer) then
GenericTypographicStringFormatBuffer.free;
if Assigned(GenericDefaultStringFormatBuffer) then
GenericDefaultStringFormatBuffer.Free;
GdiplusShutdown(gdiplusToken);
end;
GdiPlusShutdown(以及GdiPlusStartup)不能从DllMain中调用,但当调用FreeLibrary时,Windows和Delphi运行时会调用DllMain:Delphi调用DLL使用的所有单元的终止部分,而GdiPlus终止部分调用GdiPlusShutdown(在可执行文件中使用时完全可以)。初始化部分也有类似的行为。
我通过在初始化和终止部分中添加对IsLibrary的测试来避免调用有问题的函数,还添加了两个公共过程InitializeForDll和FinalizeForDll。通过这些小改动,DLL能够导出调用InitializeForDll和FinalizeForDll的函数。这些导出函数必须由托管应用程序在加载DLL后立即调用,并在卸载DLL之前调用。
以下是我对GdiPlus.pas所做的更改:
在接口部分:
var
procedure InitializeForDll;
procedure FinalizeForDll;
procedure InitializeForDll;
begin
Initialize;
end;
procedure FinalizeForDll;
begin
Finalize;
end;
还更新了初始化和结束部分,如下所示:
Initialization
if not IsLibrary then
Initialize;
Finalization
if not IsLibrary then
Finalize;
procedure Initialize; stdcall;
begin
GdiPlus.InitializeForDll;
end;
procedure Finalize; stdcall;
begin
GdiPlus.FinalizeForDll;
end;
在调用LoadLibrary之后,初始化和完成由托管应用程序调用,就在调用FreeLibrary(或任何将加载/卸载DLL的内容)之前。
我希望这可以帮助其他人。 顺便说一句:感谢Eric Bilsen提供Delphi GdiPlus Library。
GdiplusStartup
来违反规则。可能需要更深入地阅读文档。我已经在我的答案中尝试写出它。 - David Heffernan