DLL файл загружен дважды с перенаправлением DLL через манифест - PullRequest
8 голосов
/ 27 января 2010

Я включаю python.h в мой * DLL-файл Visual C ++ , который вызывает неявное связывание с python25.dll. Однако я хочу загрузить определенный python25.dll (на компьютере может присутствовать несколько), поэтому я создал очень простой файл манифеста с именем test.manifest :

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
    <file name="python25.dll" />
</assembly>

И я объединяю его с автоматически внедренным файлом манифеста, сгенерированным Visual Studio благодаря:

Configuration Properties -> Manifest Tool -> Input and Output -> Additional Manifest Files
-->$(ProjectDir)\src\test.manifest

python25.dll теперь загружается дважды: тот, который запрошен манифестом, и тот, который Windows должен найти в порядке поиска.

Скрипт Process Explorer http://dl.dropbox.com/u/3545118/python25_dll.png

Почему это происходит и как я могу просто загрузить файл DLL, указанный в манифесте?

Ответы [ 4 ]

6 голосов
/ 24 марта 2011

После исчерпывающего сражения с WinSxS и перенаправлением DLL вот мой совет для вас:

Некоторый фон

Различные вещи могут привести к загрузке DLL-файла под Windows:

  • Явное связывание (LoadLibrary) - загрузчик использует текущий контекст активации запущенного EXE-файла. Это интуитивно понятно.
  • Неявное связывание («загрузка по времени», «автоматическое») - загрузчик использует контекст активации по умолчанию зависимого DLL-файла . Если A.exe зависит от B.dll зависит от C.dll (все неявные связи), загрузчик будет использовать контекст активации B.dll при загрузке C.dll. IIRC, это означает, что если B DllMain загружает C.dll, он может использовать контекст активации B.dll - большую часть времени это означает общесистемный контекст активации по умолчанию. Таким образом, вы получаете свою библиотеку Python от %SystemRoot%.
  • COM (CoCreateInstance) - это противный. Чрезвычайно тонкий. Оказывается, загрузчик может найти в реестре полный путь DLL-файла из реестра, используя COM (в HKCR\CLSID). LoadLibrary не будет выполнять поиск, если пользователь указывает полный путь, поэтому контекст активации не может влиять на разрешение файла DLL. Их можно перенаправить с помощью элемента comClass и друзей, см. [Reference] [msdn_assembly_ref].
  • Даже если у вас правильный манифест, иногда кто-то может изменить контекст активации во время выполнения, используя API-интерфейс активации . Если это так, обычно вы ничего не можете с этим поделать (см. Окончательное решение ниже); это только здесь для полноты. Если вы хотите узнать, кто возится с контекстом активации, WinDbg bp kernel32!ActivateActCtx.

Теперь приступим к поиску виновника

  1. Самый простой способ выяснить причины загрузки DLL-файла - использовать Process Monitor . Вы можете посмотреть " Path , содержащий python25.dll" или " Detail , содержащий python25.dll" (для поиска COM). Двойной щелчок по записи на самом деле покажет вам трассировку стека (сначала нужно установить пути поиска символов, а также установить сервер Microsoft PDB). Этого должно быть достаточно для большинства ваших потребностей.
  2. Иногда трассировка стека, полученная сверху, может порождаться из нового потока. Для этого вам понадобится WinDbg . Это может быть другой темой, но достаточно сказать, что вы можете sxe ld python25 и посмотреть, что делают другие потоки (!findstack MyExeModuleName или ~*k), которые вызывают загрузку DLL-файла.

Реальное решение для мира

Вместо того, чтобы возиться с этой штукой WinSxS, попробуйте подключить LoadLibraryW, используя Mhook или EasyHook . Вы можете просто полностью заменить этот вызов своей собственной логикой. Вы можете закончить это до обеда и снова обрести смысл жизни.

[msdn_assembly_ref]: Манифесты сборки

2 голосов
/ 28 января 2010

