Date: | January 27, 2004 / year-entry #37 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20040127-00/?p=40873 |
Comments: | 24 |
Summary: | As everybody knows by now, you're not supposed to do anything even remotely interesting in your DllMain function. Oleg Lvovitch has written two very good articles about this, one about how things work, and one about what goes wrong when they don't work. Here's another reason not to do anything remotely interesting in your DllMain:... |
As everybody knows by now, you're not supposed to do anything even remotely interesting in your DllMain function. Oleg Lvovitch has written two very good articles about this, one about how things work, and one about what goes wrong when they don't work. Here's another reason not to do anything remotely interesting in your DllMain: It's common to load a library without actual intent to invoke its full functionality. For example, somebody might load your library like this: // error checking deleted for expository purposes hinst = LoadLibrary(you); hicon = LoadIcon(you, MAKEINTRESOURCE(5)); FreeLibrary(hinst); This code just wants your icon. It would be very surprised (and perhaps even upset) if your DLL did something heavy like starting up a timer or a thread. (Yes, this could be avoided by using LoadLibraryEx and LOAD_LIBRARY_AS_DATAFILE, but that's not my point.) Another case where your library gets loaded even though no code is going to be run is when it gets tugged along as a dependency for some other DLL. Suppose "middle" is the name of some intermediate DLL that is linked to your DLL. hinst = LoadLibrary(middle); pfn = GetProcAddress(hinst, "SomeFunction"); pfn(...); FreeLibrary(hinst); When "middle" is loaded, your DLL will get loaded and initialized, too. So your initialization runs even if "SomeFunction" doesn't use your DLL. This "intermediate DLL loaded for a brief time" scenario is actually quite common. For example, if somebody does "Regsvr32 middle.dll", that will load the middle DLL to call its DllRegisterServer function, which typically doesn't do much other than install some registry keys. It almost certainly doesn't call into your helper DLL. Another example is the opening of the Control Panel folder. The Control Panel folder loads every *.cpl file so it can call its CplApplet function to determine what icon to display. Again, this typically will not call into your helper DLL. And under no circumstances should you create any objects with thread affinity in your DLL_PROCESS_ATTACH handler. You have no control over which thread will send the DLL_PROCESS_ATTACH message, nor which thread will send the DLL_PROCESS_DETACH message. The thread that sends the DLL_PROCESS_ATTACH message might terminate immediately after it loads your DLL. Any object with thread-affinity will then stop working since its owner thread is gone. And even if that thread survives, there is no guarantee that the thread that calls FreeLibrary is the same one that called LoadLibrary. So you can't clean up those objects with thread affinity in DLL_PROCESS_DETACH since you're on the wrong thread. And absolutely under no circumstances should you be doing anything as crazy as creating a window inside your DLL_PROCESS_ATTACH. In addition to the thread affinity issues, there's the problem of global hooks. Hooks running inside the loader lock are a recipe for disaster. Don't be surprised if your machine deadlocks. Even more examples to come tomorrow. |
Comments (24)
|
I notice the errors themselves weren’t checked for expository purposes :)
// error checking deleted for expository purposes
hinst = LoadLibrary(you);
hicon = LoadIcon(you, MAKEINTRESOURCE(5));
FreeLibrary(hinst);
Sometimes doing these kind of "immoral" things is the only way though. Several times I’ve had to do ill advised things in DllMain because I absolutely need to ensure the dll doesn’t load if some condition isn’t met. It seems rather unfortunate that the only mechanism provided to do this shouldn’t (in theory) be used to do it.
Is this "loader deadlock" responsible for the phenomenon where Windows gets into a state where you can’t start any applications? (double-click and they just do nothing)
Shane: Can you provide examples? I’d be interested in knowing any loader problems that can’t be worked around.
Dan: On Windows NT, the loader deadlock issue is local to the process. Each process has its own loader lock.
Dan, you have probably run out of desktop heap. Happens with 30-ish IE windows open (or a similar number of other apps):
http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q126/9/62.ASP&NoWebContent=1
Dan: Yes, the loader lock IS responsible for some of those situations. Especially on Win9x.
And even more to the point, it turns out that many of these situations are undebuggable – the problem is that the debuggers can’t break into the app because to break into the app, they need to create a thread, which means that the loader lock needs to be taken (to call the DllMain in each DLL to tell them that the thread was created), but that hangs because someone has the loader lock held and hasn’t released it.
The most heinous example of this is DllMain functions that do:
DllMain(<whatever)>)
{
if (ulReason == DLL_PROCESS_ATTACH)
{
CreateThread(ThreadToInitializeStuff);
WaitForSingleObject(EventThatsSetWhenThreadRuns);
}
:
:
}
This is a recipe for deadlocks, unfortunately there’s way too much code that does this.
One nasty problem is that this SOMETIMES works – not always, and not on all versions of Windows.
One scenario is where I’m loading the dll as a CLR profiler, which allows me to modify .NET code on the fly for tracing and debugging purposes.
The profiler needs to have the chance to load on the startup of every .NET program, because you can’t control the environment variables that cause the CLR to choose to laod it for a dllhost based process. Therefore, you must modify the global environment variables to cause it to always load.
The problem is, for most processes, I don’t want it to be loaded. So I have to deliberately cause a failure result from DllMain to cause it to not be loaded and held in memory by the CLR. This is being determined via the registry, which is apparently a no-no in DllMain.
I’d love for there to be another way (particuarly since returning failure from DllMain causes nasty event log messages about not being able to load the profiler), but if you return failure from the initialization function of the profiler, it holds it in memory anyway (which is unnaceptable, as it makes replacing the dll with a new version needlessly difficult, as you’ll need to shut down every process in which the CLR is active. This is particuarly a pain when developing the component, as Visual Studio .NET contains managed code).
Probably not the most common case in the world, but it’s one where the DllMain approach seems to work well. Since it’s basically used for debugging problems on production type machines where installing a real debugger is unacceptable, I don’t particuarly care if a future version of windows breaks it. It’s not like it’s going to hurt anyone.
http://blogs.msdn.com/cbrumme/archive/2003/08/20/51504.aspx
OCXs create this problem. VB is quite willing to unload an OCX while it is still running. So, the DLL’s get unloaded, but the process is not in termination.
It is a great way for a thread to wake up and land in code space that is no longer loaded.
You can make a self reference to the dll so that it won’t unload, but that can create other problems.
Personally, I think the bug is unloading of a DLL/OCX during runtime, but the VB development environment is quite willing to do it. If Microsoft does it, it must be correct…. grin.
Mr. Chen, speaking of the MSDN page which Mr. Chen linked to, http://msdn.microsoft.com/library/en-us/dllproc/base/dllmain.asp
Here’s another example where MSDN has recursion instead of useful information: "DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools."
I don’t understand "DllMain is a placeholder for the library-defined function name" because whenever I’ve seen DllMain the name has always been DllMain. Therefore I tried to obey "For more information, see the documentation included with your development tools."
Of course I first saw those instructions in documentation that came on CD instead of seeing them on Microsoft’s web site. Guess what documentation is included with my development tools.
Mr. Lebedinsky, thank you for your explanation of DllMainCRTStartup but I’m still confused by MSDN. I think the MSDN page on DllMain is intended to instruct a DLL programmer on how to write a DllMain in VC++. I don’t think that MSDN page is intended to instruct a CRT programmer on how to make a CRT support the writing of DLLs in VC++.
For comparison, if MSDN has a page on coding a main() (I’m neglecting to check if it does), I don’t think it instructs CRT programmers on how to initialize the CRT and call the C program’s main().
(Meanwhile the recursion in that MSDN page is still there and it’s about to overflow my stack.)
When the DLL is initialised, the loader jumps to the address in the AddressOfEntryPoint member of the IMAGE_OPTIONAL_HEADER for the file. With MS tools, you can specify this with the linker’s /ENTRY option.
If you don’t specify /ENTRY, the MS linker defaults to _DllMainCRTStartup if you’ve also specified /DLL. This function (as Raymond alludes to on the other thread) has an explicit reference to the function name DllMain. It’s always statically linked to your binary, even if you’re using the DLL version of the CRT (it would be a chicken/egg situation otherwise). The CRT has a special weak-reference to an internal do-nothing DllMain so that your DLL still links even if you don’t provide a DllMain of your own.
MSDN does have a page on WinMain, although it doesn’t cover wWinMain. wWinMain is a fake provided by the CRT which uses GetCommandLine to retrieve the Unicode command line and pass it as the lpCmdLine parameter. main and wmain are also wrapped up by corresponding CRTStartup functions in the CRT, to present a WinMain-compatible entry point for the loader.
Indeed, all of the [w][Win]{M|m}ainCRTStartup functions share the same code, implemented in crt0.c (which you can read if you’ve installed the CRT source code).
If you have to specify your own entry point but still need the CRT (maybe you need to initialise something before the CRT starts up), call _cinit to initialise the CRT.
A peculiarity of Windows CE is that it has no wWinMain; instead, WinMain’s lpCmdLine parameter is a LPWSTR. CE has no need for ANSI backward compatibility, of course. For CE, the CRT is part of the core system DLL, coredll.dll, which also includes all of the (supported) system entry points from the desktop’s kernel32.dll, advapi32.dll, user32.dll, gdi32.dll and some others. The xxxCRTStartup functions are implemented in corelibc.lib.
1/30/2004 7:09 AM Mike Dimmick:
Thank you for the detailed explanation of DllMain and [w]WinMain. When I have a free hour or two I might indeed look at the source of crt0.c. But actually the part of your posting I found most important was this:
> This function […] has an explicit
> reference to the function name DllMain.
So my function still really has to be named DllMain(), and the MSDN page about DllMain might benefit by deleting threee sentences.
Now for one tangent.
> CE has no need for ANSI backward
> compatibility, of course.
For one application I accomplished that happy result by writing "