FixDS - a bit of Windows history

FixDS was a utility I wrote in the early days of real mode Windows
that radically simplified some aspects of Windows programming.

Michael Geary

-----------------------------------------------------------------------------
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
        jmp     

That 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

-----------------------------------------------------------------------------