I have the following situation:
base\C_sharp.exe -> base\sub\CPPCLI.dll -> base\sub\CPPnative.dll
where I have a C# executable, loading a C++/CLI (.NET) dll via System.Reflection.Assembly.LoadFrom
that is statically linked to CPPnative.dll
.
Now, the Standard Dll Search Order on Windows is:
Obviously, if I call
System.Reflection.Assembly.LoadFrom("D:\....\base\sub\cppcli.dll");
the .NET assembly will be found, but what then happens is that the transitive load of the cppnative.dll
succeeds!
1618:3088 @ 1557218328 - LdrLoadDll - ENTER: DLL name: D:\....\base\sub\cppcli.dll DLL path: D:\....\base\sub\;;C:\Windows\system32;...
...
1618:3088 @ 1557218328 - LdrpHandleOneOldFormatImportDescriptor - INFO: DLL "D:\....\base\sub\cppcli.dll" imports "cppnative.dll"
...
1618:3088 @ 1557218328 - LdrpLoadImportModule - ENTER: DLL name: cppnative.dll DLL path: D:\....\base\sub;;C:\Windows\system32;
TL;DR: How does the native NT loader suddenly start looking in the sub
directory?
Normal LoadLibrary
behaviour would suggest that the executable directory is searched first.
There is the possibility that the .NET framework is using LOAD_WITH_ALTERED_SEARCH_PATH
internally when eventually doing the LoadLibrary
call to the C++/CLI assembly.
At least, I can clearly see in my call stacks that the framework is using LoadLibraryEx
internally to load the mixed mode cppcli.dll into the process.
First:
> cppnative1.dll!DllMain(HINSTANCE__ * hModule, unsigned long ul_reason_for_call, void * lpReserved) Line 13 C++ Symbols loaded.
cppnative1.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 199 C++ Symbols loaded.
ntdll.dll!LdrpRunInitializeRoutines() Unknown Symbols loaded.
ntdll.dll!LdrpLoadDll() Unknown Symbols loaded.
ntdll.dll!LdrLoadDll() Unknown Symbols loaded.
KernelBase.dll!LoadLibraryExW() Unknown Symbols loaded.
[Managed to Native Transition] Annotated Frame
mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, System.IntPtr pPrivHostBinder, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks) Unknown No symbols loaded.
Then:
> cppcli.dll!DllMain(HINSTANCE__ * hModule, unsigned long ul_reason_for_call, void * lpReserved) Line 17 C++ Symbols loaded.
cppcli.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 199 C++ Symbols loaded.
mscoreei.dll!000007fef3ec0e64() Unknown No symbols loaded.
mscoree.dll!000007fef5fd3281() Unknown No symbols loaded.
mscoree.dll!000007fef5fd32cf() Unknown No symbols loaded.
ntdll.dll!LdrpRunInitializeRoutines() Unknown Symbols loaded.
ntdll.dll!LdrpLoadDll() Unknown Symbols loaded.
ntdll.dll!LdrLoadDll() Unknown Symbols loaded.
KernelBase.dll!LoadLibraryExW() Unknown Symbols loaded.
[Managed to Native Transition] Annotated Frame
mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, System.IntPtr pPrivHostBinder, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks) Unknown No symbols loaded.
I can also observe, that the executable path is no longer searched when resolving the transitive (native) module dependencies during this LoadLibraryEx
call.
That is, all transitive (native) dependencies MUST reside in the directory of the LoadFrom
call, not in the application directory.
(Incidentally the CWD "." is searched, so that can add to the confusion as well.)