P / Invoke来动态加载库在Mono(P/Invoke to dynamically load

2019-08-02 23:45发布

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

因此,该库可以当它是不是在路径中找到,我明确地将其保存到临时文件后加载它。 在Windows中,正常工作与LoadLibrary从kernel32.dll中。 我试图做同样的dlopen在Linux上,但我得到一个DllNotFoundException当谈到加载后的的P / Invoke方法。

我已验证库“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);
}

更新:

这样看来,后续调用LoadLibrary在Windows上看看,如果同名的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;
}

如果相同,则试图用dlopen Linux上,第二个电话会失败,因为它不认为具有相同名称的库将在相同的路径。 有没有什么办法解决这?

Answer 1:

一番搜索和头部划伤后,我发现一个解决方案。 完全控制可以通过P / Invoke的过程中通过使用实行动态P /调用准确地告知运行在哪里可以找到的代码。


编辑:

的Windows解决方案

你需要这些进口:

[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");

通过调用得到一个指向DLL中的函数GetProcAddress

IntPtr ptr = GetProcAddress(moduleHandle, methodName);

投射此ptr到类型的委托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 。



Answer 2:

尝试从一个终端运行它是这样的:

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

现在每个图书馆查找将被打印到终端,这样你就可以找出什么地方出了错。



Answer 3:

不知道为什么你认为这是单声道的关系,因为你遇到的问题不在于单一的动态加载设备。

如果您更新的样本的作品,它只是意味着调用LoadLibrary()在Windows比Linux上的dlopen()不同的语义:因此你要么必须忍受的差异或实现自己的抽象与目录问题涉及(我的预感是这并不是说是保留了目录,但Windows只是查看是否具有相同名称的库已经被加载,它会重用)。



Answer 4:

我需要加载提取到一个临时位置的天然库,并且我发现几乎溶液。 我检查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编译器调用之前发现的方法dllimport导入的味儿指该库mono_dllmap_insert()

如果是的话,奇怪的事情发生:

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()单是能够找到该库并加载它(我检查),它能够找到符号(我检查),但因为在过去somewhen它在做的时候JIT它无法加载该库,它决定抛出DllNotFoundException反正。 我想生成的代码包含硬编码throw语句或东西:-O

当您从碰巧不叫你之前已经实时编译相同的库调用另一个本地函数mono_dllmap_insert()它会工作。

所以,你既可以使用添加的手动解决方案通过@gordonmleigh或者你必须告诉单图书馆在之前JITS这些进口。 反思可以帮助那里。



文章来源: P/Invoke to dynamically loaded library on Mono