DLL Forwarders in Windows CE

I haven't seen this information consolidated online, so here it is:

 

A DLL Forwarder is used if you want to export an entry point from one DLL (or, more likely, for historical purposes you've already exported it from one dll), but you want to actually implement it in a different DLL.

 

For example, suppose you want to implement a function Foo() in a DLL I'll call impl.dll, but for whatever reason you need to export it from a DLL I'll call export.dll. Of course, one simple solution is to add code to export.dll to explicitly call into impl.dll. However, there are a couple of issues with this:

 

- There's an additional function call/return overhead.

- If the functions exported from the two DLLs have the same name, you might have some trouble convincing the linker to call the the Foo() in impl.dll from the Foo() in export.dll.

 

A forwarder solves this problem by directly interacting with the loader to forward exports from one DLL to another DLL without actually adding anything to the code path.

 

Forwarders are implemented in the .def file of the DLL you're forwarding through, and have the following syntax:

 

EXPORTS

    <FuncName>=<ForwardedDll>.<ForwardedFuncName>|#<ForwardedOrdinal> @<FuncOrdinal> NONAME

Where:

 

<FuncName> is the name of the function as exported (e.g. from export.dll).

@<FuncOrdinal> is the ordinal of the function as exported (e.g. from export.dll). Note the use of the '@' symbol. Optional.

 

<ForwardedDll> is the name of the DLL into which you're forwarding the call (e.g. into impl.dll). Optional; if not specified, the forwarded function is assumed to be in this dll.

<ForwardedFuncName> is the name of the function in the ForwardedDll.

#<ForwardedOrdinal> is the ordinal of the function in the ForwardedDll. Note the use of the '#' symbol.

 

NONAME is the keyword that causes the linker to throw away the name of the function you're exporting so that it can only be referenced by its ordinal. This saves some space in the DLL and forces all callers to use the ordinal to link or GetProcAddress on the function. Optional.

Note: You need to specify the forwarded function name or ordinal (not both). You'll get slightly better load perf and smaller code size by specifying the ordinal. The ordinal is also necessary if the function in the DLL you're forwarding to is specified as NONAME in its def file.

Using our example, to forward Foo() from export.dll to impl.dll, the export.def file would have a line that looks like this:

 

EXPORTS 

    Foo = impl.Foo

 

If you run "dumpbin /exports" against export.dll, you should see an entry for showing the forward that looks something like this:

 

    ordinal hint RVA name
nnnn mm Foo (forwarded to Impl.Foo)

 

Tricky detail 1: When linking export.dll, the linker needs to figure out that the function you're exporting is a "C" style function, which it would normally do by looking at the function signature in the code implementation of the function. I've found that the easiest way to work around this is to implement a code stub that is linked into export.dll so it can get the right name in the export.lib file (e.g. decorated/undecorated). The actual code is thrown away at link time, so it doesn't contribute to the size of the Dll. For example, one would need to implement the following and link it into export.dll to make the linker happy.

#define FORWARD(fn) extern "C" void fn(){}

FORWARD(Foo)

Tricky detail 2: I've run into one issue with ROMIMAGE: if export.dll is in the modules section of the .bib file, but impl.dll is in the files section, ROMIMAGE will generate an error when it tries to resolve the import at makeimg time. This will likely be fixed in the future, but for now it's just something that needs to be avoided.

 

Tricky detail 3: Forwarders are used at load time to forward references between DLLs. The linker will not use forwarders at link time to resolve links within your DLL. Therefore, if the DLL you're forwarding from includes code with references to the function you're forwarding, the link phase of you DLL will fail with an unresolved extern error.

 

Tricky example: If you want to export it a function at ordinal 1000 (and not export it by name), and want to forward it to DLL which exports it at ordinal 2000 (without a name), the syntax in your .def file is:

 

EXPORTS

    SHIM_ORD_1000=IMPL.@2000 @1000 NONAME

 

The SHIM_ORD_1000 is an arbitrary name; it's only there to satisfy the def file syntax rules. It doesn't really matter what you call it as long as it doesn't alias to anther function exported in your .def. If you then run dumpbin /exports on the resulting dll, you'll see something like:

 

    1000 [NONAME] (forwarded to IMPL.@2000)