Пишем модуль для экспорта данных с сайта издательства "ЛАНЬ" как пример реализации плагина к ИРБИС64
Вероятно, кому-нибудь из вас, как и мне, хотелось бы расширить функциональность Ирбиса своими собственными программными дополнениями. Но что, если вы предпочитаете вести разработку на современных управляемых языках программирования, таких как C# ? Можно ли встраивать создаваемые на таких языках модули в Ирбис? Чтобы разобраться в этом вопросе, я и создал данную тему. Впрочем, те, кто планирует писать свои дополнения на Delphi, тоже могут найти здесь что-то полезное для себя. Для них такое задание: переписать описываемый модуль полностью на Delphi
.
Прежде всего, dll, написанная на C#, в отличие от dll на Delphi и других языках с компиляцией в машинный код, представляет собой
управляемую сборку. Это не набор функций (обычно называемых нативными), которые мы можем напрямую вызывать из Каталогизатора или других приложений, написанных на неуправляемых языках. В данном случае мы имеем отдельный класс с набором методов, являющихся управляемыми и, следовательно, недоступных непосредственно из машинного кода. Их выполнению должна предшествовать загрузка исполняемой среды CLR, в которой работают все управляемые приложения .NET. Обычно для этого нужные .NET сборки упаковывают в COM-компоненты. Это достаточно долгий путь, которому посвящены отдельные статьи. Нет ли способа проще? Оказывается, есть. Он состоит в том, чтобы напрямую экспортировать отдельные методы нашей сборки в виде неуправляемых. Существует широко распространённое мнение о том, что это невозможно. Однако оно не соответствует действительности. Давайте немного разберёмся.
Если речь идёт о встроенных средствах языков C# и VB.NET, то, действительно, они не позволяют нам помечать методы как неуправляемые. Однако такие средства есть в ассемблере IL, в код которого транслируются все программы на этих языках. Для этого существует специальный атрибут, который говорит нам о том, что методы следует экспортировать как неуправляемые (нативные). Раньше для этого нужно было откомпилировать модуль, дизассемблировать его в IL, добавить необходимые атрибуты и собрать снова. Естественно, это требовало определённых познаний языка IL (стекового ассемблера). К счастью, нашёлся добрый человек, который создал средства, позволяющие автоматизировать этот процесс. Их описанию посвящена статья [
www.codeproject.com]. Теперь всё, что нам требуется, это подключить в Visual Studio к нашему проекту dll-плагина сборку ExportDllAttribute, после чего отдельные методы можно помечать специальным атрибутом.
В нашем демонстрационном проекте мы будем из функции, вызываемой Ирбисом (её имя, как и имя её библиотеки dll, указывается в конфигурационном файле), загружать окно с браузером, в котором мы можем осуществлять навигацию по страницам сайта издательства "ЛАНЬ" и копировать библиографические данные отдельных изданий, которые мы можем напрямую импортировать в Ирбис. Для этого наша функция на C# помечена специальным атрибутом и имеет следующий вид:
[ExportDllAttribute("LANExport", System.Runtime.InteropServices.CallingConvention.Cdecl)]
public static int LANExport(IntPtr buf1, ref IntPtr buf2, int bufsize)
{
Form1 form = new Form1();
byte[] textBytes = new byte[bufsize];
Marshal.Copy(buf1, textBytes, 0, bufsize);
byte[] textAnsiArray = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(1251), textBytes);
form.record = new StringBuilder(Encoding.GetEncoding(1251).GetString(textAnsiArray));
form.ShowDialog();
if (form.readyToPass)
{
buf2 = Marshal.AllocHGlobal(bufsize);
textBytes = Encoding.GetEncoding(1251).GetBytes(form.record.ToString());
textBytes = Encoding.Convert(Encoding.GetEncoding(1251), Encoding.UTF8, textBytes);
Marshal.Copy(textBytes, 0, buf2, textBytes.Length);
return 1;
}
return 0;
}
Как мы видим, функция
ExportBO имеет два параметра типа IntPtr, представляющих собой исходную и конечную записи для Ирбиса, и один целочисленный параметр - размер записи. Вызывается оконная форма и ей передаётся управление. По выходу из окна, если параметр
readyToPass имеет значение
true, сохранённое значение записи преобразуется к типу IntPtr и кодировке UTF-8 и затем экспортируется путём указания 1 в качестве кода возврата в операторе
return 1; В случае отсутствия изменений выход из функции осуществляется оператором
return 0;
Чтобы проверить работу нашего приложения (как известно, dll-библиотека не может быть запущена напрямую), мы добавили к решению небольшой проект
Call_LAN с кнопкой и текстовым окном. При запуске приложения мы создаём пробную запись для передачи функции. Нажатием кнопки мы вызываем функцию ExportBO, а возвращаемые ей данные копируются в текстовое окно.
Внешний вид оконного приложения, вызываемого функцией
ExportBO(), следующий:
Отметим, что библиографическое описание можно скопировать не с любой страницы сайта, а лишь со страниц, содержащих информацию о каком-либо отдельном издании:
Однако пока скомпилированный нами модуль
LAN_Export_NET.dll может вызываться только из управляемых приложений, подобных нашему проекту Call_LAN. Чтобы сделать его доступным для неуправляемого кода, недостаточно просто пометить метод, как мы сделали. Надо ещё обработать его специальной утилитой (которая выполняет за нас всю "грязную" работу по взаимодействию с кодом IL). Для этого требуется зайти в папку с её исполняемым файлом и передать ей в командной строке в качестве параметра полный путь к файлу
LAN_Export_NET.dll. Например, если вы разархивируете все мои приложенные файлы в папку D:\Temp, найдите там папку
D:\Temp\ExportDll\ExportDll\bin\Debug\ и вызовите из неё утилиту следующим образом:
ExportDll.exe D:\Temp\LAN_Export2IRBIS\bin\Debug\LAN_Export_NET.dll
Небольшой нюанс: чтобы утилита сработала, в конфигурационном файле
ExportDll.exe.config должны быть правильным образом настроены пути к файлам ilasm.exe и ildasm.exe. Это зависит от установленной у вас версии .NET и Windows. Второй путь - это обычно
C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe или
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe.
Теперь надо скопировать полученный файл
LAN_Export_NET.dll в папку Ирбиса и указать его в качестве значения параметра UMDLL
n конфигурационного файла
irbisc.ini (где n - порядковый номер кнопки, на которую навешан наш плагин). Надо также прописать там UMFUNCTION0=LANExport, UMPFT0=LAN и кинуть файл
LAN.pft во вложенную папку
Datai\Deposit\ (подробнее см. документацию и [
irbis.gpntb.ru]). После этого теоретически всё должно работать, и если бы мы изначально создавали нашу dll на Delphi, то проблем бы, наверное, не было. А теперь к плохим новостям
.
Мне удалось реализовать таким образом лишь передачу данных из Ирбиса на входе в функцию, когда она отображает текущую запись в своём текстовом поле. Передачи её обратно мне достичь таким путём, увы, не удалось. Видимо, существует какая-нибудь особенность при передаче параметров из управляемого метода, не учтённая создателями Ирбиса. Если вам известен простой способ решения этой проблемы, пожалуйста, сообщите мне об этом. Так или иначе, мы создадим пока ещё одну программную прослойку на Delphi, которая и будет вызывать нашу управляемую сборку
LAN_Export_NET.dll. Причём, поскольку вся логика содержится в управляемом коде, знание Delphi тут практически не требуется. Этот проект должен будет создавать библиотеку
LAN_Export.dll и состоять из двух модулей -
LAN_Export.dpr и
LAN_Export1.pas. Вот их текст:
{LAN_Export.dpr}
library LAN_Export;
uses
SysUtils,
Classes,
LAN_Export1 in 'LAN_Export1.pas';
Exports LANExport1 name 'LANExport';
{$R *.res}
begin
end.
{LAN_Export1.pas}
unit LAN_Export1;
interface
uses
SysUtils,
StrUtils;
function LANExport1(buf1,buf2: Pchar; size: integer): integer; stdcall;
implementation
function LANExport(buf1: Pchar; var buf2: string; size: integer): integer; stdcall; external 'LAN_Export_NET.DLL' ;
function LANExport1(buf1, buf2: Pchar; size: integer): integer;
var
res : integer;
rec : string;
begin
res := LANExport(buf1, rec, size);
StrLCopy(buf2,Pchar(rec),size);
LANExport1 := res;
end;
end.
Скомпилируем
LAN_Export.dll и также скопируем её в рабочую папку Ирбиса. Вы уже догадались, что именно этот файл мы будем вызывать по нажатию пользовательской кнопки Каталогизатора. Поскольку в этом модуле тоже имеется функция LANExport, достаточно будет изменить значение параметра UMDLL.
По идее после этого всё должно заработать. На практике же наш модуль срабатывает только один раз из нескольких попыток запуска, а в остальное время Каталогизатор наглухо висит с отображением на белом фоне сообщения "Выполняется режим пользователя". Думаю, это программный глюк Ирбиса - ведь посредством внешнего тестового модуля наша функция срабатывает всегда.
В любом случае, даже если у вас не получилось проделать описанный мной трюк, вы всё равно научились кое-чему новому. Возможно, этот опыт пригодится при написании на .NET плагинов для других (неуправляемых) приложений. Пока "приручить Ирбис" на 100% для работы с .NET-сборками мне не удалось. Но мне кажется, у данного направления есть перспектива. Например, насколько мне известно, программный интерфейс для ручки-сканера C-Pen существует только под .NET. И если мы хотим автоматизировать процесс каталогизации с помощью C-Pen (
речь идёт именно об интеллектуальной автоматизации, а не о простом переносе отсканированных строк текста в Ирбис!), то приведённое решение, позволяющее воспользоваться интегрированным модулем лишь один раз за весь сеанс работы с Каталогизатором, думаю, пользователей не устроит. Мне кое-как удалось встроить таким образом модуль распознавания библиографического описания, предварительно убрав из него все сборки, подключающие OCR и spell-check, оставив только голое текстовое поле. Но и в таком виде он сработал всего пару раз, после чего мне пришлось перезагрузиться
.
На всякий случай вот фрагмент файла
irbisc.ini, содержащий требуемую модификацию:
[USERMODE]
UMNUMB=2
UMDLL0=LAN_Export.dll
UMFUNCTION0=LANExport
UMPFT0=LAN
UMNAME0=Экспорт из ЛАНИ
UMGROUP0=2
UMICON0=
Файл
LAN.pft состоит из одной строки:
&unifor('+0')