Подтвердить что ты не робот

P/Вызов динамически загруженной библиотеки на Mono

Я пишу кросс-платформенную библиотеку .NET, которая использует некоторый неуправляемый код. В статическом конструкторе моего класса обнаружена платформа, и соответствующая неуправляемая библиотека извлекается из встроенного ресурса и сохраняется в каталоге temp, аналогично коду, указанному в qaru.site/info/16728/....

Чтобы библиотека могла быть найдена, когда она не находится в PATH, я явно загружаю ее после ее сохранения в файл temp. В Windows это отлично работает с LoadLibrary из kernel32.dll. Я пытаюсь сделать то же самое с dlopen в Linux, но я получаю DllNotFoundException, когда дело доходит до загрузки методов P/Invoke позже.

Я проверил, что библиотека libindexfile.so успешно сохраняется в каталоге temp и что вызов 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 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);
}

Update:

Похоже, что последующие вызовы 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, второй вызов завершится неудачно, так как он не предполагает, что библиотека с тем же именем будет на одном пути. Есть ли способ обойти это?

4b9b3361

Ответ 1

После долгих поисков и царапин на голове я обнаружил решение. Полный контроль может осуществляться через процесс P/Invoke с помощью динамического P/Invoke, чтобы сообщить время выполнения, где именно найти код.


Edit:

Решение для 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 flags 

Загрузите библиотеку:

IntPtr moduleHandle = dlopen(modulePath, RTLD_NOW);

Получить указатель на функцию:

IntPtr ptr = dlsym(moduleHandle, methodName);

Передайте его делегату по-прежнему:

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

Для вспомогательной библиотеки, которую я написал, см. мой GitHub.

Ответ 2

Попробуйте запустить его с терминала:

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

Теперь каждый поиск библиотеки будет напечатан на терминале, так что вы сможете узнать, что происходит не так.

Ответ 3

Не уверен, почему вы считаете, что это связано с моно, поскольку проблема, с которой вы сталкиваетесь, - это не монодинамические возможности загрузки.

Если ваш обновленный образец работает, это просто означает, что LoadLibrary() в Windows имеет разную семантику, чем dlopen() для Linux: таким образом вы либо должны жить с разницей, либо реализовать свою собственную абстракцию, которая касается проблемы с каталогом (например, я подозреваю, что это не тот каталог, который сохранен, но окна просто смотрят, была ли уже загружена библиотека с тем же именем, и она повторяет это).