IBProvider и «Registration Free COM»
Решение первой проблемы с указанием платформы
Решение второй проблемы с сервисными компонентами OLE DB
Модификация манифеста существующего приложения
Модификация манифеста в процессе компиляции приложения
Другие примеры
Смотрите также
Введение
Начиная с Windows XP в операционной системе появилась поддержка технологии «Registration Free COM», которая позволяет использовать COM-объекты без их регистрации в реестре Windows.
Для этого достаточно описать необходимые компоненты в манифесте приложения.
Манифест представляет собой XML-документ, хранящийся в ресурсах модуля приложения. Описание компоненты включает в себя ProgID, CLSID и имя модуля (DLL). ProgID может быть произвольным. CLSID должен поддерживаться модулем.
Пример:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider-v5_vc16_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5" clsid="{769A12A0-04BF-11D8-AE8B-00A0C907DB93}" threadingModel = "Both" /> </file> </assembly>
Это позволяет:
- Создавать приложения, устанавливаемые простым копированием файлов.
- Привязывать приложение к конкретным модулям с COM объектами.
Выглядит все замечательно, но у этой технологии есть ряд проблем, на которые стоит обратить внимание.
Первое. Отсутствует возможность указания платформы компоненты: 32 бита или 64 бита. Это не проблема для нативных приложений, которые компилируются под конкретную платформу. Но создает определенные трудности для управляемых приложений, которые могут компилироваться для «AnyCPU».
Второе. В общем случае эта технология не подходит для OLE DB провайдеров, поскольку они опираются на системные компоненты, а системные компоненты получают необходимые сведения о провайдере из реестра Windows. То есть, OLE DB провайдер должен быть зарегистрирован в системе. Без этого, в частности, не будут работать объекты, предоставляющие описания ошибок, и пул подключений OLE DB.
IBProvider предоставляет решение для обоих проблем.
Решение первой проблемы с указанием платформы
Компоненты провайдера привязаны к двум наборам идентификаторам (CLSID):
- Публичные идентификаторы
- Специфические идентификаторы
Публичные идентификаторы не зависят от платформы (32/64 бита) и используемого компилятора. Именно эти CLSID-ы используются для регистрации компонент провайдера в реестре Windows.
Специфические идентификаторы у каждого модуля свои собственные.
Например, «LCPI.IBProvider.5» привязан к публичному CLSID «769A12A0-04BF-11D8-AE8B-00A0C907DB93».
Специфические CLSID-ы у «LCPI.IBProvider.5», откомпилированного в VS2019 (vc16), такие:
Модуль | CLSID провайдера |
lcpi.ibprovider-v5_vc16_w32_prof_i.dll | 16CBE51A-E8DA-4D75-81A5-58E671171A9D |
lcpi.ibprovider-v5_vc16_w64_prof_i.dll | F77C84F7-B095-45D7-9554-600A259E9DE1 |
Все специфические идентификаторы перечислены в файле «cpp\public\source\lcpi\sdk\ibprovider\v05\lcpi_sdk__ibprovider__v05__private_clsids.cpp».
Если мы хотим использовать «lcpi.ibprovider-v5_vc16_w32_prof_i.dll» в 32-битном процессе, а «lcpi.ibprovider-v5_vc16_w64_prof_i.dll» в 64-битном процессе, то можно создать такой манифест:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider-v5_vc16_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.32bit" clsid="{16CBE51A-E8DA-4D75-81A5-58E671171A9D}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc16_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.64bit" clsid="{F77C84F7-B095-45D7-9554-600A259E9DE1}" threadingModel = "Both" /> </file> </assembly>
В управляемом приложении, откомпилированном под «AnyCPU», мы динамически определяем разрядность процесса (например, через анализ значения IntPtr.Size) и используем либо «LCPI.IBProvider.5.32bit», либо «LCPI.IBProvider.5.64bit».
Решение второй проблемы с сервисными компонентами OLE DB
Напомним, что вторая проблема состоит из двух частей:
- Обработка ошибок OLE DB.
- Пул подключений OLE DB.
Обе задачи решаются за счет замещения стандартных сервисных компонент совместимыми реализациями.
IBProvider не использует стандартные компоненты для обработки ошибок и возвращает свои собственные объекты, которые не нуждаются в данных из реестра.
Вместо стандартного пула подключений OLE DB следует использовать пул подключений из «LCPI OLE DB Services». Как и IBProvider, компоненты «LCPI OLE DB Services» имеют публичные и специфические идентификаторы. Кроме того, можно использовать идентификаторы компонент стандартного пула подключений OLE DB:
Стандартная компонента | CLSID |
MSDAINITIALIZE | 2206CDB0-19C1-11D1-89E0-00C04FD7A829 |
PDPO | CCB4EC60-B9DC-11D1-AC80-00A0C9034873 |
Для замены стандартного пула подключений OLE DB на «LCPI OLE DB Services», реализованный 32-битным модулем «lcpi.oledb_services-v1_vc16_w32_prof_i.dll», в манифест приложения нужно добавить такие записи:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.oledb_services-v1_vc16_w32_prof_i.dll"> <comClass progid="MSDAINITIALIZE" clsid="{2206CDB0-19C1-11D1-89E0-00C04FD7A829}" threadingModel = "Both" /> <comClass progid="PDPO" clsid="{CCB4EC60-B9DC-11D1-AC80-00A0C9034873}" threadingModel = "Both" /> </file> </assembly>
Если приложение будет 64-битным, то вместо «lcpi.oledb_services-v1_vc16_w32_prof_i.dll» следует использовать «lcpi.oledb_services-v1_vc16_w64_prof_i.dll». ProgID-ы и CLSID-ы менять не нужно.
Модификация манифеста существующего приложения
Для обновления (добавления) манифеста нужно использовать утилиту командной строки «mt.exe», которая есть в поставке Visual Studio.
Первое. Нужно определить целевую платформу (32 или 64 бита) приложения. Если приложение было откомпилировано под «AnyCPU», нужно определить разрядность его процесса.
Для 32-битного случая мы будем использовать модули:
- lcpi.ibprovider-v5_vc16_w32_prof_i.dll
- lcpi.oledb_services-v1_vc16_w32_prof_i.dll
- lcpi.infrastructure.core-v01_vc16_w32_i.dll [это вспомогательный модуль]
- lcpi.infrastructure.multitasking.ibp-v02_vc16_w32_i.dll [это вспомогательный модуль]
В случае 64-битного процесса мы будем использовать модули:
- lcpi.ibprovider_v3_vc15_w64_prof_i.dll
- lcpi.oledb_services_v1_vc15_w64_prof_i.dll
- lcpi.infrastructure.core-v01_vc16_w64_i.dll [это вспомогательный модуль]
- lcpi.infrastructure.multitasking.ibp-v02_vc16_w64_i.dll [это вспомогательный модуль]
Проведем наш эксперимент с 32-битным нативным приложением «RowsetViewer.exe» из состава «OLE DB SDK», у которого изначально вообще нет никакого манифеста.
Скопируем модули IBProvider-а и «LCPI OLE DB Services» в подкаталог «private», расположенный на том же каталоге, что и «RowsetViewer.exe».
Второе. Создаем файл «ibprovider_w32.manifest» с описаниями компонент, которые нужно будет добавить в манифест приложения:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider-v5_vc16_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5" clsid="{769A12A0-04BF-11D8-AE8B-00A0C907DB93}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc16_w32_prof_i.dll"> <comClass progid="MSDAINITIALIZE" clsid="{2206CDB0-19C1-11D1-89E0-00C04FD7A829}" threadingModel = "Both" /> <comClass progid="PDPO" clsid="{CCB4EC60-B9DC-11D1-AC80-00A0C9034873}" threadingModel = "Both" /> </file> </assembly>
Помещаем файл «ibprovider_w32.manifest» в каталог с файлом «RowsetViewer.exe».
Третье. Добавляем описания компонент из «ibprovider_w32.manifest» в манифест приложения.
-
Запускаем «Developer Command Prompt for VS 2017» (или для другой версии Visual Studio):
- Переходим в каталог с файлами «RowsetViewer.exe» и «ibprovider_w32.manifest».
- Выполняем из командной строки:
mt.exe -outputresource:RowsetViewer.exe;#1 -manifest ibprovider_w32.manifest
- Запускаем «RowsetViewer.exe» и заставляем его подключиться к базе данных через «LCPI.IBProvider.5».
-
Проверяем с помощью программы «Process Explorer», что «RowsetViewer.exe» действительно задействовал наши DLL из каталога «private»:
- Видим, что все OK.
Поскольку «RowsetViewer.exe» изначально не имел манифеста, мы вызывали утилиту «mt.exe» с командой «outputresource».
В случае, когда приложение уже имеет манифест нужно вызывать «mt.exe» с командой «updateresource»:
Модификация манифеста в процессе компиляции приложения
Если нужно модифицировать манифест приложения в процессе его компиляции (в Visual Studio), то можно добавить в проектный файл приложения задачу «AfterBuild» с вызовом «mt.exe».
Обновление управляемого (.NET) приложения, компилируемого для 32-битной платформы:
<Target Name="AfterBuild"> <Message Importance="High" Text="Try to modify the assembly manifest" /> <Exec Condition="'$(PlatformTarget)'=='x86'" Command=""$(WindowsSDK80Path)bin\x86\mt.exe" -updateresource:$(TargetPath);#1 -manifest .\manifests\ibprovider_w32.manifest" /> </Target>
Другие примеры
В дистрибутиве IBProvider можно найти готовые примеры, в которых используется технология «Registration Free COM».
Основная тестовая система IBProvider
Каталог: «TestCode\ActiveX\IBP\oledb_test».
Эта тестовая система является нативным приложением, написанным на C++ и компилируемым под 32-битную и 64-битную Windows.
Данные для манифеста 32-битного бинарного файла (ibprovider_w32.manifest):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider_v3_vc14xp_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc14xp.release" clsid="{5C58747A-2F61-45BA-AC80-5157A4F7AC61}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc14xp_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc14xp.debug" clsid="{3146533B-0C38-4399-A54F-C2A44897F33F}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc15_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc15.release" clsid="{1ED1A41E-5D9F-4EAC-9A8A-E3BAA4BE4901}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc15_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc15.debug" clsid="{2F9837F6-EE9F-4841-A8D3-D0D6803A917C}" threadingModel = "Both" /> </file> <!-- IBProvider v5 --> <file name = "private\lcpi.ibprovider-v5_vc12xp_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc12xp.release" clsid="{4000E7C5-B23B-43FA-9EE8-446170E78F5E}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc12xp_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc12xp.debug" clsid="{06C627D7-64B0-4871-A003-4B85B961F8D9}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc14xp_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc14xp.release" clsid="{AB74369E-0453-4B44-AB71-4370A5F32766}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc14xp_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc14xp.debug" clsid="{B44D56D2-8C4F-4A26-B667-5753CD0E5537}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc15_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc15.release" clsid="{B81942DB-3D3F-43AD-ABC1-54E86004A867}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc15_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc15.debug" clsid="{E5E5D036-0DC4-410C-B091-52AF6E6E9F10}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc16_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc16.release" clsid="{16CBE51A-E8DA-4D75-81A5-58E671171A9D}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc16_w32_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc16.debug" clsid="{3FF6ADB2-22E6-4BDA-8168-EEB5CFB650E7}" threadingModel = "Both" /> </file> <!-- LCPI OLEDB Services --> <file name = "private\lcpi.oledb_services-v1_vc15_w32_prof_i.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc15.release" clsid="{1329FCE3-50CB-4036-AC2F-A98C9651940F}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc15_w32_prof_d.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc15.debug" clsid="{5E8359F3-6D19-4EA9-80EC-7588C2D43698}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc16_w32_prof_i.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc16.release" clsid="{F1CE2600-2673-4DDB-96A8-4EE229B0E97E}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc16_w32_prof_d.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc16.debug" clsid="{2D3F2A3D-A129-47B1-8127-7280C9C51402}" threadingModel = "Both" /> </file> </assembly>
Данные для манифеста 64-битного бинарного файла (ibprovider_w64.manifest):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider_v3_vc14xp_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc14xp.release" clsid="{1B93BA36-09CF-4C04-9BF2-D15EC740B125}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc14xp_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc14xp.debug" clsid="{18111C74-F5DE-4FBE-8234-DC90227C15AF}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc15_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc15.release" clsid="{C010F775-31CB-457A-9B2F-ED6FA1A686F5}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider_v3_vc15_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.3.Private.vc15.debug" clsid="{213E7754-8C67-41C8-9CD4-B92B882D2399}" threadingModel = "Both" /> </file> <!-- IBProvider v5 --> <file name = "private\lcpi.ibprovider-v5_vc12xp_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc12xp.release" clsid="{2A9077CE-0CCE-4BD3-8D35-D7DD9454609D}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc12xp_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc12xp.debug" clsid="{FF98B403-6A5F-40C3-B23B-50EF2427591D}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc14xp_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc14xp.release" clsid="{3BEA0EA0-3C99-4D91-971F-9BC61CA8EE8C}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc14xp_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc14xp.debug" clsid="{5A5602AF-1BC8-4347-A9F5-C61E876E4FEA}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc15_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc15.release" clsid="{CB8F7D22-F43F-441D-A520-B854CE3B8E57}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc15_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc15.debug" clsid="{7A9A2F39-2688-4DC2-9FA1-0F9F5032E85D}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc16_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc16.release" clsid="{F77C84F7-B095-45D7-9554-600A259E9DE1}" threadingModel = "Both" /> </file> <file name = "private\lcpi.ibprovider-v5_vc16_w64_prof_d.dll"> <comClass progid="LCPI.IBProvider.5.Private.vc16.debug" clsid="{AF304342-FD44-454E-A9FC-8CEEB7902A5A}" threadingModel = "Both" /> </file> <!-- LCPI OLEDB Services --> <file name = "private\lcpi.oledb_services-v1_vc15_w64_prof_i.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc15.release" clsid="{C5E11739-40A5-4A00-B8F9-9EDF5BFD4207}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc15_w64_prof_d.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc15.debug" clsid="{2FF3E7C9-5D84-458E-9030-1A7165278A1F}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc16_w64_prof_i.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc16.release" clsid="{F5B3B347-0F32-4D21-B28D-8A9E6619C040}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services-v1_vc16_w64_prof_d.dll"> <comClass progid="LCPI.OleDbServices.DataInitManager.Local.1.Private.vc16.debug" clsid="{4F8AD48C-2ED4-485E-B129-63D1B6192365}" threadingModel = "Both" /> </file> </assembly>
Как видите, ProgID-ы в файлах идентичны, но они привязаны к различным CLSID-ам.
Так же следует обратить внимание на различные ProgID-ы для релизных и отладочных сборок.
Для автоматизации обновления манифеста бинарного файла, в проектный файл добавлены следующие команды:
<Target Name="BeforeBuild"> <Error Text="WindowsSDK80Path not defined!" Condition="'$(WindowsSDK80Path)' == ''" /> </Target> <Target Name="AfterBuild"> <Message Importance="High" Text="Try to modify the assembly manifest" /> <Exec Condition="'$(Platform)'=='x64'" Command=""$(WindowsSDK80Path)bin\x86\mt.exe" -updateresource:$(TargetPath);#1 -manifest ..\..\manifests\ibprovider_w64.manifest" /> <Exec Condition="'$(Platform)'=='Win32'" Command=""$(WindowsSDK80Path)bin\x86\mt.exe" -updateresource:$(TargetPath);#1 -manifest ..\..\manifests\ibprovider_w32.manifest" /> </Target>
Пример для «LCPI ADO.NET Data Provider For OLE DB»
Каталог: «Samples\oledb_net\Collection_001\Sample_0026__PrivateIBProvider»
Пример написан на C# и компилируется под 32-битную и 64-битную платформу.
Для каждой платформы указываются разные модули «IBProvider» и «LCPI OLE DB Services».
Данные для манифеста для 32-битного модуля:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider_v3_vc15_w32_prof_i.dll"> <comClass progid="LCPI.IBProvider.3" clsid="{769A1280-04BF-11D8-AE8B-00A0C907DB93}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services_v1_vc15_w32_prof_i.dll"> <comClass progid="MSDAINITIALIZE" clsid="{2206CDB0-19C1-11D1-89E0-00C04FD7A829}" threadingModel = "Both" /> <comClass progid="PDPO" clsid="{CCB4EC60-B9DC-11D1-AC80-00A0C9034873}" threadingModel = "Both" /> </file> </assembly>
Данные для манифеста для 64-битного модуля:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name = "private\lcpi.ibprovider_v3_vc15_w64_prof_i.dll"> <comClass progid="LCPI.IBProvider.3" clsid="{769A1280-04BF-11D8-AE8B-00A0C907DB93}" threadingModel = "Both" /> </file> <file name = "private\lcpi.oledb_services_v1_vc15_w64_prof_i.dll"> <comClass progid="MSDAINITIALIZE" clsid="{2206CDB0-19C1-11D1-89E0-00C04FD7A829}" threadingModel = "Both" /> <comClass progid="PDPO" clsid="{CCB4EC60-B9DC-11D1-AC80-00A0C9034873}" threadingModel = "Both" /> </file> </assembly>
Для автоматизации модификации манифеста, в проектный файл добавлены следующие команды:
<Target Name="BeforeBuild"> <Error Text="WindowsSDK80Path not defined!" Condition="'$(WindowsSDK80Path)' == ''" /> </Target> <Target Name="AfterBuild"> <Message Importance="High" Text="Try to modify the assembly manifest" /> <Exec Condition="'$(PlatformTarget)'=='x64'" Command=""$(WindowsSDK80Path)bin\x86\mt.exe" -updateresource:$(TargetPath);#1 -manifest .\manifests\ibprovider_w64.manifest" /> <Exec Condition="'$(PlatformTarget)'=='x86'" Command=""$(WindowsSDK80Path)bin\x86\mt.exe" -updateresource:$(TargetPath);#1 -manifest .\manifests\ibprovider_w32.manifest" /> </Target>