Exported functions that are really forwarders


Last time, we saw how the way Win32 exports functions is pretty much the same as the way 16-bit Windows exports functions, but with a change in emphasis from ordinal-based exports to name-based exports. This change in emphasis is not expressed anywhere in the file format; both 16-bit and 32-bit DLLs can export either by name or by ordinal (or by both), but the designers of Win32 were biased in spirit in favor of name-only exports.

But there is a new type of exported function in Win32, known as a forwarder. A forwarder looks just like a regular exported function, except that the entry in the ordinal export table says, "Oh, I'm not really a function in this DLL. I'm really a function in that DLL over there." For example, if you do a link /dump /exports kernel32.dll, you'll see a line like this:

151   EnterCriticalSection (forwarded to NTDLL.RtlEnterCriticalSection)

This means that if a program links to KERNEL32.EnterCriticalSection, the loader silently redirects it to NTDLL.RtlEnterCriticalSection. Forwarders are a handy way to accommodate functionality moving from one DLL to another. The old DLL can continue to export the function but forward it to the new DLL.

The forwarding trick is actually better than just having a stub function in the old DLL that calls the function in the new DLL, because the stub function creates a dependency between the old DLL and the new one. (After all, the old DLL needs to be linked to the new DLL in order to call it!) With a forwarder, however, the new DLL is not loaded unless somebody actually asks for the forwarded function from the old DLL. As a result, you don't pay for the new DLL until somebody actually wants it.

Okay, we saw that with forwarders, Win32 has diverged from 16-bit Windows, but when it comes to imports, it's a whole new ball game. We'll pick up the story next time.

Comments (15)
  1. Cody says:

    I just have to comment that these are always interesting and informative articles.  I can honestly say, coming just out of college, that I’ve learned more aspects of programming from my partial reading of this one blog than all my years in classes.

  2. Tom says:

    Export forwarding is described in http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/ and a way to do this is described in http://www.microsoft.com/msj/archive/S202B.aspx by using the incantation

       #pragma comment(linker, "/export:SomeFunc=DllWork.SomeFunc")

    The first link says you can do this with .DEF files as well, referring the searcher to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_exports.asp but the page does not really tell you how to write the line.  I would presume it would be something like this:

       EXPORTS

       SomeFunc=DllWork.SomeFunc

  3. Bob says:

    Yeah, the MSDN Magazine link shows an example of the .DEF statement needed.

  4. Tom says:

    @Bob: Guess I didn’t read far enough down the page. :)  

    In my defense, however, the .DEF file documentation in my third link does not describe how to do it.  If one were to read only that documentation and not the MSDN article, one might not never know that export forwarding was even possible.

  5. Daniel Colascione says:

    Thanks for posting these entries; I haven’t done any win32 (or win16, for that matter) programming in a long time. These days, I’m a web developer (ugh) in UNIXland. Just wanted to let you know that your posts are appreciated.

  6. Matt Green says:

    This is pretty neat. The most valuable part is the discussion of the design decisions, and their implications. They are not immediately obvious to people who are not intimately familiar with the code.

  7. @Cody:

    Agreed. I’ve been in the industry almost fifteen years, and I still learn all kinds of interesting things here – mostly not from the "what", since I’m very familiar with how DLLs import and export functionality (every old school Win3.1 programmer is), but from the "why". Most of the time, I don’t have time for "why", because I need to have something working twenty minutes ago… so it’s nice when someone puts the "why" up where I can read it once things calm down.

  8. Cody says:

    Well, the why is much more important than the what.  The what will change, the truth in why will always stay the same.  I think that could have been a less cryptic statement though.

  9. TinFoilBeanie says:

    You know everytime I look at MSDN in Opera, I’m amazed that it works just well enough to be able to read the article text, but not quite perfectly – e.g. the navigation pane doesn’t synchronize properly, and clicking on links reloads the whole page, rather than just the right pane as in IE.

    It’s as if for legal / business reasons there’s a optimal level somewhere between a complete lock out and complete compatibility that the developers strive for – anywhere outside this zone will incur chairs hurled from the Gods, blighted careers and so on.

  10. Tihiy says:

    What’s with this functionality in 9x/Me?

  11. Andy says:

    I’m experiencing a strange error with DLL forwarding. When I try to export a function like the following, the linker doesn’t like it and stop with an error:

    EXPORTS

     EnterCriticalSection=ntdll.RtlEnterCriticalSection

    To resolve the problem, I have to append @4 like this:

    EXPORTS

     EnterCriticalSection=ntdll.RtlEnterCriticalSection@4

    or even

    EXPORTS

     EnterCriticalSection=ntdll.RtlEnterCriticalSection@40000000000000000000

    works. Very strange, isn’t it…

    What’s even more curious is, using #pragma  in .c like the following always *works* regardless of use of @ suffix:

    #pragma comment(linker, "/EXPORT:EnterCriticalSection=ntdll.RtlEnterCriticalSection")

    Could you (or another guru would be appreciated as well :-) give me an brief description on why there’s such an inconsitency between .def file and #pragma?

  12. RoBo says:

    Is there a (recommended) way to forward a function from one DLL to a function of the same name in a DLL of the same name, located in a different directory?

    What I’m thinking of is being able to intercept calls between an app and a ‘standard’ DLL – without having to change the app or the standard DLL – by putting a custom DLL in the same directory as the app. If my custom DLL has the same name as the system DLL it will be loaded in place of the system one and, depending on the functions I’ve exported, the relevant functions can be trapped. Any functions that aren’t intercepted would automatically forward to the underlying system DLL.

    As far as I know, this can be achieved by supplying the full path to the DLL for forwarded functions:

    MyFunction=C:/Windows/System32/MyDll.MyFunction

    But this relies on knowledge of the location of system DLL in advance and can’t be deployed to non-conforming systems.

    Are there any alternative ways to achieve this?

  13. bruteforce says:

    I tried to take advantage of the delay-loading feature described above for the forwarder DLLs but either I am doing something wrong or there is something I am missing.

    What I wanted to do is create a forwarder DLL that will allow a C# application to safely invoke OS-dependent APIs without much of a hussle (C# applications have a serious problem invoking what gets returned from GetProcAddress).

    The idea is that the C# application imports a function named fwdXXX from the forwarder DLL, which forwards the call to the actual XXX function. Then the application calls GetProcAddress("XXX") to check whether the function is present in the current environment, and if it is present then it calls fwdXXX.

    If forwarding DLLs do not create a dependency between the app and the target DLL then the app will have no problem loading because its fwdXXX imports will be in place (the forwarder DLL is present).

    I used InterlockedCompareExchange64 for the test. The forwarder DLL exports fwdInterlockedCompareExchange64 and the test app only calls it if GetProcAddress returns non-NULL for "InterlockedCompareExchange64" in kernel32.DLL.

    However the test app FAILS to load on WinXP (The procedure entry point Kernel32.InterlockedCompareExchange64 could not be located in the dynamic library Forwarder.dll)

    So what’s missing here? Maybe the loader sees that kernel32.dll is already loaded so it starts to fixup all of Forwarder.DLL exports that forward to kernel32?

    Then I also tried to do a second level of forwarding. APP –> FWD1.DLL ==> FWD2.DLL ==> Kernel32.DLL, but to no avail. If FWD2.DLL forwards to a non-existing export in Kernel32.DLL then APP fails to load with the same error.

    So the question is, is there really some sort of delay loading for the target DLLs?

    Regards,

    Dimitris Staikos

  14. If you forward to a function, it still has to exist.

  15. I found this list of article on Raymond's blog . Raymond's blog is one of the more interesting

Comments are closed.