Saturday, February 21, 2009

Delay Loading using Visual Studio - Pitfall

This is a small post on delay loading using Visual Studio. As everyone knows that we can dynamically load a dll in a process. Let us say binary B1 depends on binary B2, one way of doing this, is to put a hard dependency on the B2 in the project settings of B1. This actually makes B2 load into the process where B1 is already loaded (or if B1 is the exe then B1 is the process itself) while starting the process. You can actually view this by starting your process in the debugger (Windbg) using gflags set with "Show loader snaps" checked. It shows all the binaries that are loaded when the process comes up.

The second option is to call LoadLibrary() in the code of B1 to load B2 and then call GetProcAddress(). This is the best delay loading method. As per my experience it wards off all non-execution errors in the field (once your solution is installed in the customer environments). But this is tedious if there is a large number of functions to be imported from B2 into B1 and used. You will end up writing function pointer definitions in B1 for every function that you need to use from B2.

The third option is a middle way of both the approaches. It is to delayload B2 in B1 using the project settings "Delay Loaded Dlls" in the project settings of B1 (under Linker options). But there is a specific way of doing this.

You will have to put B2.lib in the "Additional Dependencies" section as if you are hard depending(1st case) and in addition you will have to put B2.dll in the "Delay Loaded Dlls" entry in the same tab in the project settings to make it delay loaded. Once you do this, B2 will be loaded only when any code path using B2's functions or classes will be hit in B1. Until then B2 will not be loaded into the process space of B1.

Pitfall:
Here most of the developers, when they have multiple delay loaded dlls, let us say B2 and B3 to be delay loaded in B1 type the entries as 'B2.dll B3.dll' (ofcouse without the single quotes). They do this because they usually copy/paste or go with the notion of the same delimiter as in "Additional Dependencies" section. But doing so will actually tell the compiler that there is a binary called "B2.dll B3.dll" to be delay-loaded into the process of B1. But since the same entry is not found in "Additional Dependencies" section it ignores this directives and happily links it. Now the consequence is that B2.dll and B3.dll are linked as hard dependency into B1. And you wonder why they are getting loaded like this (let us say because of package differences B2.dll and B3.dll do not get installed always along with B1.dll and so result in load failures of B1.dll when you think everything is right).

The delimiter in the delay-loaded section is a semi-colon ";". If we put the entry as 'B2.dll;B3.dll' in the "Delay Loaded Dlls" section then everything goes fine as expected.

Second pitfall is that if you have a delay loaded dll B2 in B1 and in course of time the code in B1.dll that referes to B2.dll is yanked out (changing code because of a any reason), then the B2.dll's delay load directive you have put will be ignored during linking. Consequence is that your binary B1.dll now hard depends on B2.dll!

No comments:

Post a Comment