Дата публикации: 25.01.2023

IBProvider и «LCPI COM API»

Введение

Несколько лет назад к нам обратились с вопросом – «как использовать IBProvider в Windows Azure?»

Изучив проблему, мы поняли, что «никак».

Потому что:

  1. Нет возможности зарегистрировать IBProvider в целевой системе.
  2. «Registration Free COM» предполагает модификацию запускающего EXE-файла, который является частью Azure.

Но мы не стали поднимать руки вверх и сейчас предлагаем вам решение для этой задачи.

В этом документе описывается общая идея LCPI COM API и работа с IBProvider через одну из его реализаций – «LCPI Easy COM».

Все файлы, связанные с «IBProvider Professional Edition» / «Free IBProvider» / «LCPI OLE DB Services» / «LCPI ADO.NET Data provider for OLEDB» / «LCPI Easy COM», можно найти в личном кабинете пользователя на сайте «ibprovider.com».

Предполагается что у читателя уже есть опыт работы с IBProvider и «LCPI ADO.NET Data provider for OLEDB».

Теория

Изначально IBProvider и появившиеся позднее компоненты «LCPI OLE DB Services» работали с COM API напрямую.

Schema_001

Начиная с версии IBProvider v5.27 («LCPI OLE DB Services» — v1.22) взаимодействие с COM API идет через прослойку в виде пары DLL (далее – LCPI COM API), названия которых зависят от разрядности приложения.

Для 32-битных процессов это:

  • lcpi.infrastructure.os.windows.ole32-v01_w32.dll
  • lcpi.infrastructure.os.windows.oleaut32-v01_w32.dll

Для 64-битных процессов:

  • lcpi.infrastructure.os.windows.ole32-v01_w64.dll
  • lcpi.infrastructure.os.windows.oleaut32-v01_w64.dll

Schema_002

LCPI COM API, в общих чертах, представляет собой просто подмножество функций COM API в названия которых был добавлен префикс “LCPI_OS__”:

  • LCPI_OS__CoCreateInstance
  • LCPI_OS__GetErrorInfo
  • LCPI_OS__SetErrorInfo
  • И так далее…

Установочные пакеты IBProvider и «LCPI OLE DB Services» содержат первую реализацию LCPI COM API – «Simple Gate», которая делегирует вызовы в Native Windows COM API.

Schema_003

Как вы уже догадались — существует вторая реализация LCPI COM API, которая называется «LCPI Easy COM».

В ней часть API реализуется третьей DLL (далее – ECM), название которой так же зависит от разрядности процесса.

32-битная версия:

  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll

64-битная версия:

  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll

Schema_004

ECM реализует управление динамическими библиотеками с COM-объектами, описания которых хранятся в XML-файле.

32-битный ECM работает с файлом «lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll.settings».

64-битный ECM работает с файлом «lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll.settings».

Пример файла с описанием IBProvider для 64-битного приложения:

<ECM Version="1.0">
 <Registry>
  <Key Name="HKCR">
   <Key Name="CLSID">
    <Key Name="{769A12A0-04BF-11D8-AE8B-00A0C907DB93}">
     <Value Name="">LCPI.IBProvider.5</Value>
     <Value Name="OLE DB Services" Type="ui4">0xFFFFFFFF</Value>
     <Value Name="flush_log_period" Type="ui4">0</Value>
     <Key Name="ExtendedErrors">
      <Value Name="">Extended Error Service</Value>
      <Key Name="{769A12A1-04BF-11D8-AE8B-00A0C907DB93}">LCPI.IBProvider Error Lookup [v5]</Key>
     </Key>
     <Key Name="InprocServer32">
      <Value Name="">lcpi.ibprovider-v5_vc17_w64_prof_i.dll</Value>
      <Value Name="ThreadingModel">Free</Value>
     </Key>
     <Key Name="ProgID">LCPI.IBProvider.5</Key>
     <Key Name="VersionIndependentProgID">LCPI.IBProvider</Key>
    </Key>

    <Key Name="{769A12A1-04BF-11D8-AE8B-00A0C907DB93}">
     <Value Name="">LCPI.IBProvider Error Lookup [v5]</Value>
     <Key Name="InprocServer32">
      <Value Name="">lcpi.ibprovider-v5_vc17_w64_prof_i.dll</Value>
      <Value Name="ThreadingModel">Free</Value>
     </Key>
    </Key>
   </Key> <!-- /CLSID -->

   <Key Name="LCPI.IBProvider.5">
    <Value Name="">LCPI OLE DB Provider for InterBase [v5]</Value>
    <Key Name="CLSID">{769A12A0-04BF-11D8-AE8B-00A0C907DB93}</Key>
   </Key>

   <Key Name="LCPI.IBProvider">
    <Value Name="">LCPI OLE DB Provider for InterBase [v5]</Value>
    <Key Name="CurVer">LCPI.IBProvider.5</Key>
   </Key>
  </Key>
 </Registry>
