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 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
.
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