Дата публикации: 22.08.2018
Обновлено: 23.08.2018

IBProvider и «Registration Free COM»


Введение

Начиная с 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_v3_vc15_w32_prof_i.dll">
   <comClass progid="LCPI.IBProvider.3" clsid="{769A1280-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.3» привязан к публичному CLSID «769A1280-04BF-11D8-AE8B-00A0C907DB93».

Специфические CLSID-ы у «LCPI.IBProvider.3», откомпилированного в VS2017 (vc15), такие:

Модуль CLSID провайдера
lcpi.ibprovider_v3_vc15_w32_prof_i.dll 1ED1A41E-5D9F-4EAC-9A8A-E3BAA4BE4901
lcpi.ibprovider_v3_vc15_w64_prof_i.dll C010F775-31CB-457A-9B2F-ED6FA1A686F5

Все специфические идентификаторы перечислены в файле «sdk\ibprovider\v03\lcpi_sdk__ibprovider__v03__private_clsids.cpp».

Если мы хотим использовать «lcpi.ibprovider_v3_vc15_w32_prof_i.dll» в 32-битном процессе, а «lcpi.ibprovider_v3_vc15_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_v3_vc15_w32_prof_i.dll">
   <comClass progid="LCPI.IBProvider.3.32bit" clsid="{1ED1A41E-5D9F-4EAC-9A8A-E3BAA4BE4901}" threadingModel = "Both" />
  </file>
  <file name = "private\lcpi.ibprovider_v3_vc15_w64_prof_i.dll">
   <comClass progid="LCPI.IBProvider.3.64bit" clsid="{C010F775-31CB-457A-9B2F-ED6FA1A686F5}" threadingModel = "Both" />
  </file>
</assembly>

В управляемом приложении, откомпилированном под «AnyCPU», мы динамически определяем разрядность процесса (например, через анализ значения IntPtr.Size) и используем либо «LCPI.IBProvider.3.32bit», либо «LCPI.IBProvider.3.64bit».

Решение второй проблемы с сервисными компонентами OLE DB

Напомним, что вторая проблема состоит из двух частей:

  1. Обработка ошибок OLE DB.
  2. Пул подключений 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_vc15_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_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-битным, то вместо «lcpi.oledb_services_v1_vc15_w32_prof_i.dll» следует использовать «lcpi.oledb_services_v1_vc15_w64_prof_i.dll». ProgID-ы и CLSID-ы менять не нужно.

Модификация манифеста существующего приложения

Для обновления (добавления) манифеста нужно использовать утилиту командной строки «mt.exe», которая есть в поставке Visual Studio.

Первое. Нужно определить целевую платформу (32 или 64 бита) приложения. Если приложение было откомпилировано под «AnyCPU», нужно определить разрядность его процесса.

Для 32-битного случая мы будем использовать модули:

  • lcpi.ibprovider_v3_vc15_w32_prof_i.dll
  • lcpi.oledb_services_v1_vc15_w32_prof_i.dll

В случае 64-битного процесса мы будем использовать модули:

  • lcpi.ibprovider_v3_vc15_w64_prof_i.dll
  • lcpi.oledb_services_v1_vc15_w64_prof_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_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>

Помещаем файл «ibprovider_w32.manifest» в каталог с файлом «RowsetViewer.exe».

Третье. Добавляем описания компонент из «ibprovider_w32.manifest» в манифест приложения.

  1. Запускаем «Developer Command Prompt for VS 2017» (или для другой версии Visual Studio):
    Selelection at Windows Menu
  2. Переходим в каталог с файлами «RowsetViewer.exe» и «ibprovider_w32.manifest».
  3. Выполняем из командной строки:
    mt.exe -outputresource:RowsetViewer.exe;#1 -manifest ibprovider_w32.manifest

    Console Window
  4. Запускаем «RowsetViewer.exe» и заставляем его подключиться к базе данных через «LCPI.IBProvider.3».
  5. Проверяем с помощью программы «Process Explorer», что «RowsetViewer.exe» действительно задействовал наши DLL из каталога «private»:
    Rowset Viewer and used DLLs
  6. Видим, что все OK.

Поскольку «RowsetViewer.exe» изначально не имел манифеста, мы вызывали утилиту «mt.exe» с командой «outputresource».

В случае, когда приложение уже имеет манифест нужно вызывать «mt.exe» с командой «updateresource»:

mt.exe -updateresource:SomeApplication1.exe;#1 -manifest ibprovider_w32.manifest

Модификация манифеста в процессе компиляции приложения

Если нужно модифицировать манифест приложения в процессе его компиляции (в 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="&quot;$(WindowsSDK80Path)bin\x86\mt.exe&quot; -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>

  <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>
</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>

  <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>
</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="&quot;$(WindowsSDK80Path)bin\x86\mt.exe&quot; -updateresource:$(TargetPath);#1 -manifest ..\..\manifests\ibprovider_w64.manifest" />
 <Exec Condition="'$(Platform)'=='Win32'" Command="&quot;$(WindowsSDK80Path)bin\x86\mt.exe&quot; -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="&quot;$(WindowsSDK80Path)bin\x86\mt.exe&quot; -updateresource:$(TargetPath);#1 -manifest .\manifests\ibprovider_w64.manifest" />
 <Exec Condition="'$(PlatformTarget)'=='x86'" Command="&quot;$(WindowsSDK80Path)bin\x86\mt.exe&quot; -updateresource:$(TargetPath);#1 -manifest .\manifests\ibprovider_w32.manifest" />
</Target>

Дата публикации: 22.08.2018. Права на материал принадлежат: IBProvider. При перепечатке ссылка на сайт https://www.ibprovider.com/rus обязательна.