P /调用Mono上动态加载的库

我正在编写一个使用一些非托管代码的跨平台.NET库。 在我的类的静态构造函数中,检测平台并从嵌入式资源中提取适当的非托管库并保存到临时目录,类似于另一个stackoverflow应答中给出的代码。

因此,当它不在PATH中时可以找到它,我在将它保存到临时文件后显式加载它。 在Windows上,这适用于kernel32.dll的LoadLibrary 。 我正在尝试使用Linux上的dlopen进行相同的操作,但是稍后在加载P / Invoke方法时会遇到DllNotFoundException

我已经validation库“libindexfile.so”已成功保存到临时目录,并且对dlopen的调用成功。 我深入研究了单声道源代码 ,试图弄清楚发生了什么,我认为可能归结为后续调用dlopen是否会重用以前加载的库。 (当然,假设我天真地通过单声道来源得出了正确的结论)。

这是我正在尝试做的形状:

 // 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); } 

更新:

看起来在Windows上对LoadLibrary后续调用看起来是否已经加载了同名的dll,然后使用该路径。 例如,在以下代码中,对LoadLibrary两次调用都将返回一个有效的句柄:

 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; } 

如果在Linux上使用dlopen尝试相同,则第二次调用将失败,因为它不会假定具有相同名称的库将位于同一路径中。 这有什么办法吗?

经过多次搜索和搔痒,我发现了一个解决方案。 通过使用动态P / Invoke告诉运行时确切地找到代码的位置,可以对P / Invoke过程进行完全控制。


编辑:

Windows解决方案

你需要这些import:

 [DllImport("kernel32.dll")] protected static extern IntPtr LoadLibrary(string filename); [DllImport("kernel32.dll")] protected static extern IntPtr GetProcAddress(IntPtr hModule, string procname); 

应该通过调用LoadLibrary来加载非托管库:

 IntPtr moduleHandle = LoadLibrary("path/to/library.dll"); 

通过调用GetProcAddress获取指向dll中函数的指针:

 IntPtr ptr = GetProcAddress(moduleHandle, methodName); 

将此ptr转换为TDelegate类型的TDelegate

 TDelegate func = Marshal.GetDelegateForFunctionPointer( ptr, typeof(TDelegate)) as TDelegate; 

Linux解决方案

使用这些导入:

 [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 

加载库:

 IntPtr moduleHandle = dlopen(modulePath, RTLD_NOW); 

获取函数指针:

 IntPtr ptr = dlsym(moduleHandle, methodName); 

像以前一样把它投射到一个代表:

 TDelegate func = Marshal.GetDelegateForFunctionPointer( ptr, typeof(TDelegate)) as TDelegate; 

对于我编写的帮助程序库,请参阅我的GitHub 。

尝试从终端运行它:

 export MONO_LOG_LEVEL=debug export MONO_LOG_MASK=dll mono --debug yourapp.exe 

现在每个库查找都将打印到终端,因此您将能够找出出错的地方。

不确定为什么你认为这与mono有关,因为你所遇到的问题不是单声道的动态加载设施。

如果您更新的示例有效,那就意味着Windows上的LoadLibrary()与Linux上的dlopen()具有不同的语义:因此您要么必须接受差异,要么实现自己的抽象来处理目录问题(我的预感是它不是保留的目录,但是Windows只是查看是否已经加载了具有相同名称的库并且它重用了它。

我需要将提取的本机库加载到临时位置,我几乎找到了解决方案。 我检查了Mono的源代码并找到了一种方法:

 [DllImport("__Internal", CharSet = CharSet.Ansi)] private static extern void mono_dllmap_insert(IntPtr assembly, string dll, string func, string tdll, string tfunc); // and then somewhere: mono_dllmap_insert(IntPtr.Zero, "somelib", null, "/path/to/libsomelib.so", null); 

这种作品。 问题是,你不能允许Mono愚蠢的JIT编译器在调用mono_dllmap_insert()之前捕获任何引用该库的DllImported方法的味道。

因为如果确实如此,会发生奇怪的事情:

 Mono: DllImport searching in: '/tmp/yc1ja5g7.emu/libsomelib.so' ('/tmp/yc1ja5g7.emu/libsomelib.so'). Mono: Searching for 'someGreatFunc'. Mono: Probing 'someGreatFunc'. Mono: Found as 'someGreatFunc'. Error. ex=System.DllNotFoundException: somelib 

所以现在我正在调用我的原生someGreatFunc() ,Mono能够找到库并加载它(我检查过),它能够找到符号(我已经检查过),但是因为有些人在过去的时候正在做JIT它无法加载该库,它决定抛出DllNotFoundException 。 我猜生成的代码包含一个硬编码的throw语句或其他东西:-O

当您在调用mono_dllmap_insert()之前从同一个库中调用另一个未经过JIT编辑的本机函数时,它将起作用。

所以你可以使用@gordonmleigh添加的手动解决方案,或者你必须告诉Mono库在哪里JIT这些导入。 反思可能有助于那里。