13

我正在编写一个使用一些非托管代码的跨平台 .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);
}

更新:

看起来随后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;
}

如果在 Linux 上尝试相同dlopen,第二次调用将失败,因为它不假定具有相同名称的库将位于同一路径。有没有办法解决这个问题?

4

4 回答 4

22

经过大量搜索和挠头,我发现了一个解决方案。通过使用动态 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");

通过调用获取指向 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

于 2012-11-21T11:50:33.417 回答
1

尝试从终端像这样运行它:

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

现在每个库查找都将打印到终端,因此您将能够找出问题所在。

于 2012-11-19T22:22:37.867 回答
1

我需要加载一个提取到临时位置的本机库,我几乎找到了解决方案。我检查了 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().

因为如果真的发生了,就会发生奇怪的事情:

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

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

当您从同一个库中调用另一个在您调用之前恰好没有经过 JITted 的本机函数时mono_dllmap_insert(),它将起作用。

因此,您可以使用@gordonmleigh 添加的手动解决方案,或者您必须在它 JIT 任何这些导入之前告诉 Mono 库在哪里。反思可能会有所帮助。

于 2018-05-09T15:03:25.400 回答
0

不知道为什么您认为这与单声道有关,因为您遇到的问题与单声道的动态加载设施无关。

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

于 2012-11-20T09:48:29.773 回答