【发布时间】:2016-01-19 08:00:56
【问题描述】:
我有一个应用程序加载一个使用Delphi GDI+ Library 的DLL。此应用程序在卸载 DLL 时挂起(调用 FreeLibrary)。
我将问题追踪到 GdiPlus.pas 单元完成部分,该部分调用 GdiPlusShutdown,但永远不会返回。
如何避免这种死锁?
【问题讨论】:
我有一个应用程序加载一个使用Delphi GDI+ Library 的DLL。此应用程序在卸载 DLL 时挂起(调用 FreeLibrary)。
我将问题追踪到 GdiPlus.pas 单元完成部分,该部分调用 GdiPlusShutdown,但永远不会返回。
如何避免这种死锁?
【问题讨论】:
GdiplusStartup 函数的文档是这样说的:
请勿在
DllMain或任何电话中致电GdiplusStartup或GdiplusShutdownDllMain调用的函数。如果你想创建一个 DLL 使用 GDI+,您应该使用以下技术之一 初始化 GDI+:
- 要求您的客户在调用 DLL 中的函数之前调用
GdiplusStartup,并在调用时调用GdiplusShutdown使用完您的 DLL。- 导出您自己的调用
GdiplusStartup的启动函数和您自己的调用GdiplusShutdown的关闭函数。要求您的客户 在他们调用你的其他函数之前调用你的启动函数 DLL 并在它们完成使用后调用您的关闭函数 你的 DLL。- 在调用 GDI+ 的每个函数中调用
GdiplusStartup和GdiplusShutdown。
通过将此 Delphi GdiPlus 库编译为 DLL,您将违反 GdiplusStartup 和 GdiplusShutdown 的这条规则。这些函数分别在单元initialization 和finalization 部分中调用。对于库项目,单元的initialization 和finalization 部分中的代码从DllMain 执行。
您使用的 GdiPlus 库似乎从未打算从库中使用。但作为一般规则,在编写库代码时,您应该了解DllMain 周围的限制,并确保您放置在initialization 和finalization 部分的代码尊重这一点。我认为这个 GdiPlus 库在这方面失败了。
作为对比,看看 Delphi RTL 的 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;
此代码遵守规则,确保不会从DllMain 调用GdiplusStartup 和GdiplusShutdown。相反,它让任何使用 WinApi.GDIPOBJ 的库的作者承担责任,以确保在适当的时间调用 GdiplusStartup 和 GdiplusShutdown。
如果我是你,我会选择上面列出的三个要点选项之一。这些选项中的第三个不是很实用,但前两个是不错的选择。如果是我,我会选择第一个选项并修改GdiPlus 库中的initialization 和finalization 代码,使其看起来更像WinApi.GDIPOBJ 中的代码。
【讨论】:
GdiPlusShutdown(和 GdiPlusStartup btw)不能从 DllMain 调用,但是当调用 FreeLibrary 时,DllMain 由 Windows 和 Delphi 运行时调用: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;
在 DLL 中,我导出了这些函数:
procedure Initialize; stdcall;
begin
GdiPlus.InitializeForDll;
end;
procedure Finalize; stdcall;
begin
GdiPlus.FinalizeForDll;
end;
Initialize 和 Finalize 由宿主应用程序在调用 LoadLibrary 之后和调用 FreeLibrary(或任何将加载/卸载 DLL 的方法)之前调用。
我希望这对其他人有所帮助。 btw:感谢Eric Bilsen提供Delphi GdiPlus Library
【讨论】:
GdiplusStartup 的规则。可能需要更深入地阅读文档。我已尝试将其写在我的答案中。