</ECM>

Как видите, в файле находятся данные идентичные описаниям COM-объектов в Windows Registry.

В разделe «InprocServer32» можно указывать как абсолютное расположение бинарного файла, так и косвенное. В последнем случае базовым каталогом будет считаться каталог с ECM.

Settings-файлы формируются вручную, но не все так страшно — в дистрибутиве «LCPI Easy COM» уже есть готовые файлы с описанием компонент IBProvider v5 и «LCPI OLE DB Services».

То есть, в итоге, «LCPI Easy COM» в настоящий момент времени представляет собой четыре файла.

Для 32-битных процессов это:

  • lcpi.infrastructure.os.windows.ole32-v01_w32.dll
  • lcpi.infrastructure.os.windows.oleaut32-v01_w32.dll
  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll
  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll.settings

Для 64-битных процессов:

  • lcpi.infrastructure.os.windows.ole32-v01_w64.dll
  • lcpi.infrastructure.os.windows.oleaut32-v01_w64.dll
  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll
  • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll.settings

«LCPI Easy COM» может работать только с DLL-серверами и требует от последних специальной поддержки.

Это означает то, что «LCPI Easy COM» не может создавать, к примеру, COM-объекты ADODB. Даже если они будут зарегистрированы в settings-файле.

И наоборот – вы не можете создавать компоненты, привязанные к «LCPI Easy COM» через «Native Windows COM».

Подытожим ранее сказанное.

