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

gor*_*igh 12 c# mono pinvoke unmanaged

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

因此,当它不在PATH中时可以找到它,我在将它保存到临时文件后显式加载它.在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);
}
Run Code Online (Sandbox Code Playgroud)

更新:

看起来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;
}
Run Code Online (Sandbox Code Playgroud)

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

gor*_*igh 14

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


编辑:

Windows解决方案

你需要这些进口:

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

[DllImport("kernel32.dll")]
protected static extern IntPtr GetProcAddress(IntPtr hModule, string procname);
Run Code Online (Sandbox Code Playgroud)

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

IntPtr moduleHandle = LoadLibrary("path/to/library.dll");
Run Code Online (Sandbox Code Playgroud)

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

IntPtr ptr = GetProcAddress(moduleHandle, methodName);
Run Code Online (Sandbox Code Playgroud)

将此ptr转换为类型的委托TDelegate:

TDelegate func = Marshal.GetDelegateForFunctionPointer(
    ptr, typeof(TDelegate)) as TDelegate;
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

加载库:

IntPtr moduleHandle = dlopen(modulePath, RTLD_NOW);
Run Code Online (Sandbox Code Playgroud)

获取函数指针:

IntPtr ptr = dlsym(moduleHandle, methodName);
Run Code Online (Sandbox Code Playgroud)

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

TDelegate func = Marshal.GetDelegateForFunctionPointer(
    ptr, typeof(TDelegate)) as TDelegate;
Run Code Online (Sandbox Code Playgroud)

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