How does DllImport in ASP.Net look for the DLL?
Is it documented somewhere how ASP.Net sets up search paths for native DLLs? I need to be able to replicate the logic in my own code.
For more background: I'm maintaining a managed library (say Managed.DLL) that wraps a native library (say Native.DLL) that in turn uses another native DLL (say Driver.DLL). So far Managed.DLL has been importing functions from Native.DLL using .Net's DllImport attribute, but now I have to change this to hand-coded calls to LoadLibrary and GetProcAddress to get more control; in particular, I need to be able to call FreeLibrary to unload Native.DLL and I can't do this when Native.DLL has been loaded via DllImport.
And here comes the problem: While just DllImport("Native.DLL") is sufficient to locate both Native.DLL and Driver.DLL, calling LoadLibrary("Native.DLL") fails with ERROR_FILE_NOT_FOUND when Managed.DLL is used in an ASP.Net application, because the directory containing Managed.DLL is not on the search path for native code DLLs.
My first thought was to use Assembly.GetExecutingAssembly().Location and then issue the LoadLibrary call with a full path, but then Native.DLL fails to find Driver.DLL, because the directory containing them both is still not on the search path.
I could work around this by using the Assembly.GetExecutingAssembly().Location value to set the native DLL search path with SetDllDirectory, but this has two major drawbacks:
1) SetDllDirectory changes global WinAPI settings and could interfere with other code within the same ASP.Net worker process that also uses native code DLLs; I have also verified that using DllImport attribute does not mess with this setting, so changing it now could indeed break something that was working before.
2) It would still not work for debugging ASP.Net applications from within Visual Studio, because VS copies the managed resources into a temporary directory, but leaves the native DLLs in the project build directory, so they end up in different locations in the debugging session (and the temporary directory is wiped for every debugging session, so copying the native DLLs into it manually does not work either; I had to copy the native DLLs into IIS's directory for the debugging session to find them and this is clearly not acceptable solution).
I really would like to do the compatible thing here, but so far haven't been able to find out what this is and after couple of days of fruitless searches any pointers would be greatly appreciated.
To answer my own question:
1) The keyword I was missing regarding the copying of Managed.DLL was "shadow copying" (James Schubert explains it much better than any official Microsoft documentation I've seen) and the trick is to use Assembly.CodeBase
instead of Assembly.Location
, because the former gives the the original location of Managed.DLL and the latter the location of the shadow copy (John Sibly and Sneal shared nice code snippets to extract the directory name from the URI in Assembly.CodeBase
).
2) The way to make dependencies of the native DLLs available is to explicitly load them using LoadLibrary
before they are needed (and since this increments their reference counts, also release them with FreeLibrary
when done).
So, the loading sequence is
string dir = Assembly.GetExecutingAssembly().CodeBase;
dir = new Uri(dir).LocalPath;
dir = Path.GetDirectoryName(dir);
IntPtr driver = LoadLibrary(dir + Path.DirectorySeparatorChar + "Driver.DLL");
IntPtr managed = LoadLibrary(dir + Path.DirectorySeparatorChar + "Managed.DLL");
and the unloading sequence
FreeLibrary(managed);
FreeLibrary(driver);
(note also the order of LoadLibrary
and FreeLibrary
calls).