I'm writing a cross-platform .NET library that uses some unmanaged code. In the static constructor of my class, the platform is detected and the appropriate unmanaged library is extracted from an embedded resource and saved to a temp directory, similar to the code given in another stackoverflow answer.
So that the library can be found when it isn't in the PATH, I explicitly load it after it is saved to the temp file. On windows, this works fine with LoadLibrary
from kernel32.dll. I'm trying to do the same with dlopen
on Linux, but I get a DllNotFoundException
when it comes to loading the P/Invoke methods later on.
I have verified that the library "libindexfile.so" is successfully saved to the temp directory and that the call to dlopen
succeeds. I delved into the mono source to try figure out what is going on, and I think it might boil down to whether or not a subsequent call to dlopen
will just reuse a previously loaded library. (Of course assuming that my naïve swoop through the mono source drew the correct conclusions).
Here is the shape of what I'm trying to do:
// actual function that we're going to p/invoke to
[DllImport("indexfile")]
private static extern IntPtr openIndex(string pathname);
const int RTLD_NOW = 2; // for dlopen's flags
const int RTLD_GLOBAL = 8;
// its okay to have imports for the wrong platforms here
// because nothing will complain until I try to use the
// function
[DllImport("libdl.so")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string filename);
static IndexFile()
{
string libName = "";
if (IsLinux)
libName += "libindexfile.so";
else
libName += "indexfile.dll";
// [snip] -- save embedded resource to temp dir
IntPtr handle = IntPtr.Zero;
if (IsLinux)
handle = dlopen(libPath, RTLD_NOW|RTLD_GLOBAL);
else
handle = LoadLibrary(libPath);
if (handle == IntPtr.Zero)
throw new InvalidOperationException("Couldn't load the unmanaged library");
}
public IndexFile(String path)
{
// P/Invoke to the unmanaged function
// currently on Linux this throws a DllNotFoundException
// works on Windows
IntPtr ptr = openIndex(path);
}
Update:
It would appear that subsequent calls to LoadLibrary
on windows look to see if a dll of the same name has already been loaded, and then uses that path. For example, in the following code, both calls to LoadLibrary
will return a valid handle:
int _tmain(int argc, _TCHAR* argv[])
{
LPCTSTR libpath = L"D:\\some\\path\\to\\library.dll";
HMODULE handle1 = LoadLibrary(libpath);
printf("Handle: %x\n", handle1);
HMODULE handle2 = LoadLibrary(L"library.dll");
printf("Handle: %x\n", handle2);
return 0;
}
If the same is attempted with dlopen
on Linux, the second call will fail, as it doesn't assume that a library with the same name will be at the same path. Is there any way round this?
After much searching and head-scratching, I've discovered a solution. Full control can be exercised over the P/Invoke process by using dynamic P/Invoke to tell the runtime exactly where to find the code.
Edit:
You need these imports:
[DllImport("kernel32.dll")]
protected static extern IntPtr LoadLibrary(string filename);
[DllImport("kernel32.dll")]
protected static extern IntPtr GetProcAddress(IntPtr hModule, string procname);
The unmanaged library should be loaded by calling LoadLibrary
:
IntPtr moduleHandle = LoadLibrary("path/to/library.dll");
Get a pointer to a function in the dll by calling GetProcAddress
:
IntPtr ptr = GetProcAddress(moduleHandle, methodName);
Cast this ptr
to a delegate of type TDelegate
:
TDelegate func = Marshal.GetDelegateForFunctionPointer(
ptr, typeof(TDelegate)) as TDelegate;
Use these imports:
[DllImport("libdl.so")]
protected static extern IntPtr dlopen(string filename, int flags);
[DllImport("libdl.so")]
protected static extern IntPtr dlsym(IntPtr handle, string symbol);
const int RTLD_NOW = 2; // for dlopen's flags
Load the library:
IntPtr moduleHandle = dlopen(modulePath, RTLD_NOW);
Get the function pointer:
IntPtr ptr = dlsym(moduleHandle, methodName);
Cast it to a delegate as before:
TDelegate func = Marshal.GetDelegateForFunctionPointer(
ptr, typeof(TDelegate)) as TDelegate;
For a helper library that I wrote, see my GitHub.