Date: | July 14, 2006 / year-entry #235 |
Tags: | history |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060714-16/?p=30513 |
Comments: | 20 |
Summary: | The whole point of dynamic link libraries (DLLs) is that the linkage is dynamic. Whereas statically-linked libraries are built into the final product, a module that uses a dynamically-linked library merely says, "I would like function X from Y.DLL, please." This technique has advantages and disadvantages. One advantage is more efficient use of storage, since... |
The whole point of dynamic link libraries (DLLs) is that the linkage is dynamic. Whereas statically-linked libraries are built into the final product, a module that uses a dynamically-linked library merely says, "I would like function X from Y.DLL, please." This technique has advantages and disadvantages. One advantage is more efficient use of storage, since there is only one copy of Y.DLL in memory rather than a separate copy bound into each module. Another advantage is that an update to Y.DLL can be made without having to re-compile all the programs that used it. On the other hand, the ability to swap in functionality automatically is also one of the main disadvantages of dynamic link libraries, because one program can change a DLL that has cascade effects on other clients of that DLL. Anyway, let's start with how 16-bit Windows managed imports and exports. After that, we'll see how things changed during the switch to 32-bit Windows, and then we'll take a look at the compiler-specific A 16-bit DLL has not one but three export tables. (Things are actually more complicated than I describe them here, but I'm going to skip over the nitpicky details just to keep everyone's heads from exploding.) The most important table is a sparse array of functions, indexed by a 1-based integer (the "ordinal"). It is this function table that is the master list of all exported functions. If you request a function by ordinal, the ordinal is looked up in this table. The table is physically rather complicated due to the sparseness, but logically, it looks like this:
The first column in the table is the ordinal of the function, and the second function describes where the function can be found. (Notice that there is no function 3 or 4 in this DLL.) Things get interesting when you want to export a function by name. The exported names table is a list of function names with their associated ordinal equivalents. For example, a section of the exported names table for the 16-bit window manager (USER) went like this:
If somebody asks for the address of the function Wait, did I say the exported names table? I'm sorry, that was an oversimplification. There are actually two exported names tables, the resident names table and the non-resident names table. As their names suggest, the names in the resident names table remain in memory as long as the DLL is loaded, whereas the names in the non-resident names table are loaded into memory only when somebody calls The large size of the table for exported function names meant that only functions that are passed to Since obtaining a function by name is so expensive (requiring the non-resident names table to be loaded from disk so it can be searched), all functions exported by operating system DLLs are exported both by name and by ordinal, with the ordinal taking precedence in the import library table. Obtaining a procedure address by ordinal avoids the name tables entirely. Notice that every named function has a corresponding ordinal. If you do not assign an ordinal to your named function in your module definition file, the linker will make one up for you. (However, the value that it makes up need not be the same from build to build.) This situation did not occur in practice, for as we noted above, everybody explicitly assigned an ordinal to their exports and put that ordinal in the import library in order to avoid the huge cost of a name-based function lookup. That's a quick look at how functions were exported in 16-bit Windows. Next time, we'll look at how they are imported. |
Comments (20)
Comments are closed. |
Oh man, 16-bit. This awakes some memories. Like that time I was woken by the phone in an ungodly hour by the FoxPro Norwegian team, responding to my (quite serious) complain that their import .lib exported the WEP!
For the ones lucky enough to never having explored these deep and dark corners of Windows 16-bit, WEP was the “Windows Exit Procedure” (IIRC – Raymond will correct me if I’m wrong). It was a very special named function exported by DLLs to be called when all references to the module had been released, i.e. no programs used the DLL anymore. This was required as all applications and the system itself shared a single address space resource, and the DLL had to have a way to clean up what resources it allocated before it was unloaded.
The thing was, while you usually did write a WEP for moderately and more complex DLLs, you never EVER exported it like these morons did. You put it in the exported names table in the DLL, but you NEVER put it in the import library for that DLL. Doing so would make it effectively impossible for anyone else writing a DLL to use your DLL.
Well, guess what. That was a scenario the FoxPro developers never considered (one could rightfully, and in the most polite way, say “they didn’t really and fully know what they were doing”), and so I was left with a DLL I couldn’t link, and a phone call at an hour I didn’t appreciate.
What really made me see the quality of this was when I presented, again, the error for this person over the phone, and the response was “OK, we export the WEP from the import lib? So what?”.
Just out of curiosity, what sort of function /would/ GetProcAddress() be called for with high frequency?
And do you have any idea what made it impractical/impossible for callers to remember the returned pointer(s) themselves? If there aren’t that many of those functions, and it’s only 4 bytes per pointer (GetProcAddress returns a FAR pointer?), then that wouldn’t have eaten too much mem at the time, would it?
For 16-bit, I also can’t think of one. For 32-bit though (even that it works differently), I’d say GetDisk(Free?)SpaceEx is at the top of the list. Oh the shortsightedness of what is/was known as Win32c (the "Chicago", aka Win95, version of the Win32 API).
Adam:
There is no reason a single process wouldn’t hold on to the pointer, once it got it. But remember, 16-bit. All the applications in the system shared a single address space. The DLL was loaded once, at an address that then became unavailable for all other use in all other processes. So the issue wasn’t at all a single process not being able to keep track of what it got – it was the whole system using that single and very small adddress space and needing to share it between all applications (we’re talking pre-386 here).
A base-1 integer? What’s that? 0 for 0, 00 for 1, 000 for 2, etc?
Wait, you said "1-based integer"? Never mind. :)
BTW Mike, was that back when FoxPro was an MS app, or was that before MS bought them?
As far as a function that gets GetProcAddress()ed frequently in 16-bit, I’d say that DefWindowProc might be a good candidate. Programs won’t call GetProcAddress() on it directly, but they will import it, so the system needs to resolve the link every time any program starts.
However, it may be that the programs linked to DefWindowProc by ordinal number, in which case it’s not one of the frequent functions. Hmm…
Mike:
I don’t believe that any version of Windows was written in Pascal. The reason that Pascal calling conventions were used was, IIRC, the Pascal calling convention (callee cleans up after the call) was a few bytes smaller (per call site), and a few clock ticks faster than C calling conventions (caller cleans up after the call).
When you’re trying to shoehorn a system into 640K, every few bytes help.
Of course, C calling conventions allow for varargs.
On the subject of 1-based, so I don’t die wondering: Raymond, was Windows at one time written in/for PASCAL (specifically the API or API-handling code)? I’m thinking of the many clues in the form of calling convention for system functions, the one-based indices and other implementation details that seems to have leaked through. If not, do you know (or can you find out) why many of these design decisions were made at the time? I’d hate to die not knowing this piece of history.
Gabe:
IIRC it was around 1991, so I’m quite confident MS was the owner of FoxPro then. Another clue could be; top score on getting back to me on the issue quickly, but a rock bottom for giving me a "Huh, what you say goes *whoosh* over our head". I see it as just another proof of what Raymond recently wrote in another entry – many are the groups that ship Windows software that break (the) rules of Windows programming, and not all within MS writing software are Windows API gurus.
Still, not exporting the WEP into the import library might perhaps not have been considered a "guru" issue. ;-)
(I btw think MS never fixed this. IIRC I had to fix it manually by introducing, for no other reason than to fix this FoxPro bug, yet another intermediate DLL that had no data whatsoever and only forwarded my calls from my real DLL to the FoxPro DLL).
The WEP problem is easily fixed. You simply load the import library
into a binary editor, find the WEP text and replace it with (say) XXX.
One should never treat third party tools with more respect than
your own code. (There was a time when our company had MSVC6 checked
into SourceSafe to make it easy to deploy all the necessary patches.)
Hello Raymond,
sorry to be off-topic to this post, but could you look into this
and correct this on your tooltip post? I had the same problem (missing
black border most of the time, but with XP) and the suggestion by
Martin Filteau made it go away.
Original posting tooltip: http://blogs.msdn.com/oldnewthing/archive/2006/06/26/647365.aspx
Martin Filteau’s suggestion: http://blogs.msdn.com/oldnewthing/archive/2006/06/27/648493.aspx#649674
Thanks,
Tony
Sorry Mike, but MS didn’t buy FoxPro until the middle 1992, and the Windows version that was released a few months later was already mostly complete by the time MS moved in. It would almost certainly have been a Fox Software engineer you were talking to.
Gabe, right, my bad. I’m fairly sure I was using MSVC1.x for that project, and it seems it was released early 1993 (?), why I’d guesstimate sometime after Q1-Q2 1993.
A threaded linked list.
256KB machine? Did some version of Windows (version 1?) actually work with that?
A table of function pointers.
[And then a new version of the import library is released, you update your copy, and then your program doesn’t work. After two weeks of fighting, you realize, “Oh right, I have to patch the import library first.” That’s two weeks of your life you can’t get back. -Raymond]
Unlikely in this case, since the error is 100% certain to recur the very first time you link after updating the 3rd party product, and the error message will say something like “You’ve got two WEPs – one in your code and one in this library over here.”. I think two minutes is a more reasonable estimate of the loss of life in this case.
Preserving the spirit while accommodating separate address spaces and new processors.
In case you’ve missed it and are interested, Raymond Chen has started a series on how DLL imports/exports…
For reference.
I found this list of article on Raymond's blog . Raymond's blog is one of the more interesting