I have a case when the LoadLibrary()
function and the delay-load DLL use different DLL search path. Specifically:
LoadLibrary("DLL2.dll"); // succeeds
while:
FunctionFromDLL1(); // fails to delay-import a function from DLL2
Running some diagnostic reveals both LoadLibrary()
and delay-import call the LdrpComputeLazyDllPath()
function to calculate the DLL search path:
LdrpComputeLazyDllPath()
calculates the correct DLL search path when called from the LoadLibrary()
function - resulting in DLL loaded correctly;LdrpComputeLazyDllPath()
calculates the incorrect DLL search path when called from the delay-import code (ResolveDelayLoadedAPI()
) - resulted in 127 (0x7F) error (which is ERROR_PROC_NOT_FOUND
) being thrown when calling the delay-imported function.Why is that?
It is unforeseen consequences of the LOAD_LIBRARY_SEARCH_USER_DIRS
, LOAD_LIBRARY_SEARCH_APPLICATION_DIR
, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
flags. These flags are applied not only to the loaded DLL itself, but also to all delay-loaded DLLs - even if these DLLs are loaded outside of the LoadLibraryEx()
call.
In other words, if some code loads the DLL1.dll
via the LoadLibraryEx()
function with one of the above flags, the system will remember these flags and will use them later to resolve delay loads from the DLL1.dll
.
For example, if your application have plugins that are located in their own folders:
App\Plugins\Plugin1\Plugin1.dll // statically linked to DLL1
App\Plugins\Plugin1\DLL1.dll // delay-imports from DLL2 - a system DLL
You may load the Plugin1.dll
via LoadLibraryEx()
with the LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
flag, so the Plugin1.dll
could find/load the statically linked DLL1.dll
. The simple LoadLibrary()
would not work here, as the DLL's folder is not in default DLL search path.
Now, even if your app loads the DLL1.dll
file via the LoadLibrary()
function - the system will still remember that the DLL1.dll
was loaded with custom search paths.
Next, if you call a function from the DLL1.dll
that delay-imports a function from, say, some system DLL (DLL2.dll
), the system will use the saved custom DLL search path to find the DLL2.dll
. And since the LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
flag overrides DLL search path, the System32
folder will not be in custom DLL search path. This will cause the DLL2.dll
to be searched only in the App\Plugins\Plugin1\
folder - which will fail.
I don't think this behaviour is documented anywhere, but I may be wrong. I looked for it, but can't find any mentions. Perhaps, I looked in wrong places or used bad search terms.
People on the internet use workarounds like: loading the DLL2.dll
explicitly via LoadLibrary()
before calling functions from the DLL1.dll
. Loading the DLL explicitly will ensure it is loaded by the time delay-import is executed. Since the DLL2.dll
will be already loaded, there is no need to search for it, so delay-import will succeed. While this will work, but it is a giant hack, as it requires you to know implementation details of the DLL1.dll
- which may be not under your control.
I don't think there is a way to tell the system to use some specific search paths for delay-loads. So the only/real solution here is to load the Plugin1.dll
with something like LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
. The LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
flag will allow the Plugin1.dll
to pick up the DLL1.dll
from its folder, while the LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
will include System32
folder - which will allow delay-load to succeed later.
Please note LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
does not include %PATH%
. So if your DLL delay-imports from DLL on %PATH%
- this will not work.