Date: | February 12, 2007 / year-entry #50 |
Tags: | history |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20070212-01/?p=28053 |
Comments: | 15 |
Summary: | If you look at the documentation for the UnregisterClass function, you'll see that it calls out different behavior depending on whether you're running Windows 95 or Windows NT. Commenter Vipin asked why Windows NT doesn't follow Windows 95's lead. Back in the old days, 16-bit Windows did unregister classes automatically when a DLL unloaded. It had to do this... |
If you look at the documentation for the Back in the old days, 16-bit Windows did unregister classes automatically when a DLL unloaded. It had to do this since all of 16-bit Windows ran in a single address space. If a module's classes were not unregistered when it was unloaded, then all of these leaked classes would clog up the system until it was restarted. Since instance handles were just selectors, and by their nature, selectors could be re-used, it meant that a leaked window class could prevent some totally unrelated program from starting. With 32-bit Windows and separate address spaces, a leaked window class affects only the process into which it was leaked. The scope of the damage was contained, and indeed, the scope was limited precisely to the program that did something wrong. If a program leaked a class, it affected only itself. (I suspect there is a contigent of my readership who considers this a good thing. Make programs that screw up pay for their mistake, but don't let them impact other programs.) In addition to the philosophical reason for not unregistering classes, there is also a technological one: Sure, you can unregister the classes, but a DLL that forgets to unregister its classes may very well be so careless as to unload itself while there were still windows left undestroyed! Even if you unregistered the class, those windows are going to crash once the windows receive a message and the window procedure is called. There's also the issue of subclassing. If the module had subclassed a window, unloading the module will leave the subclassed window procedure dangling. The problem isn't solved; it's just papered over. The third reason is architectural. Unregistering a module's classes when it unloads means that there is now an "upward dependency": You've made the kernel call into the window manager. When a module unloads, the kernel needs to call into the window manager to say, "Hey, I just unloaded this module. You might want to clean up stuff." This means that non-GUI programs still have a dependency on the window manager, something you hard-core command line junkies probably would find distasteful. "Why does my non-GUI program have a dependency on the GUI?" There was no such thing as a command line 16-bit Windows program, so this sort of upward dependency wasn't really a problem. But upward dependencies violate modern software design principles. A low-level component should not depend on a higher-level component. Since Windows NT was a "next generation" operating system, the designers chose to take it as an opportunity to clean up some of the expediencies that had built up in 16-bit Windows. This is another example of "no matter what you do, somebody will hate it." The Windows NT folks decide to do some architectural clean-up, the sort of thing one faction of my readership applauds, while another faction argues that, no, it should have made the architecture dirtier in order to solve the problem so applications don't have to. |
Comments (15)
Comments are closed. |
This seems to be another of those things where the OS can clean up after processes quite well, but it can’t clean up after DLLs very well at all. Opened file handles, for instance, are treated the same way — if your DLL opens a file but the file isn’t closed before the DLL is unloaded, the file will stay open in the hosting process until it exits (at which point the OS closes the file). Memory is the same way — pagetable allocations are per process, not per module.
In any case, it’s easy to track which process has registered a window class, or opened a file, or allocated memory. So you can clean up after a process exits, whether it was a clean exit or an unclean one. But it’s not easy (with current OSes anyway; I don’t know whether it’s possible with some other OS design) to track which module did these things, so you can’t clean up after a DLL unload.
In short: DLLs are not processes, and it’s currently not possible to treat them like they were. So don’t. ;-)
As an aside :
You could have some notification system (something like KiRegisterDllUnloadNotifyHandlerPlease :)) which does not create dependencies because works the other way (the WM loads and asks the kernel to be notified) and has the advantage of being generic enough (it can be used by .. whatever other software has a similar need).
This is not to say that I think the decision is wrong – I think it was the right one but the upward dependency argument can be easily circumvented if it’s worth to.
BrianK > Uh – what about where a DLL creates a resource which the caller is supposed to clean up? Say you write a library of useful functions that includes my_strdupf(char const *, …) that works like sprintf() but allocates the return buffer itself so you don’t have to, like strdup(). As with strdup(), it’s up to the caller to free() the memory returned by the function.
Now, what happens when your useful library gets unloaded? Suddenly all those strings returned by my_strdupf() that your program was hanging onto and was going to free in good time get deleted from underneath it! As soon as you try to use them, BOOM, invalid memory access.
You can do the same thing with file handles; write a function that searches, say, the MY_SPECIAL_PATH environment variable for a bare filename (no preceding directory) to open and returns an otherwise normal file handle to the caller. Read and write with fread()/fwrite()/fprintf(), and close with fclose(). Again, it all goes pear-shaped when the DLL gets unloaded and the app tries to access a file that was previously open.
They track every memory allocation, and thus every new object, why don’t the file and UI APIs work the same way? They could even use reference counting (or various other methods) to do garbage collection and ensure that something isn’t needed any more before it’s unloaded, and unloaded when it isn’t needed any more.
How so? Memory is tracked just like file handles and so on. When the process exits, the OS cleans up the memory, just like it cleans up file, window, socket, and other handles.
The point of the article is that these things are "tracked" by PROCESS not by MODULE, so it can be automatically freed when the process exits but not when the module is unloaded.
Actually, the point is that you should probably be freeing these things yourself, rather than relying on the system to do it for you…
Nar, memory allocations are not tracked. Their lifetime is intrinsic, not associated to the lifetime of any other object (well, no… they are tied to the heap they’re allocated from. But the heap, in turn, isn’t tied to anything)
Um, OK? I don’t think I said that the kernel *should* try to free resources that a DLL allocated when the DLL was unloaded; I just said that with current OSes (that I know of), it can’t, because that stuff isn’t tracked per module. (This applies to more than just window classes, in other words.) You point out a reason that memory shouldn’t be tracked per module, but I agree: it shouldn’t. In fact, it can’t. ;-)
Now, when a DLL allocates memory for you (e.g. your my_strdupf function), the API is supposed to document how to free that memory. And most of the time, on Windows anyway, the DLL will have to provide a separate function that the caller is supposed to use to free the memory — this way, the DLL can use its own heap to do the free. And in that case, if the DLL gets unloaded, you can’t free the memory anymore anyway; you have to wait for the process to die. Reloading the DLL *might* work, but I doubt it: the heap structure that it uses would have to be self-describing enough that it can handle freeing memory without the rest of the heap that it came from being around anymore.
(And on Windows, there are several different C libraries. If your program is linked against a different library than the DLL — or even a different library version, e.g. multi-thread vs. single-thread — then if you try to free() the memory yourself you’ll cause problems. You have to call the free() that matches the malloc() that was originally used. So unless you *know* that your callers will *always* use the same C library that you’re using, don’t write your my_strdupf function that way. Provide a call into your DLL instead, so your DLL can use your C library’s free() function. No, I don’t like this design, but it’s a consequence of the way the Windows C libraries were written way back when, and I doubt it can be changed now. I actually like systems where there’s a single C library for everything, and that library handles back-compat on its own (using versioned symbols to keep the old code around in the tree; yes, this requires linker support for user programs). But that isn’t going to happen anytime soon on Windows.)
Something out-of-topic here…
I installed fiddler yesterday, and today when I visited this blog I noticed the following error:
Fiddler has detected a protocol violation in session #1012.
The Server did not return properly formatted HTTP Headers. HTTP headers
should be terminated with CRLFCRLF. These were terminated with LFLF.
The error does not occur in other’s blog like Larry’s or Rico’s, don’t everyone in blogs.msdn.com use the same blog web application?
BrianK, single C library: I think that the Win32’s kernel32.dll was designed to be the common library for that kind of thing; kernel32.dll does a lot of things that a classic UNIXy C library does like a front-line API for accessing files, heap functions (three sets actually: Heap*, Local* and Global*), text conversion functions, process, console, threading, etc.
There are lots of Win32 components that return buffers to the caller from kernel32’s "local" heap to be freed by LocalFree. See
http://www.google.com/search?hl=en&q=localfree+site%3Amsdn2.microsoft.com&btnG=Search
A good article on different standard Windows heaps:
http://msdn2.microsoft.com/en-us/library/ms810466.aspx
Back on topic, a little nit about the kernel having to call back into user mode: isn’t the module loader implemented mostly in user mode, in the ntdll!Ldr* functions? AFAIK, the only thing the kernel does is provide a little special handling for mapping image sections. Things like load/unload notification are done in user mode. Even so, the only function that would need to be modified is kernel32!FreeLibrary, to find and delete window classes registered with the same HMODULE. This would still be a serious upwards-dependency since window classes are implemented in user32, which kernel32 isn’t and shouldn’t be dependent on.
And as BrianK said, it’s impractical to track every resource a dll allocates (e.g. files) so why give window classes special treatment?
Cheong: That happens on the request to http://www.assoc-amazon.com — it’s not a problem with blogs.msdn.com per-se.
Dean: You’re right, I should have checked the offening session URL first.
Amazing to find that it also misspelled the word "Connection" as "nnCoection" in the header… :O
See Fun with HTTP Headers by Andrew Wooster:
http://www.nextthing.org/archives/2005/08/07/fun-with-http-headers
"imdb.com, amazon.com, gamespy.com, and google.com have all at various times used these or similar misspellings of connection, and I’m not by any means the first to have noticed. My first thought was that this was just a typo. After more consideration, however, I now believe this is something done by a hackish hardware load balancer trying to “remove” the connection close header when proxying for an internal server. That way, the connection can be held open and images can be transmitted through the same TCP connection, while the backend web server doesn’t need to be modified at all. It just closes the connection and moves on to the next request…"
> BrianK, single C library: I think that the Win32’s kernel32.dll was designed to be the common library for that kind of thing
Except it’s not a *C* library: a program intended to be portable can’t use it. ;-)
There is no malloc()/free() in kernel32, only Global*, Local*, and Heap* — some of which are direct drop-in replacements for malloc/free (so portability can be obtained with a pair of #defines), but none of which can be *mixed* with malloc/free on a single buffer. If your DLL uses malloc, then the calling program can’t use GlobalFree, LocalFree, *or* HeapFree. It *must* use free() (and it *must* be the free() from the same C library that the DLL used).
But this is getting a bit off topic; I think I’ll stop here.
“The third reason is architectural. Unregistering a module’s classes when it unloads means that there is now an “upward dependency”: You’ve made the kernel call into the window manager. When a module unloads, the kernel needs to call into the window manager to say, “Hey, I just unloaded this module. You might want to clean up stuff.” This means that non-GUI programs still have a dependency on the window manager, something you hard-core command line junkies probably would find distasteful. “Why does my non-GUI program have a dependency on the GUI?””
Unless I’m misunderstanding you, that’s not true at all. The kernel already does this in PspExitThread and PspExitThread by using the Win32k Process/Thread callouts, if the thread/process is a GUI One (by checking if KTHREAD->Win32Thread is != NULL). So why couldn’t the same check be done to DLL unload, since it’s already being done with threads/processes.
Will all of you stop being lazy arses and start writing your own cleanup code instead of relying on the OS to do the cleanup for you?!?!?