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?
The documentation for the GdiplusStartup function says this:
Do not call
GdiplusStartuporGdiplusShutdowninDllMainor in any function that is called byDllMain. 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
GdiplusStartupbefore they call the functions in your DLL and to callGdiplusShutdownwhen they have finished using your DLL.- Export your own startup function that calls
GdiplusStartupand your own shutdown function that callsGdiplusShutdown. 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
GdiplusStartupandGdiplusShutdownin 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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With