我正在编写一个使用一些非托管代码的跨平台.NET库。在我的类的静态构造函数中,检测平台并从嵌入式资源中提取适当的非托管库并保存到临时目录,类似于another stackoverflow answer中给出的代码。
因此,当它不在PATH中时可以找到它,我在将它保存到临时文件后显式加载它。在Windows上,这适用于kernel32.dll中的LoadLibrary
。我试图在Linux上对dlopen
做同样的事情,但是稍后在加载P / Invoke方法时会得到DllNotFoundException
。
我已经验证库“libindexfile.so”已成功保存到临时目录,并且对dlopen
的调用成功。我深入研究mono source以试图弄清楚发生了什么,我认为可能归结为对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
尝试相同,则第二次调用将失败,因为它不会假定具有相同名称的库将位于同一路径中。这有什么办法吗?
答案 0 :(得分:13)
经过多次搜索和搔痒,我发现了一个解决方案。通过使用dynamic P/Invoke告诉运行时确切地找到代码的位置,可以对P / Invoke过程进行完全控制。
修改强>
您需要这些导入:
[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
:
IntPtr ptr = GetProcAddress(moduleHandle, methodName);
将此ptr
投射到TDelegate
类型的委托:
TDelegate func = Marshal.GetDelegateForFunctionPointer(
ptr, typeof(TDelegate)) as TDelegate;
使用这些导入:
[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;
对于我撰写的帮助程序库,请参阅my GitHub。
答案 1 :(得分:1)
尝试从终端运行它:
export MONO_LOG_LEVEL=debug
export MONO_LOG_MASK=dll
mono --debug yourapp.exe
现在每个库查找都将打印到终端,因此您将能够找出问题所在。
答案 2 :(得分:0)
不确定为什么你认为这与mono有关,因为你遇到的问题不是单声道的动态加载设施。
如果更新后的示例有效,那就意味着Windows上的LoadLibrary()与Linux上的dlopen()具有不同的语义:因此您要么必须接受差异,要么实现自己的处理目录问题的抽象(我的预感是它不是保留的目录,但是Windows只是查看是否已经加载了具有相同名称的库并且它重用了它。)
答案 3 :(得分:0)
我需要将提取的本机库加载到临时位置,而我几乎找到了解决方案。我查看了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这些导入。反思可能有所帮助。