LCPI COM API представляет собой набор функций, реализуемых парой DLL:

  • lcpi.infrastructure.os.windows.ole32-v01_wXXXX.dll
  • lcpi.infrastructure.os.windows.oleaut32-v01_wXXXX.dll
  • Конкретные реализации могут быть как проекциями на «Native Windows COM», так и проекциями на «LCPI Easy COM».

    А теперь давайте перейдем к практической части и посмотрим — как использовать LCPI COM API в клиентских приложениях?

    Практика

    Есть два варианта использования LCPI COM API.

    1. В native приложениях.
    2. В .NET приложениях.

    В первом случае следует заменить вызовы COM API на их аналоги с префиксом «LCPI_OS__».

    Список поддерживаемых функций можно посмотреть в заголовочных файлах в каталоге «src\cpp\public\source\lcpi\infrastructure\os» дистрибутива «LCPI Easy COM».

    Не будем здесь тратить на это время и сразу перейдем ко второму, наиболее интересному варианту — .NET приложениям.

    Для поддержки разных вариантов COM API в .NET приложениях вместо прямых вызовов Native Windows COM API используется «прослойка» в виде интерфейса lcpi.lib.com.IComApiProvider.

    Есть две реализации этого интерфейса:

    1. lcpi.lib.com.NativeComApiProvider
    2. lcpi.lib.com.LcpiComApiProvider

    Первая реализация работает с Native Windows COM API.

    Вторая реализация работает с LCPI COM API, который представляет собой ранее описанную пару DLL-ей:

    1. lcpi.infrastructure.os.windows.ole32-v01_wXXXX.dll
    2. lcpi.infrastructure.os.windows.oleaut32-v01_wXXXX.dll

    По умолчанию ADO.NET провайдер работает с NativeComApiProvider.

    Для того чтобы задействовать LCPI COM API нужно:

    1. Создать объект LcpiComApiProvider с указанием каталога с файлами LCPI COM API.
    2. Передать провайдер COM API в статический метод CreateWithComApiProvider нужной компоненты ADO.NET провайдера.

    Объекты класса LcpiComApiProvider создаются стандартным способом – через вызов его конструктора.

    // Constructor
    public LcpiComApiProvider(string rootDir);
    

    В зависимости от разрядности исполняемого процесса LcpiComApiProvider загрузит из каталога, указанного в rootDir, или 32-битные модули или 64-битные модули.

    Имеет смысл хранить 32-битные и 64-битные файлы в разных каталогах и формировать значение параметра rootDir с учетом разрядности текущего процесса.

    Что касается второго пункта, то ADO.NET провайдер содержит следующие классы, создающие внутренние COM-объекты через COM API:

    • OleDbServices
    • OleDbConnectionStringBuilder
    • OleDbConnection
    • OleDbEnumerator

    Для поддержки IComApiProvider в этих классах определены статические методы с общим названием – CreateWithComApiProvider.

    Отметим некоторые вещи, связанные с этими классами.

    OleDbEnumerator в настоящий момент времени будет работать только с Native Windows COM по причине отсутствия COM-объекта «MSDAENUM» с поддержкой «LCPI Easy COM».

    Глобальный экземпляр OleDbServices, используемый по умолчанию объектами OleDbConnection, всегда использует NativeComApiProvider.

    Если объекты OleDbConnection и OleDbServices привязаны к разным провайдерам COM API, то при вызове OleDbConnection.Open будет сгенерировано исключение. Поэтому имеет смысл создавать объекты OleDbConnection через вызов OleDbServices.CreateInstance – в этом случае объект подключения «унаследует» провайдер COM API сервисного объекта.

    Закрытый объект OleDbConnection разрешает менять COM API провайдер через RW-свойство ComApiProvider.

    Тестовый проект

    Для создания тестового примера будет использоваться Visual Studio 2017, Firebird 3 и база данных employee.fdb.

    Предполагается что приложение будет выполняться в 32-битном процессе. Для этого в настройках проекта нужно поставить галочку «Prefer 32-bit».

    Итак…

    1. Устанавливаем «LCPI ADO.NET Data provider for OLE DB». Указываем установку сборок для FW4.8.

    2. Создадим в VS2017 консольный проект Example_001 на C# для .NET FW 4.8.

    3. Добавляем в наш проект ссылки на lcpi.lib и lcpi.data.oledb.

    Schema_002

    4. Пишем следующий код:

    using System;
    
    using xdb=lcpi.data.oledb;
    using com_lib=lcpi.lib.com;
    
    namespace Example_001
    {
     class Program
     {
      private const string c_cn_str
       ="provider=LCPI.IBProvider.5;"
       +"location=inet4://localhost/d:\\database\\fb_03_0_0\\employee.fdb;"
       +"dbclient_type=fb.direct;"
       +"auto_commit=true;"
       +"user id=SYSDBA;"
       +"password=masterkey;";
    
      static void Main(string[] args)
      {
       com_lib.IComApiProvider comApiProvider=null;
    
       try
       {
        var asmPath
         =System.Reflection.Assembly.GetExecutingAssembly().Location;
    
        var curAsmDir
         =System.IO.Path.GetDirectoryName(asmPath);
    
        switch(IntPtr.Size)
        {
         case 4:
          curAsmDir=System.IO.Path.Combine(curAsmDir,"private-w32");
          break;
    
         case 8:
          curAsmDir=System.IO.Path.Combine(curAsmDir,"private-w64");
          break;
    
         default:
          throw new ApplicationException("ERROR!");
        }//switch
    
        comApiProvider
         =new com_lib.LcpiComApiProvider(curAsmDir);
    
        var svc
         =xdb.OleDbServices.CreateWithComApiProvider(comApiProvider);
    
        var cn
         =svc.CreateConnection(c_cn_str);
    
        cn.Open();
    
        var cmd
         =new xdb.OleDbCommand("select count(*) from employee",cn);
    
        object value
         =cmd.ExecuteScalar();
    
        Console.WriteLine("VALUE: {0}",value);
       }
       catch(Exception e)
       {
        Console.WriteLine("ERROR: {0} - {1}",e.Source,e.Message);
       }
      }
     }
    }
    

    5. Компилируем его и сразу запускаем (Ctrl+F5). Должна вывестись такая ошибка:

    Error in lcpi.lib.com.LcpiComApiProvider
    Ошибка загрузки DLL [D:\Users\…\Samples\Example_001\bin\Debug\private-w32\lcpi.infrastructure.os.windows.ole32-v01_w32.dll]. Код ошибки Win32: 126.

    Код ошибки COM: 0x8007007E.

    Она означает что LcpiComApiProvider не смог загрузить DLL из LCPI COM API.

    6. Копируем в каталог «bin\Debug\private-w32» 32-битные файлы «LCPI Easy COM»:

    • lcpi.infrastructure.os.windows.ole32-v01_w32.dll
    • lcpi.infrastructure.os.windows.oleaut32-v01_w32.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll.settings

    7. Проверяем название файла «lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll.settings». Если у него есть суффикс «—example», то удаляем этот суффикс.

    8. Снова запускаем нашу программу. Видим новую ошибку:

    Error in Multiple Sources
    1. [lcpi.infrastructure.os.windows.ecm] [win32] Ошибка загрузки динамической библиотеки [D:\Users\…\Samples\Example_001\bin\Debug\private-w32\lcpi.oledb_services-v1_vc17_w32_prof_i.dll]. Код ошибки WIN32: 126.

    Код ошибки COM: 0x8007007E.
    ———————————
    2. [lcpi.data.oledb.OleDbServices] Ошибка создания системной компоненты для инициализации OLE DB провайдеров. Идентификатор компоненты: 2206CDB0-19C1-11D1-89E0-00C04FD7A829.

    Код ошибки COM: 0x8007007E.

    Она означает, что ECM не находит файл lcpi.oledb_services-v1_vc17_w32_prof_i.dll.

    Скачиваем «LCPI OLE DB Services» (vc17, 32bit).

    Устанавливаем его.

    Из каталога «c:\Program Files (x86)\LCPI\OleDbServices.1\bin» нужно скопировать в «bin\Debug\private-w32» следующие файлы:

    • lcpi.infrastructure.core-v01_vc17_w32_i.dll
    • lcpi.infrastructure.multitasking.ibp-v03_vc17_w32_i.dll
    • lcpi.oledb_services-v1_vc17_w32_prof_i.dll

    Не надо копировать из «c:\Program Files (x86)\LCPI\OleDbServices.1\bin» файлы:

    • lcpi.infrastructure.os.windows.ole32-v01_w32.dll
    • lcpi.infrastructure.os.windows.oleaut32-v01_w32.dll

    Это проекция LCPI COM API на Native Windows COM API.

    9. Снова запускаем нашу программу (Ctrl+F5) и получаем очередную ошибку:

    Error in Multiple Sources
    1. [lcpi.infrastructure.os.windows.ecm] [win32] Ошибка загрузки динамической библиотеки [D:\Users\…\Samples\Example_001\bin\Debug\private-w32\lcpi.ibprovider-v5_vc17_w32_prof_i.dll]. Код ошибки WIN32: 126.

    Код ошибки COM: E_FAIL.
    ———————————
    2. [LCPI.OleDbServices.DataInitManager.Global.1] Не удалось создать OLE DB провайдер [clsid: {769A12A0-04BF-11D8-AE8B-00A0C907DB93}][clsctx: 1].

    Код ошибки COM: 0x8007007E.
    ———————————
    3. [lcpi.data.oledb.OleDbConnection] Ошибка создания OLE DB провайдера с ProgID «LCPI.IBProvider.5».

    Код ошибки COM: 0x8007007E.

    Здесь у нас сервисный объект не может создать OLE DB провайдер – отсутствует файл «lcpi.ibprovider-v5_vc17_w32_prof_i.dll».

    10. Копируем «lcpi.ibprovider-v5_vc17_w32_prof_i.dll» из установочного каталога «IBProvider Professional Edition» (vc17, 32bit) в каталог «bin\Debug\private-w32».

    11. Снова запускаем нашу программу. Если все было сконфигурировано правильно, то программа должна отработать без ошибок. Возможно программа выведет следующий текст:

    VALUE: 42

    В конечном итоге в «bin\Debug\private-w32» у нас будут следующие файлы:

    • lcpi.ibprovider-v5_vc17_w32_prof_i.dll
    • lcpi.infrastructure.core-v01_vc17_w32_i.dll
    • lcpi.infrastructure.multitasking.ibp-v03_vc17_w32_i.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w32.dll.settings
    • lcpi.infrastructure.os.windows.ole32-v01_w32.dll
    • lcpi.infrastructure.os.windows.oleaut32-v01_w32.dll
    • lcpi.oledb_services-v1_vc17_w32_prof_i.dll

    Если у вас нет «IBProvider Professional Edition» (lcpi.ibprovider-v5_vc17_w32_prof_i.dll), то можно задействовать «Free IBProvider».

    Для этого поменяйте программный идентификатор провайдера в переменной c_cn_str.

      private const string c_cn_str
       ="provider=LCPI.IBProvider.5.Free;" // <-----------
       +"location=inet4://localhost/d:\\database\\fb_03_0_0\\employee.fdb;"
       +"dbclient_type=fb.direct;"
       +"auto_commit=true;"
       +"user id=SYSDBA;"
       +"password=masterkey;";
    

    После этого скопируйте lcpi.ibprovider-v5_vc17_w32_free_i.dll в «bin\Debug\private-w32».

    Если этого не сделать, то программа будет выдавать такую ошибку:

    Error in Multiple Sources
    1. [lcpi.infrastructure.os.windows.ecm] [win32] Ошибка загрузки динамической библиотеки [D:\Users\…\Samples\Example_001\bin\Debug\private-w32\lcpi.ibprovider-v5_vc17_w32_free_i.dll]. Код ошибки WIN32: 126.

    Код ошибки COM: E_FAIL.
    ———————————
    2. [LCPI.OleDbServices.DataInitManager.Global.1] Не удалось создать OLE DB провайдер [clsid: {769A12AC-04BF-11D8-AE8B-00A0C907DB93}][clsctx: 1].

    Код ошибки COM: 0x8007007E.
    ———————————
    3. [lcpi.data.oledb.OleDbConnection] Ошибка создания OLE DB провайдера с ProgID «LCPI.IBProvider.5.Free».

    Код ошибки COM: 0x8007007E.

    Запускаем программу и видим результат идентичный предыдущему:

    VALUE: 42

    Если программа запускается в 64-битном процессе, то нужно создать каталог «bin\Debug\private-w64» и добавить в него такие файлы:

    • lcpi.ibprovider-v5_vc17_w64_prof_i.dll
    • lcpi.infrastructure.core-v01_vc17_w64_i.dll
    • lcpi.infrastructure.multitasking.ibp-v03_vc17_w64_i.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll
    • lcpi.infrastructure.os.windows.lcpi_easy_com-v01_w64.dll.settings
    • lcpi.infrastructure.os.windows.ole32-v01_w64.dll
    • lcpi.infrastructure.os.windows.oleaut32-v01_w64.dll
    • lcpi.oledb_services-v1_vc17_w64_prof_i.dll

    Для работы через 64-битный «Free IBProvider», в каталог «private-w64» нужно скопировать файл «lcpi.ibprovider-v5_vc17_w64_free_i.dll».


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