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