воскресенье, 1 июня 2008 г.

Про регистрацию COM объектов при помощи манифестов

Одна из проблем возникающая при попытке сделать "portable" версию некоторой программы, это использование основным exe-файлом COM-объектов.

COM-объекты обычно реализуются в виде dll-файлов, которые регистрируются в системе примерно так:

regsvr32.exe SomeCOMObjects.dll

На самом деле вся регистрация сводится к прописыванию в реестре ключей со списком GUID-ов и ссылками на путь к SomeCOMObjects.dll.

Понятно, что теперь, чтобы программа заработала на компьютере отличном от того, где она установлена, не достаточно утащить туда только exe-файл. В рамках нашей задачи создания portable версии программы, которую можно будет носить на внешнем винчестере или Flash Drive, неплохим вариантом решения будет следующие действия:

1. На внешнем носителе создаем папку для программы и копируем туда, непосредственно exe-файл и все используемые им dll-файлы с COM-объектами.

2. Создаем два bat-файла (или, например, два vbs-файла): COMDllRegister.bat и COMDllUnregister.bat в первом для каждой dll прописываем

regsvr32.exe SomeCOMObjects.dll

во втором

regsvr32.exe /u SomeCOMObjects.dll

Приходя на новый компьютер, запускаем вначале COMDllRegister.bat, потом саму программу, работаем, а перед уходом запускаем COMDllUnregister.bat. Если кроме COM-объектов никаких других привязок в системе у программы нет, то все работает замечательно.

Какие минусы у данного способа?

Минусы очевидны. Если в системе уже были зарегистрированы COM-объекты используемые программой, с которой Вы работаете (известно, что COM это в том числе и способ разделения кода и многие программы используют одни и те же COM-объекты, например, DirectX это исключительно набор COM-компонентов). То при запуске первого bat-файла Вы перенаправите пути с Dll-файлов, расположенных на компьютере, на Dll-файлы на вашем внешнем диске, а после запуска второго, эти COM-объекты из системы исчезнут, что может отрицательно сказаться на работоспособности программ на этом компьютере.

Понятно, что есть варианты решения данной проблемы, например, осуществлять проверку, не зарегистрированы ли уже GUID-ы в реестре, и если да то не регистрировать их, и не разрегистрировать те Dll, которые регистрировали не Вы. Все это понятно, и в принципе, реализуемо.

Но есть способ лучше. Он подробно, с примерами, расписан в статье Registration-Free Activation of COM Components: A Walkthrough.

Суть этого способа сводится к тому, чтобы вместо регистрации COM-объектов в реестре системы, использовать manifest-файлы. Таким образом, в том числе можно использовать в программе вместо dll-зарегистрированной в системе свою.

Итак, что надо сделать.

1. Для каждой dll, содержащей COM-объекты надо создать manifest-файл. Можно это сделать вручную (предварительно придется зарегистрировать dll, а затем воспользоваться утилитой OLE/COM Object viewer), можно воспользоваться утилитой mt.exe из Visual Studio 2005. В результате должен получится набор manifest-файлов примерно такого содержания


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="STDUCore.X" version="1.0.0.0"/>
<file name="STDUCore.dll">
<comClass
clsid="{2BB2E135-4B81-4840-B7CD-A744DD236AB0}"
threadingModel = "Apartment"/>
<comClass
clsid="{1740E2A8-ACF7-4930-B6DF-5D0C85E874DA}"
threadingModel = "Apartment"/>
<typelib tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}" version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub
name="ISTDUImage"
iid="{3905360D-3C87-498C-93D2-27E002B941B1}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}"/>
<comInterfaceExternalProxyStub
name="ISTDUTransform"
iid="{721B21ED-7BCD-4127-9EFF-6C06AA95FE31}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}"/>
</assembly>



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


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type = "win32" name = "client" version = "1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUCore.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUDjVuFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUPDFFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUTiffFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUViewer.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>


STDUCore.X, STDUViewer.X и т.п. это имена файлов manifest для соответствующих dll.

3. Теперь собираем все dll-файлы, основную программу, и manifest-файлы в одну директорию - "portable" версия программы готова.

Выше в качестве примера используются куски manifest-файлов для "portable" версии STDU Viewer



Какие минусы у данного способа?

Во-первых, не всегда возможно определить, какие COM-объекты из каких dll-файлов использует конкретная программа. Во-вторых, мне так и не удалось заставить работать этот способ под Windows Vista, что скорее всего это связано с UAC (но нельзя сказать, что я сильно старался). И, наконец, создание manifest-файлов в ручном режиме, для серьезной программы, мягко говоря, работа не простая, и не шибко веселая.

4 комментария:

Dmitry Kovalenko комментирует...

Эх, жаль, что Висту на асилил.

Тут непонятки всякие вылазят.

RowsetView x64 (средство для тестирования OLEDB провайдеров) под Vista x64 - цепляет манифест и создает COM-объекты

А 32-битный - не хочет.

---
Попробовал на другой программе (консольная, откомпилирована в VS2005 SP1) - не работает ни 64 бита, ни 32 бита.

Моя в печали.

Vladimir комментирует...

Есть мнение, что на Висте с правами какие-то непонятности. Но я так и не докопал:( Другой вариант еще попытаться через вот такую http://msdn.microsoft.com/en-us/library/aa376620.aspx шляпу покопать, но у меня оно как-то вообще не пошло.

Dmitry Kovalenko комментирует...

А я асилил :)

Фича такая - надо встраивать манифест в exe. Если у exe уже есть свой манифест, то надо его вытащить, модифицировать и засунуть обратно. С помощью mt.exe

Тестировал и 32-битные и 64 битные exe. Судя по результатам, от компилятора это не зависит.

Все что нужно указать - это секцию file c подсекцией comClass. На уровне секции assembly.

Виста (у меня x64), если у exe есть встроенный манифест, внешний манифест не цепляет.

Кроме того, почему то не хочет их цеплять к 32-битным exe, даже если у него нет встроенного манифеста. Если указать в свойствах exe "совместимость с XP SP2", то внешний манифест начинает использоваться.

С 64-битными exe без манифеста, внешний манифест цепляется без доп. усилий.

Vladimir комментирует...

> А я асилил :)

Это куль! Буду пробовать. Спасибо.