Holy cow, I wrote a book!
As I noted earlier, when you create a forwarder entry in an export table, the corresponding target DLL is not loaded until somebody links to the forwarder entry. It looks like some people misread this statement to suggest some sort of delay-loading so I'm going to state it again with an example in mind in the hopes of clearing up any confusion (and risking creating more confusion than I clear up).
Suppose that you have a DLL called A.DLL that has a forwarder entry to B.DLL:
A.DLL
B.DLL
; A.DEF EXPORTS Dial = B.Call Pour Refill
This specifies that if somebody wants the function Dial from A.DLL, they will actually get the function Call from B.DLL. The delay-load-like behavior is that B.DLL is not loaded until somebody asks for the Dial function.
Dial
Call
I will use the notation DLLNAME!FunctionName to mean "the function FunctionName from the DLL named DLLNAME." This is the notation used by the ntsd debugger.
DLLNAME!FunctionName
FunctionName
DLLNAME
ntsd
Consider this program:
POURME.EXE Imports from A.DLL Pour Refill
The POURME program will not result in B.DLL being loaded since it never links to A!Dial. Of course A.DLL will get loaded because the program wants the functions A!Pour and A!Refill. This is the "delay-load-like behavior" I mentioned in the original entry: If you don't call a function that forwards to B.DLL, then B.DLL won't get loaded.
POURME
A!Dial
A!Pour
A!Refill
Alternative, you could have used this method to do the forwarding:
; A2.DEF EXPORTS Dial Pour Refill /* a2.c */ // Forward Dial to B!Call HRESULT Dial() { return Call(); }
This pseudo-forwarder is not a forwarder in the linker sense; it is an attempt to emulate linker forwarding in code. Now let's look at the corresponding alternate POURME program:
POURME2.EXE Imports from A2.DLL Pour Refill
Even though POURME2 doesn't call A2!Dial, the file B.DLL will nevertheless be loaded when POURME2 runs because A2.DLL contains a dependency on B.DLL in its own import table:
POURME2
A2!Dial
A2.DLL
; dump of headers of A2.DLL Imports from B.DLL Call
Loading A2.DLL will cause B.DLL to be loaded since B.DLL is listed as one of A2's dependencies.
A2
Commenter bruteforce got off on the wrong foot by calling the above mechanism a delay-loading feature.
I tried to take advantage of the delay-loading feature described above for the forwarder DLLs...
The mechanism is not delay-loading and I never said that it was. The quasi-delay-load behavior is that a forwarded-to DLL is not loaded until somebody links to it. The term delay-loading typically is used to apply to delaying the load of a module until a function in that module is called. But import resolution happens at load time, not run time.
Commenter bruteforce tried to create a forwarder to a nonexistent function, and then tried to link to the forwarder DLL. As we saw above, this triggers an attempt to resolve the forward by loading the forwarded-to DLL and looking for the function. If this fails, then the original import request is declared to have failed. This all happens as part of the import resolution process. And as we saw many years ago, Win32 fails a module load if an import cannot be resolved. Since the forwarder cannot be resolved, the load fails. Import forwarding functionality is completely unsuitable for functions whose presence you wish to detect and respond to at runtime. As with all imports, an import failure is considered a fatal error. If you want delay-loading, then you need to do delay-loading. Forwarding is not delay-loading.