Program hangs when unloading a DLL making use of G

2019-06-22 12:15发布

I have an application which load a DLL that makes use of Delphi GDI+ Library. This application hangs when it unload the DLL (Calling FreeLibrary).

I tracked the issue down to GdiPlus.pas unit finalization section which calls GdiPlusShutdown which never returns.

How to avoid this deadlock?

标签: delphi dll gdi+
2条回答
疯言疯语
2楼-- · 2019-06-22 12:43

The documentation for the GdiplusStartup function says this:

Do not call GdiplusStartup or GdiplusShutdown in DllMain or in any function that is called by DllMain. If you want to create a DLL that uses GDI+, you should use one of the following techniques to initialize GDI+:

  • Require your clients to call GdiplusStartup before they call the functions in your DLL and to call GdiplusShutdown when they have finished using your DLL.
  • Export your own startup function that calls GdiplusStartup and your own shutdown function that calls GdiplusShutdown. Require your clients to call your startup function before they call other functions in your DLL and to call your shutdown function when they have finished using your DLL.
  • Call GdiplusStartup and GdiplusShutdown in each of your functions that make GDI+ calls.

By compiling this Delphi GdiPlus library into a DLL you are breaking this rule for both GdiplusStartup and GdiplusShutdown. These functions are called in unit initialization and finalization sections, respectively. And for library projects, code in the initialization and finalization sections of a unit is executed from DllMain.

It seems that the GdiPlus library that you use was never intended to be used from a library. But as a general rule, when writing library code, you should be aware of the restrictions around DllMain and make sure that code that you place in initialization and finalization sections respects that. I think that this GdiPlus library fails in that regard.

By way of contrast, have a look at the code in the Delphi RTL's WinApi.GDIPOBJ unit:

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;

This code respects the rules by making sure that it does not call GdiplusStartup and GdiplusShutdown from DllMain. Instead it leaves the onus on the author of any library that uses WinApi.GDIPOBJ to make sure that GdiplusStartup and GdiplusShutdown are called at appropriate times.

If I were you I would pick one of the three bullet point options listed above. The third of these options is not very practical, but the first two are good choices. Were it me, I would opt for the first option and modify the initialization and finalization code in your GdiPlus library to look more like that found in WinApi.GDIPOBJ.

查看更多
手持菜刀,她持情操
3楼-- · 2019-06-22 13:03

GdiPlusShutdown (and GdiPlusStartup btw) cannot be called from DllMain but DllMain is called by Windows and Delphi runtime when FreeLibrary is called: Delphi call the finalization section of all units used by the DLL and GdiPlus finalization section calls GdiPlusShutdown (This is perfectly OK when used from an executable). Similar behavior with initialization section.

I have fixed the issue by adding a test for IsLibrary in both initialization and finalization sections to avoid calling the offending functions, also added two public procedures InitializeForDll and FinalizeForDll. With those little changes, the DLL is able to export functions calling InitializeForDll and FinalizeForDll. Those exported function have to be called by the hosting application right after loading the DLL and just before unloading the DLL.

Here are the changes I made to GdiPlus.pas:

In the interface section:

var
  procedure InitializeForDll;
  procedure FinalizeForDll;

In the implementation section:

procedure InitializeForDll;
begin
  Initialize;
end;

procedure FinalizeForDll;
begin
  Finalize;
end;

Also updated the initialization and finalization sections like this:

Initialization
  if not IsLibrary then
    Initialize;

Finalization
  if not IsLibrary then
    Finalize;

In the DLL, I exported those functions:

procedure Initialize; stdcall;
begin
  GdiPlus.InitializeForDll;
end;

procedure Finalize; stdcall;
begin
  GdiPlus.FinalizeForDll;
end;

Initialize and Finalize are called by the hosting application right after calling LoadLibrary and just before calling FreeLibrary (Or whatever will load/unload the DLL).

I hope this will help others. btw: Thanks to Eric Bilsen for providing Delphi GdiPlus Library

查看更多
登录 后发表回答