IBProvider и «LCPI COM API»
Введение
Несколько лет назад к нам обратились с вопросом – «как использовать IBProvider в Windows Azure?»
Изучив проблему, мы поняли, что «никак».
Потому что:
- Нет возможности зарегистрировать IBProvider в целевой системе.
- «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 напрямую.
Начиная с версии 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
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.
Как вы уже догадались — существует вторая реализация 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
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:
Конкретные реализации могут быть как проекциями на «Native Windows COM», так и проекциями на «LCPI Easy COM».
А теперь давайте перейдем к практической части и посмотрим — как использовать LCPI COM API в клиентских приложениях?
Практика
Есть два варианта использования LCPI COM API.
- В native приложениях.
- В .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.
Есть две реализации этого интерфейса:
- lcpi.lib.com.NativeComApiProvider
- lcpi.lib.com.LcpiComApiProvider
Первая реализация работает с Native Windows COM API.
Вторая реализация работает с LCPI COM API, который представляет собой ранее описанную пару DLL-ей:
- lcpi.infrastructure.os.windows.ole32-v01_wXXXX.dll
- lcpi.infrastructure.os.windows.oleaut32-v01_wXXXX.dll
По умолчанию ADO.NET провайдер работает с NativeComApiProvider.
Для того чтобы задействовать LCPI COM API нужно:
- Создать объект LcpiComApiProvider с указанием каталога с файлами LCPI COM API.
- Передать провайдер 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.
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). Должна вывестись такая ошибка:
Ошибка загрузки 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. Снова запускаем нашу программу. Видим новую ошибку:
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) и получаем очередную ошибку:
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. Снова запускаем нашу программу. Если все было сконфигурировано правильно, то программа должна отработать без ошибок. Возможно программа выведет следующий текст:
В конечном итоге в «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».
Если этого не сделать, то программа будет выдавать такую ошибку:
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.
Запускаем программу и видим результат идентичный предыдущему:
Если программа запускается в 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».