Mike van der Meulen's Blog about day-to-day, work-related "stuff", often technical, rarely exciting, just the everyday things that go on.
This problem has plagued us since we started using GDI+ in some of our products (http://www.softelvdm.com).
The issue first appeared when we added GDI+ support for translucent images (with alpha-channel) to our DLL-based tree control for C/C++ (SftTree/DLL). During development of the release incorporating GDI+, we found that during application shutdown, the application started to "hang" forever. The application really had ended already, but it was still shown as a process in task manager. It also wasn't possible to delete the executable, because it was still in use. After spending some time debugging, it was determined that our call to GdiplusShutdown was causing the problem, since it never returned to our DLL. We "fixed" it initially, but later on found additional problems with ActiveX controls.
First, I checked the documentation. It says, you can't use GdiplusStartup and GdiplusShutdown in DllMain. That was a clue. Well, since our control is a DLL, it starts things up when the DLL is loaded and terminates everything as it's unloaded (yes, that's in DllMain, or at least code that is run called from the C runtime's equivalent to DllMain).
That really left me stumped. Our control is a DLL. I could call GdiplusStartup and GdiplusShutdown as each window is created (really in WM_CREATE or WM_NCCREATE) and as it's destroyed (WM_DESTROY or WM_NCDESTROY). By using a simple use counter, that would limit initialization and termination to the first or last window respectively. There were no calls for any additional windows.
That sounds OK in theory. In reality, it just didn't work. Why not? Well, assuming you have just one window. You allocate some GDI+ objects, like images, to be used by the control. Then, you destroy the window and recreate it immediately. You just called GdiplusShutdown and invalidated all your GDI+ objects. At first, this sounds like an impossible scenario. Who would do this? Unfortunately, as we implemented SftTree/OCX we ran into this exact behavior. Actually, we knew about it all along. This happens routinely when using ActiveX controls with .NET (C#, VB.NET, etc.).
So, at this point, we could not call GdiplusShutdown when the last window was destroyed and obviously not during DLL termination (DllMain). So what other possibility is there?
Microsoft's documentation has the following to say about my options:
A) and B) are not viable, since controls should not require an explicit initialization call from an application (at least for ActiveX controls). That would look very odd. It would be possible, but anyone upgrading from an older release would have to add code to their application. If all else fails, this could be a possible workaround.
C) That really didn't make any sense, because once you call GdiplusShutdown, all your objects are invalid.
At this point, I did what everybody else usually does (well, at least after I was done cursing). I googled it - "GdiplusShutdown hangs". Yes, there are lots of entries, by equally frustrated developers. This turned out to be a dead end. Except, for some post I found about a mystery thread, but there was no immediate solution or workaround, other than the ones I already dismissed.
But somehow, this "mystery thread" triggered something in my mind when I later went back to the documentation:
GdiplusStartup has a few parameters that probably everyone pretty much ignores. But, the second parameter (GdiplusStartupInput) holds the now obvious solution. SuppressBackgroundThread can be set to TRUE, but then you would have to explicitly call the hook and unhook functions returned in the third parameter (GdiplusStartupOutput).
I went from this:
static GdiplusStartupInput gdiplusStartupInput;
static GdiplusStartupOutput gdiplusStartupOutput;
// Initialization
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput,
&gdiplusStartupOutput);
// Termination
GdiplusShutdown(gdiplusToken); to this:
static GdiplusStartupInput gdiplusStartupInput;
static GdiplusStartupOutput gdiplusStartupOutput;
// Initialization
gdiplusStartupInput.SuppressBackgroundThread = TRUE;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput,
&gdiplusStartupOutput);
Status stat = gdiplusStartupOutput.NotificationHook(
&gdiplusBGThreadToken);
ASSERT(stat == Ok);
// Termination
gdiplusStartupOutput.NotificationUnhook(gdiplusBGThreadToken);
GdiplusShutdown(gdiplusToken); It appears GDI+ creates a background thread (why?), which is causing the shutdown problem. By explicitly calling the hook and unhook functions, everything works just fine even when (and especially when) called in DllMain.
Loading
Thank you.
This post is a life saver!
This post is a life saver!
Thanks a lot! Most useful!
Wow! This post is two and a half year old, but still useful !
It saved my life today. I even started to plan not to shutdown GDI+ at all in order to make my ActiveX plug-in work.
Thanks !!!
echo Olivier: a life saver.