Я достиг некоторого прогресса в понимании проблемы.

Сначала позвольте мне прояснить сценарий:

  • Я создаю файл DLL, который встраивает и расширяет Python, используя Python C API и Boost.Python.
  • Таким образом, я предоставляю python25.dll в той же папке, что и мой файл DLL, а также boost_python-vc90-mt-1_39.dll.
  • Тогда у меня есть EXE-файл, который является демонстрацией, показывающей, как создать ссылку на мой DLL-файл: этот EXE-файл не обязательно должен находиться в той же папке, что и мой DLL-файл, если файл DLL можно найти в ПУТИ (я предполагаю, что конечный пользователь может или не может поместить его в одну и ту же папку).

Затем при запуске EXE-файла текущим каталогом не является python25.dll, и поэтому используется порядок поиска, а некоторые другие python25.dll могут быть найдены до моего.

Теперь я понял, что техника манифеста - это хороший подход: мне удалось перенаправить загрузку на «мой» python25.dll.

Проблема в том, что это Boost DLL-файл boost_python-vc90-mt-1_39.dll, который отвечает за "двойную" загрузку!

Если я не загружу этот файл, python25.dll будет правильно перенаправлен. Теперь мне нужно как-то выяснить, как сказать файлу Boost DLL не загружать другой python25.dll ...

1 голос
/ 27 января 2010

Dependency Walker обычно является лучшим инструментом для решения такого рода проблем. Я не слишком уверен, насколько хорошо он обрабатывает манифесты, хотя ...

Где в этом запутанном беспорядке находится фактический исполняемый файл процесса?

На ум приходят две возможности:

  1. Вы пишете DLL-файл расширения Python. Таким образом, процесс Python загружает ваш DLL-файл, и он будет уже иметь свою собственную зависимость python25.dll.

  2. EXE-файл, загружающий ваш DLL-файл, создается с использованием заголовочных файлов и библиотек, предоставляемых проектом DLL-файла. Таким образом, он наследует #pragma comment(lib,"python25.lib") из вашего заголовочного файла и в результате загружает сам файл DLL.

Моя проблема со вторым сценарием заключается в том, что я ожидаю, что файл EXE и ваш файл DLL будут находиться в одной папке в случае, если файл EXE неявно загружает ваш файл DLL. В этом случае файл EXE, ваш файл DLL и файл python25.dll уже находятся в одной папке. Почему тогда будет загружаться версия system32? Порядок поиска неявно загружаемых DLL-файлов всегда находится в папке EXE-файла приложения.

Таким образом, фактический интересный вопрос, неявный в вашем запросе: как вообще загружается system32 python26.dll?

0 голосов
/ 08 октября 2013

Недавно я столкнулся с очень подобной проблемой :

  1. Мое приложение, встраивающее Python, загружает python32.dll из известного расположения, то есть параллельной сборки (WinSxS) с Python.manifest
  2. Попытка import tkinter во встроенном интерпретаторе Python вызвала повторную загрузку того же python32.dll , но по другому адресу, отличному от адреса по умолчанию .
  3. Не удалось выполнить функцию инициализации модуля tkinter (в частности, _tkinter.pyd ) из-за неверного состояния потока интерпретатора Python (_PyThreadState_Current == NULL). Очевидно, Py_Initialize() никогда не вызывался для второго интерпретатора Python, загруженного из дубликата python32.dll.

Почему python32.dll был загружен дважды? Как я объяснил в моем посте о python-capi , это было вызвано тем, что приложение загружало python32.dll из WinSxS, но _tkinter.pyd не распознало сборку, поэтому python32.dll был загружен с использованием обычного пути поиска DLL.

Сборка Python.manifest + python32.dll была распознана механизмом загрузки DLL как другой модуль (в другом контексте активации), чем python32.dll, запрошенный _tkinter.pyd.

Удаление ссылки на Python.manifest из приложения, внедряющего Python и позволяющего пути поиска DLL для поиска DLL, решило проблему.

...