使用GdiPlus卸载DLL时程序挂起

时间:2016-01-19 08:00:57

标签: delphi dll gdi+

我有一个加载使用Delphi GDI+ Library的DLL的应用程序。该应用程序在卸载DLL(调用FreeLibrary)时会挂起。

我将问题追溯到GdiPlus.pas单元终结部分,该部分调用了永不返回的GdiPlusShutdown。

如何避免这种僵局?

2 个答案:

答案 0 :(得分:4)

GdiplusStartup功能的文档说明了这一点:

  

请勿在{{1​​}}或任何内容中致电GdiplusStartupGdiplusShutdown   由DllMain调用的函数。如果要创建一个DLL   使用GDI +,您应该使用以下技术之一   初始化GDI +:

     
      
  • 要求您的客户在调用DLL中的函数之前致电DllMain,并在他们拥有时调用GdiplusStartup   完成了你的DLL。
  •   
  • 导出您自己的调用GdiplusShutdown的启动函数和您自己的调用GdiplusStartup的关闭函数。需要你的客户   在他们调用你的其他功能之前调用你的启动功能   DLL并在完成使用后调用关闭函数   你的DLL。
  •   
  • 在进行GDI +通话的每个功能中调用GdiplusShutdownGdiplusStartup
  •   

通过将此Delphi GdiPlus库编译为DLL,您违反了GdiplusShutdownGdiplusStartup的此规则。这些函数分别在单元GdiplusShutdowninitialization部分中调用。对于图书馆项目,单元的finalizationinitialization部分中的代码是从finalization执行的。

您使用的GdiPlus库似乎从未打算从库中使用。但作为一般规则,在编写库代码时,您应该了解DllMain周围的限制,并确保放在DllMaininitialization部分的代码尊重该代码。我认为这个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; 调用GdiplusStartupGdiplusShutdown来尊重规则。相反,它会让使用DllMain的任何库的作者承担责任,以确保在适当的时间调用WinApi.GDIPOBJGdiplusStartup

如果我是你,我会选择上面列出的三个项目符号选项之一。这些选项中的第三个不太实用,但前两个是不错的选择。如果是我,我会选择第一个选项并修改GdiplusShutdown库中的initializationfinalization代码,使其更像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