Как плагин Win32 App может загрузить свою DLL в своем собственном каталоге
Мой код является плагином для конкретного приложения, написанного на C++ с использованием Visual Studio 8. Он использует две библиотеки DLL от внешнего поставщика. К сожалению, мой плагин не запускается, потому что библиотеки DLL не найдены (я поместил их в тот же каталог, что и сам плагин).
Когда я вручную перемещаю или копирую DLL в директорию хост-приложения, плагин загружается нормально. Это перемещение было сочтено неприемлемо громоздким для конечного пользователя, и я ищу способ, чтобы мой плагин мог прозрачно загружать свои библиотеки DLL. Что я могу сделать?
Соответствующие детали:
- Плагины хост-приложения находятся в каталоге, указанном хост-приложением. Этот каталог не находится в пути поиска DLL, и я не контролирую его.
- Сам плагин упакован как подкаталог каталога плагинов, содержащий сам код плагина, а также любой ресурс, связанный с плагином (например, изображения, файлы конфигурации...). Я контролирую, что находится внутри этого подкаталога, называемого "комплектом", но не где он находится.
- общая идиома установки плагина для этого приложения заключается в том, что конечный пользователь должен скопировать комплект плагинов в каталог плагинов.
Этот плагин является портом от версии плагина для Macintosh. На Mac нет проблем, потому что каждый двоичный файл содержит свой собственный путь поиска в динамической библиотеке, который я установил так, как мне было нужно для моего двоичного файла плагина. Чтобы установить это на Mac, просто включите настройки проекта в Xcode IDE. Вот почему я хотел бы надеяться на что-то подобное в Visual Studio, но я не смог найти ничего уместного. Более того, помощь Visual Studio была не чем иным, как и Google.
Возможный обходной путь - мой код явно скажет Windows, где найти DLL, но я не знаю как, и в любом случае, поскольку мой код даже не запущен, у него нет возможности сделать это.
Как разработчик Mac, я понимаю, что могу попросить что-то очень элементарное. Если это так, я прошу прощения, но у меня кончились волосы, чтобы вытащить.
4 ответа
Вы не просите чего-то очень элементарного. Windows просто не поддерживает то, что вы хотите.
У вас есть несколько вариантов решения этой проблемы:
- Создайте две библиотеки DLL. Ваша реализация плагина dll, которая статически связывается с любыми другими dll, которые вам нужны. И простая "фасадная" dll, которая загружается приложением хостинга. Фасадная библиотека DLL вызывает метод SetDllDirectory, затем LoadLibrary для загрузки библиотеки реализации с требуемым путем поиска, а затем для каждой экспортируемой функции плагина она реализует функцию-заглушку, которая использует GetProcAddress, чтобы просто передать вызов прямо в вашу библиотеку реализации.
Если интерфейс плагина сложный, а используемый вами интерфейс dll - нет, то:
Откажитесь и просто используйте LoadLibrary (с явным путем) и GetProcAddress, чтобы получить доступ к функциональности в ваших спутниковых DLL. Боль.
Последний вариант наименее документирован и плохо понимается программистами Windows. В основном мы используем версию технологии Windows, созданную для поддержки сборок.NET: Side by Side. Не пугайся "Сборка бок о бок" - это просто обычная старая dll, но с сопровождающим файлом.manifest, который предоставляет некоторую дополнительную информацию о ней.
Причина, по которой мы хотим это сделать, заключается в том, что порядок поиска DLL-файлов, связанных с помощью технологии SxS, отличается от обычного порядка поиска DLL:- А именно - после поиска в c: \ windows \ WinSxS окна будут искать в той же папке, что и DLL, которая ссылается на DLL, а не на папку exe.
Начните с инвентаризации всех спутниковых библиотек, на которые ваш плагин должен ссылаться, и создайте из них "сборку". Что означает: создать файл.manifest с кучей файлов = узлов. Вам необходимо дать имя сборке. Давайте назовем это "MyAssembly".
Создайте файл "MyAssembly.manifest" в папке вашего dll, с содержанием, подобным следующему: (перечисляя каждый из dll, который вам нужно включить)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="MyAssembly" processorArchitecture="*" type="win32" version="1.0.0.1"/>
<file name="firstrequireddll.dll"/>
<file name="2ndrequireddll.dll"/>
</assembly>
Теперь это ваш сборочный манифест. Мы наполовину готовы.
Следующая половина заключается в том, чтобы на самом деле заставить вашу dll использовать сборку, и для этого вам нужно добавить ресурс манифеста в ваш файл Dll. В конечном итоге этот манифест должен содержать следующее содержание:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="MyAssembly" version="1.0.0.1" processorArchitecture="*"/>
</dependentAssembly>
</dependency>
</assembly>
Очевидно, манифесты приложения (которые вводят в заблуждение при внедрении в DLL), также могут использовать <file>
узел, так что можно было бы пропустить создание сборки и просто перейти с
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<file name="firstrequireddll.dll"/>
<file name="2ndrequireddll.dll"/>
</assembly>
как манифест DLL. Я еще не играл с этой итерацией, так что я не уверен, как это изменяет нормальный путь поиска DLL (если вообще).
Не зная вашей среды разработки, трудно понять, как посоветовать вам, как добавить манифест в dll. Если вы редактируете файл.rc и вводите манифест вручную, знайте, что в Dlls идентификатор ресурса для использования равен 2, а не 1, что обычно используется в примерах exe.
Если вы используете DevStudio 2005 или выше, есть удобная директива #pragma, которая заставит все магически иметь правильные идентификаторы и быть в правильных местах.
Если в настройках проекта установлены значения по умолчанию, VS2005 и выше автоматически сгенерируют и при необходимости вставят манифест. эта #pragma добавит дополнительные зависимости сборки в сгенерированный манифест:-
#if _MSC_VER >= 1400 // VS2005 added this directive
#pragma comment(linker, \
"\"/manifestdependency:type='Win32' "\
"name='Company.Product.Subsystem' "\
"version='6.0.0.0' "\
"processorArchitecture='*' "\
"language='*'\"")
#endif
Задержка загруженных DLL-файлов ваш друг в этой ситуации. Некоторое время назад я столкнулся с точно такой же проблемой, и на самом деле все довольно просто. Вы указываете компоновщику (/DELAYLOAD
флаг) какие модули загружаются с задержкой, и в основном они не указаны в явном импорте в заголовке PE, поэтому загрузчик не будет жаловаться, когда не может найти упомянутые модули, а все вызовы функций из этих модулей заключены в заглушку который гарантирует, что модуль загружен и функция найдена.
Итак, допустим, вы хотели отложить загрузку библиотеки XmlLite. Сначала вы бы указать /DELAYLOAD:XmlLite.dll
в компоновщике флагов. Затем в функции инициализации вашего модуля (желательно DllMain
) вы распакуете библиотеку XmlLite во временную папку, а затем вызовете LoadLibrary
в теме. С этого момента каждый вызов любой функции, экспортируемой XmlLite.dll, будет разрешаться автоматически.
Используйте GetModuleFileName(), чтобы найти путь, где находится ваша DLL. Затем используйте SetDllDirectory(), чтобы добавить этот путь к пути поиска DLL.
Предполагая, что нативный код и что вы можете использовать явную динамическую ссылку во время выполнения (а не какую-либо форму неявной ссылки), используйте GetModuleHandle и GetModuleFileName, чтобы узнать, откуда работает ваша dll.
HMODULE hModule = GetModuleHandleW(L"RunningDll.dll");
WCHAR path[MAX_PATH];
GetModuleFileNameW(hModule, path, MAX_PATH);
Затем замените базовое имя библиотеки на имя файла plugin.dll, который вы хотите загрузить.
CString plugin(path);
int pos = plugin.Find(L"RunningDll.dll");
plugin = plugin.Left(pos);
plugin += L"pluginName.dll";
Вызовите LoadLibrary для сгенерированной строки.