FixDS was a utility I wrote in the early days of real mode Windows
that radically simplified some aspects of Windows programming.
----------------------------------------------------------------------------- FIXDS Version 2.0 by Michael Geary 4/18/89 ----------------------------------------------------------------------------- Summary: Eliminates need for EXPORTS and MakeProcInstance() in Windows applications. Prevents bugs caused by omitting EXPORTS or MakeProcInstance(). Allows Windows applications to export functions that will be called directly from a dynamic link library. ----------------------------------------------------------------------------- Note to Borland C/C++ and Microsoft C/C++ 7.0 users: Instead of FIXDS, you can now use compiler switches to accomplish the same thing. Both Borland and Microsoft have "seen the light" and included FIXDS's method into their compilers. For Borland C/C++, use the -WS compiler switch. For Microsoft C/C++ 7.0, use -GA -GEs -GEf. For more efficient code, you can use -GA -GEs (no -GEf), but you will need to explicitly use __export on all your callback functions. In either case, you can get rid of the MakeProcInstance calls. Also, Windows 3.1 does much the same as FIXDS. Before calling any window function or other callback, it loads the correct value into DS. However, it is best not to rely on this behavior for two reasons: (1) 3.0 compatibility, and (2) some applications that subclass other applications' windows do *not* load the correct value into DS. So use FIXDS or one of the compiler switches mentioned above. Even though FIXDS is now somewhat obsolete, the documentation below may help clarify what goes on when callback functions are called. ----------------------------------------------------------------------------- Have you ever forgotten to EXPORT a window function? How about good old MakeProcInstance() - ever had a weirdo crash because you forgot this one? Worse yet, how about the time you tried to directly call a function you had EXPORTed? If you're like me, these have probably happened more times than you'd like to admit. With FIXDS, you will never have to use EXPORTS or MakeProcInstance() in a Windows application again! All you have to do is run FIXDS on your .EXE file. For example, in your MAKE file: foo.exe: foo.obj ... link @foo.lnk fixds foo.exe ... FIXDS can be run before or after RC; it doesn't matter. Now you can get rid of the EXPORTS and MakeProcInstance() nonsense you've been dealing with. You don't have to EXPORT window functions or any other callback functions. You don't have to do a MakeProcInstance() on any function, ever. And, it's perfectly safe to directly call any callback functions you may have. You can take out the entire EXPORTS section from your application's .DEF file. You can take out every MakeProcInstance() and FreeProcInstance() call. The places you used the return value from MakeProcInstance() (such as a DialogBox() call), you can just use the actual function name instead. Your code will even work if you leave in some MakeProcInstance's or EXPORTs. Once you start using FIXDS, they just become irrelevant - your code will work with or without them. FIXDS also gives you one useful capability that isn't ordinarily available. Your Windows application can export functions which will be directly called from a dynamic link library. This can be extremely handy. Some Windows applications can be extended by loading and calling DLL's at runtime. (SQLWindows and Excel are examples of this.) Normally, these DLL's can not make any calls to functions in the application. That's a bummer, since you probably have a slew of utility functions in the application that a custom DLL might put to good use. With normal Windows code, you can't just make normal C calls to these functions. You *could* do MakeProcInstance() on all of them and make a table of the addresses, and then pass this table to the DLL so it could make indirect calls to the application functions, but with FIXDS there is an easier way. Just list in the EXPORTS section of your application's .DEF file the functions you wish to make available to the DLL's. (This is the only case where you need an EXPORTS section, and only the functions you want available to DLL's should be listed.) Then run IMPLIB on this .DEF file and presto - you have a .LIB that you can link with your DLL's. The DLL's can then make direct calls to these functions, and the references will be fixed up at runtime just as if the called functions were in some other DLL. Now, you've probably got three questions: "What's the catch?" "How does this thing work, anyway?" "If it does work, why in the world has Microsoft been putting us through all the EXPORTS and MakeProcInstance() grief all this time?" Well, I can answer the first two questions. Darned if I know the answer to the third, though! First, what's the catch? Not much. FIXDS will work with any Windows application that has its own stack. (I read once that in theory it's possible to build an application that doesn't have its own stack, but I don't know if anyone has ever tried that. If your application doesn't have its own stack, FIXDS won't work with it.) FIXDS is for applications only, not dynamic link libraries. It depends on the fact that SS == DS, which is true only in application code. There's no other catch I can think of. FIXDS is completely compatible with every version of Windows, and with every compiler that generates the standard Windows function prolog. Now for the second question, how does this pup work? I'll give a brief explanation here; for more information on the same topic, see Chapter 8 of Charles Petzold's _Programming Windows_. The basic problem that EXPORTS and MakeProcInstance() try to solve is getting the proper value into the DS register. Each FAR function in a Windows application begins with this prolog: push ds ; May have "mov ax,ds" instead of these pop ax ; two instructions. Does the same thing. nop inc bp push bp mov bp, sp push ds mov ds, ax Now, aside for fooling around with BP, the net effect of this code is to put the same value into DS that it already had (and to save a copy of DS on the stack, which gets popped back off later). For a normal FAR function that you call from your own code, that's a lot of busywork, but doesn't hurt anything. After all, you already had the right DS value since it was your application that called this function. The busywork starts to come into play when you deal with functions that are called from outside your application, such as window functions and other callback functions. When Windows calls these functions, there is a different value in DS, so something has to change. That's what the EXPORTS and MakeProcInstance() do for you. Let's take the case of a dialog function. The dialog manager inside Windows (in USER.EXE) simply makes a direct FAR call to the address you give as your dialog function, and DS at that time points to USER's data segment, so how does DS get the right value for your application? Well, when you EXPORT your dialog function, Windows NOP's out the first two bytes of the prolog, so it looks like this: nop nop nop inc bp push bp mov bp, sp push ds mov ds, ax Now you can see that this prolog will end up moving the value from the AX register into DS (after saving the old DS value). That's why you can't just call this function directly from C code - it would take whatever happened to be laying around in AX and put it into DS; not a good idea. However, when you call MakeProcInstance(), Windows creates an "instance thunk", which looks like this: mov ax, XXXX jmpThat XXXX is the key - Windows keeps track of all instance thunks, and whenever your data segment moves in memory, it patches the XXXX to reflect the new address of your data segment. So, that's why you have to call MakeProcInstance() and then pass that address - the address of the instance thunk - into CreateDialog() or DialogBox(). The dialog manager will then call your instance thunk, which puts the right value into AX, which the modified function prolog will then copy over to DS. (I'm leaving out all the details of "reload thunks" - they aren't relevant to this discussion.) For a window function, essentially the same thing goes on, except that you only have to do the EXPORTS and not the MakeProcInstance(). That's because window functions are only called via SendMessage() or DispatchMessage(), and those functions essentially do the equivalent of the instance thunk internally - they put the proper value into AX before calling your window function. Well, that's all a lot of work just to get the right value into the DS register, but after all, Windows has to deal with the fact that your data segment may move around. Worse yet, it has to handle the fact that there may be multiple instances of your application. If it weren't for that, Windows could just patch your function prolog to put in the right DS value directly, like it does for dynamic link libraries. But, the function code is shared among all instances, and they all have different data segment addresses. That's why the MakeProcInstance() is necessary, so each instance gets its own unique little header that Windows can patch. So, all that work is necessary, right? Wrong. Despite all the work that MakeProcInstance() and EXPORTS go through to put the correct value into the DS register, THAT VALUE WAS JUST SITTING IN ANOTHER REGISTER WAITING TO BE USED. Which register? SS. Remember that in a Windows application, SS == DS. Let me repeat that, SS == DS. Now, does any of the function prolog code or the instance thunk code do anything to SS? Nope. Whenever any of your application code is running, and whenever Windows calls one of your window functions or callback functions, SS contains your data segment address. The prolog code and instance thunks don't have anything to do with this; Windows' task manager puts the right value into SS before it lets your task run. If it didn't, the SS == DS assumption would be violated. You can probably guess by now what FIXDS does. It patches all FAR function prologs to look like this: mov ax, ss nop inc bp push bp mov bp, sp push ds mov ds, ax Now this prolog works for *all* FAR functions. Since SS, by definition, always has the correct data segment value, this prolog will put the correct value into DS. It doesn't matter whether it's a function you call directly or whether it is called back from Windows. This also explains why FIXDS allows you to call application functions directly from a DLL. Without FIXDS, you need the EXPORTS and MakeProcInstance() to get the proper value into DS, and the result of the MakeProcInstance() call isn't known till runtime, so the best you can do is an indirect call through the instance thunk. With FIXDS, however, the actual function entry point is perfectly usable regardless of where it is called from. The folks at Microsoft didn't believe me when I told them about the technique that FIXDS uses. After studying it a bit, they realized that of course it works. If it didn't, every application that's been compiled with the SS == DS assumption would have failed. Perhaps there's hope that a future version of Windows or the C compiler will have something like this built in. In the meantime, use FIXDS in your Windows applications and you'll never have to worry about EXPORTS and MakeProcInstance() again. (That was written several years ago when I first wrote FIXDS. Now, as I mentioned above, the FIXDS technique *is* incorporated into the Borland and Microsoft C compilers, as well as in Windows 3.1.) Michael Geary P.O. Box 1479 Los Gatos, CA 95031 AMIX: MGEARY BIX: GEARY CompuServe: 76146,42 -----------------------------------------------------------------------------