Program freezes when loading DLL using GdiPlus

I have an application that loads a DLL that uses Delphi GDI + Library . This application freezes when it unloads a DLL (Calling FreeLibrary ).

I traced the problem to the final configuration section of the GdiPlus.pas module, which calls GdiPlusShutdown, which never returns.

How to avoid this deadlock?

+5
source share
2 answers

The documentation for the GdiplusStartup function says the following:

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

  • Require your clients to call GdiplusStartup before they call functions in your DLL, and call GdiplusShutdown when they have finished using your DLL.
  • Export your own startup function, which calls GdiplusStartup and your own shutdown function, which calls GdiplusShutdown . Require your clients to call your startup function before they call other functions in your DLL and call the 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 violate this rule for both GdiplusStartup and GdiplusShutdown . These functions are called in initialization and finalization respectively. And for library projects, the code in the initialization and finalization block is executed from DllMain .

It seems that the GdiPlus library you are using was never intended to be used from a library. But as a rule, when writing library code, you should be aware of the limitations around DllMain and make sure that the code you place in the initialization and finalization sections applies to this. I think this GdiPlus library is failing in this regard.

As a contrast, take a look at the code in the 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; 

This code follows the rules, making sure that it does not call GdiplusStartup and GdiplusShutdown from DllMain . Instead, he assumes responsibility for the author of any library that uses WinApi.GDIPOBJ to ensure that GdiplusStartup and GdiplusShutdown are called at the appropriate times.

If I were you, I would choose one of the three bullet point parameters listed above. The third of these options is not very practical, but the first two are a good choice. If it were me, I would choose the first option and change the initialization and finalization in your GdiPlus library to look more like the one found in WinApi.GDIPOBJ .

+4
source

GdiPlusShutdown (and GdiPlusStartup btw) cannot be called from DllMain, but DllMain is called by Windows and Delphi at runtime when FreeLibrary is called: Delphi calls the completion section of all blocks used by the DLL and GdiPlus finalization sections, calls GdiPlusShutdown (this is fine OK when used from executable file). Similar behavior with the initialization section.

I fixed the problem by adding a test for IsLibrary in the initialization and completion sections to avoid invoking the violating functions, I also added two public procedures InitializeForDll and FinalizeForDll. With these small changes, the DLL can export functions that call InitializeForDll and FinalizeForDll. These exported functions should be called by the hosting application immediately after loading the DLL and immediately before the DLL is unloaded.

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; 

The initialization and completion sections have also been updated:

 Initialization if not IsLibrary then Initialize; Finalization if not IsLibrary then Finalize; 

In the DLL, I exported these functions:

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

Initialization and termination is called by the hosting application immediately after calling LoadLibrary and immediately before calling FreeLibrary (or whatever the DLL will load / unload).

Hope this helps others. btw: Thanks to Eric Bilsen for providing the Delphi GdiPlus Library

+3
source

Source: https://habr.com/ru/post/1240899/


All Articles