我有一个加载使用Delphi GDI+ Library的DLL的应用程序。该应用程序在卸载DLL(调用FreeLibrary)时会挂起。
我将问题追溯到GdiPlus.pas单元终结部分,该部分调用了永不返回的GdiPlusShutdown。
如何避免这种僵局?
答案 0 :(得分:4)
GdiplusStartup
功能的文档说明了这一点:
请勿在{{1}}或任何内容中致电
GdiplusStartup
或GdiplusShutdown
由DllMain
调用的函数。如果要创建一个DLL 使用GDI +,您应该使用以下技术之一 初始化GDI +:
- 要求您的客户在调用DLL中的函数之前致电
DllMain
,并在他们拥有时调用GdiplusStartup
完成了你的DLL。- 导出您自己的调用
GdiplusShutdown
的启动函数和您自己的调用GdiplusStartup
的关闭函数。需要你的客户 在他们调用你的其他功能之前调用你的启动功能 DLL并在完成使用后调用关闭函数 你的DLL。- 在进行GDI +通话的每个功能中调用
GdiplusShutdown
和GdiplusStartup
。
通过将此Delphi GdiPlus库编译为DLL,您违反了GdiplusShutdown
和GdiplusStartup
的此规则。这些函数分别在单元GdiplusShutdown
和initialization
部分中调用。对于图书馆项目,单元的finalization
和initialization
部分中的代码是从finalization
执行的。
您使用的GdiPlus库似乎从未打算从库中使用。但作为一般规则,在编写库代码时,您应该了解DllMain
周围的限制,并确保放在DllMain
和initialization
部分的代码尊重该代码。我认为这个GdiPlus库在这方面失败了。
通过对比,看看Delphi RTL的finalization
单元中的代码:
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;
调用GdiplusStartup
和GdiplusShutdown
来尊重规则。相反,它会让使用DllMain
的任何库的作者承担责任,以确保在适当的时间调用WinApi.GDIPOBJ
和GdiplusStartup
。
如果我是你,我会选择上面列出的三个项目符号选项之一。这些选项中的第三个不太实用,但前两个是不错的选择。如果是我,我会选择第一个选项并修改GdiplusShutdown
库中的initialization
和finalization
代码,使其更像GdiPlus
中的代码。
答案 1 :(得分:3)
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;
在DLL中,我导出了这些函数:
procedure Initialize; stdcall;
begin
GdiPlus.InitializeForDll;
end;
procedure Finalize; stdcall;
begin
GdiPlus.FinalizeForDll;
end;
在调用LoadLibrary之后,在调用FreeLibrary之前,或者在加载/卸载DLL的任何内容之前,托管应用程序立即调用Initialize和Finalize。
我希望这会对别人有所帮助。 顺便说一句:感谢Eric Bilsen提供了Delphi GdiPlus Library