2.4.12. Использование OLE (ActiveX компонентов)

2.4.12.1. Получить экземпляр OLE-объекта

В Pascal-скрипте, чтобы получить экземпляр ole-объекта (создать), необходимо воспользоваться функцией CreateOleObject. На вход принимается строка - идентификатор сласса объета, зарегистрированный в системе, например: ‘Word.Application’, или ‘OPOS.Scanner’. Возвращается методом интерфейс IDispatch, который в скриптере сразу заворачивается в Variant для хранения и дальнейших манипуляций. Обращение к методам и свойствам ole-объекта реализуется синтаксисом совершенно аналогичным синтаксису обращения к методам и свойствам обычного скриптового объекта.

2.4.12.2. Утилизация OLE-объекта

Время жизни объекта управляется счётчиком ссылок. Всё что требуется от разработчика - занулить все ссылки на полученный им объект, и полученные в процессе эксплуатации объекта ссылки на его составные части.

Пример:

oleobject := CreateOleObject('SomeRegisteredOLE.Factory');
  nestedObject := oleobject.NestedObject;
  nestedObject.DoSomthing;
  oleobject := null;
  nestedObject := null;

Если полученный объект не передавался в какое-либо постоянное хранилище, только использовался в операции, - зануление, в принципе, не обязательно, т.к. по окончанию выполнения операции все имевшиеся в выполнявшем её экземпляре скриптера значения всё равно будут занулены. И, соответственно, наоборот: если требуется распространить время жизни полученного ole-объекта за пределы времени выполнения операции - следует сохранить ссылку где-либо.

Например:

Selection.AddVar('MyOleObject', oleobject, ftVariant);

В этом случае время жизни ole-объекта, будет идентично времени жизни выборки, если значение MyOleObject не будет изменено раньше.

2.4.12.3. Обработка событий возбуждаемых OLE-объектом

2.4.12.3.1. Общее описание работы с обработчиками событий

В технологии OLE как подмножество технологии COM обработка событий реализована через обращение к переданному(подключенному) от владельца объекта интерфейсу. Интерфейс callback-ов (зачастую в один интерфейс включены несколько обработчиков - методов, которые вызывает ole-объект при наступлении соответствующего события, объединённых смысловой нагрузкой) в каждом отдельном случае реализации COM-объекта имеет чёткую однозначную спецификацию (описанную в документации объекта и, в случае ActiveX компонента, в TypeLibrary библиотеки) и идентификатор (GUID. Так же описан в документации или TypeLibrary).

COM-объект может специфицировать несколько разных callback-интерфейсов. К COM-объекту может быть подключено разом несколько callback-интерфесов, как разных, так и однотипных.

Для OLE-объектов интерфейсы обработчиков(callback-ов) специфицируются как наследник IDispatch - dispinterfaceDispinterface описывает какие методы и свойства содержатся в OLE-объекте, их индексы (OLE практикует индексное обращение к методам и свойствам), параметры и типы возвращаемых значений.

2.4.12.3.2. Реализация обработчика в скриптере

В скриптере имеется возможность подключить операцию выборки в качестве обработчика событий к OLE-объекту. Возможность реализована по средствам скриптового класса TbtkScriptComEvents, он реализует IDispatch и перенаправляет все вызовы в операцию выборки.

Свойства и методы TbtkScriptComEvents:

  • class function Create(EventsIID: String): TbtkStriptComEvents;

    Создаёт новый экземпляр адаптера обработки событий: события генерируемые COM-объектом вызывают операцию выборки. Экземпляр представляющий заданный в параметре EventsIID класс COM-обработчика. EventsIID - GUID представленный строкой вида {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}. GUID dispinterface-а обработчика событий COM-объекта.

  • procedure Connect(ComObject: Variant; Operataion: TbtkScriptOperationObject);

    Установить обработчик событий COM-объекту (подключить к COM-объекту). COM-объекту передаётся ссылка на IDispatch реализуемый данным экземпляром TbtkScriptComEvents, также ссылка остаётся на COM-объект у экземпляра TbtkScriptComEvents. Ссылка на COM-объект (после успешного подключения) доступна через свойство ComObject. Проверить успех подключения можно через свойство Connected. В последствии вызовы событий данного интерфейса COM-объектом будут перенаправляться в операцию выборки переданную как параметр Operation.

    Параметры передаваемые в операцию-обработчик:

    1. Sender - COM(OLE)-объект вызвавший обработчик.
    2. DispId - индекс вызванного метода интерфейса-обработчика (dispinterface)
    3. Params - параметры переданные при вызове. VariantArray of Variant.
  • procedure Disconnect;

    Отключить обработчик от COM-объекта. Убрать ссылку на себя из COM-объекта и занулить ссылку на COM-объект.

  • property Connected: Boolean;

    Свойство сообщает о состоянии обработчика, True - подключен (успешно выполнен метод Connect).

  • property ComObject: Variant;

    COM-объект к которому подключен обработчик (для которого выполнен метод Connect, он же передаётся как Sender в операцию-обработчик).

Время жизни экземпляров TbtkScriptComEvents в скриптере управляется счётчиком ссылок.

// Создать новый экземпляр COM-объекта
 device := OPOS_OpenNewDevice('Scanner', 'MyScanner1', True);
 //Создать адаптер обработки событий COM-объекта
 events := TbtkScriptComEvents.Create('{CCB90183-B81E-11D2-AB74-0040054C3719}');
 //Подключить обработчик к COM-объекту
 events.Connect(device, Selection.OperationByName('TestOp'));
 if events.Connected then
   //передать ссылку выборке, чтобы иметь возможность управлять им в последствии.
   Selection.AddVar('scanner', events)
 else
   Raise('Невозможно установить обработчик. Объект не соответствует спецификации.');

2.4.12.3.2.1. Примечания:

  1. Экземпляр обработчика(TbtkScriptComEvents) будучи успешно подключенным к COM(или OLE)-объекту требует обязательного отключения (вызова Disconnect), в противном случае ни COM-объект, ни экземпляр обработчика никогда не будут уничтожены до самого завершения программы - они будут ссылаться друг на друга и их счётчики не занулятся.

2.4.12.4. Примеры работы с OLE

Библиотеки компонентов ActiveX (*.ocx) имею исчерпывающую информацию о способах взаимодействия с OLE-объектами реализованными в данной библиотеке - TypeLibrary - ресурс самой библиотеки (OCX). Microsoft Windows SDK имеет инструменты для чтения библиотеки типов - “OleView”. Его использование может стать серьёзным подспорьем при разработке методов взаимодействия с тем или иным ActiveX компонентом.

На корпоративном FTP есть локальная копия Windows SDK, пакет непосредственно содержащий “OleView”

2.4.12.4.1. Работа с компонентами OPOS (OLE POS)

Скачать дистрибутив ActiveX компонентов OPOS

Документация OPOS (В современности описывается стандартом UnifiedPOS)

Пример создания/инициализации OPOS-устройства

function OPOS_OpenNewDevice(DeviceClass: String; DeviceName: String; Exclusive: Boolean): Variant;
 begin
   device := CreateOleObject('OPOS.' + DeviceClass);
   if device.Open(DeviceName) <> 0 then
     Raise('OPOS.' + DeviceClass + '.Open error ' + IntToStr(device.OpenResult));

   if Exclusive and (device.ClaimDevice(1000) <> 0) then
   begin
     Device.Close;
     Raise('OPOS.' + DeviceClass + '.ClaimDevice error');
   end;

   device.DeviceEnabled := True;
   if not device.DeviceEnabled then
   begin
     Device.ReleaseDevice;
     Device.Close;
     if not Exclusive then
       Raise('device not Enabled. May Exclusive needed.')
     else
       Raise('device not Enabled');
   end;
   Result := device;
 end;
device := OPOS_OpenNewDevice('Scanner', 'ScannerName', True);
  • Имена классов OPOS устройств можно наблюдать среди зарегистрированных в системе утилитой “OleView”

  • Имена устройств(и настройки) хранятся по пути реестра “HKEY_LOCAL_MACHINE\SOFTWARE\OLEForRetail\ServiceOPOS\”

    ключ ServiceOPOS содержит классы зарегистрированных в системе устройств, такие как “Scanner”. ключ конкретного типа устройств содержит ключи с Именами зарегистрированных устройств В приведённом примере подключается устройство описанное в ключе “HKEY_LOCAL_MACHINE\SOFTWARE\OLEForRetail\ServiceOPOS\Scanner\ScannerName”

Пример завершения работы с OPOS-устройством

procedure CloseDevice(Device: Variant);
 begin
   Device.DeviceEnabled := False;
   Device.ReleaseDevice;
   Device.Close;
 end;

Пример работы со сканером штрихкодов

Операция OpenScanner
  <PASCAL ScannerDeviceName>
    var events: TbtkScriptComEvents;
    begin
      Selection.ExecOpScript('CloseScanner');

      device := OPOS_OpenNewDevice('Scanner', ScannerDeviceName, True);
      events := TbtkScriptComEvents.Create('{CCB90183-B81E-11D2-AB74-0040054C3719}');
      events.Connect(device, Selection.OperationByName('OnScanner'));
      if not events.Connected then
      begin
        CloseDevice(device);
        Raise('Сканер отверг обработчик событий');
      end;

      device.DecodeData := True;
      device.DataEventEnabled := True;

      if Selection.VarExists('StaticScannerVariableName') then
        Selection.SetVar('StaticScannerVariableName', events)
      else
        Selection.AddVar('StaticScannerVariableName', events, ftVariant);
    end;
  </PASCAL>
Операция CloseScanner
  <PASCAL>
    var events: TbtkScriptComEvents;
    begin
      events := Selection.GetVar('StaticScannerVariableName')
      if VarIsNull(events) then
        Exit;

      CloseDevice(events.ComObject);
      events.Disconnect;
      Selection.GetVar('StaticScannerVariableName', Null);
    end;
  </PASCAL>
Операция OnUnloadMeta <PASCAL> Selection.ExecOpScript(‘CloseScanner’); </PASCAL>

Операция OnScanner <PASCAL ASender, ADispID, AParams> function ScanDataType2String(AType: Integer): String; begin … end;
    begin
      try
        case ADispID of
          1:
          begin
            ShowMessage('Данные сканирования:
            '+ ScanDataType2String(ASender.ScanDataType)+'
            '+ ASender.ScanDataLabel);
          end;
      finally
        ASender.DataEventEnabled := True;
      end;
    end;
  </PASCAL>

function ScanDataType2String(AType: Integer): String;

begin
  case AType of
    101: Result := 'Digits';
    102: Result := 'Digits';
    103: Result := 'EAN 8';
    104: Result := 'EAN 13';
    105: Result := 'Discrete 2 of 5) Digits';
    106: Result := 'Interleaved 2 of 5) Digits';
    107: Result := 'Digits, -, $, :, /, ., +; 4 start/stop characters (a, b, c, d)';
    108: Result := 'Full ASCII feature';
    109: Result := 'Same characters as Code 39';
    110: Result := '128 data characters';
    111: Result := 'UPC-A with supplemental barcode';
    112: Result := 'UPC-E with supplemental barcode';
    113: Result := 'UPC-D1';
    114: Result := 'UPC-D2';
    115: Result := 'UPC-D3';
    116: Result := 'UPC-D4';
    117: Result := 'UPC-D5';
    118: Result := 'EAN 8 with supplemental barcode';
    119: Result := 'EAN 13 with supplemental barcode';
    120: Result := 'EAN 128';
    121: Result := 'OCR "A"';
    122: Result := 'OCR "B"';
    131: Result := 'Reduced Space Symbology - 14 digit GTIN';
    132: Result := 'RSS - 14 digit GTIN plus additional fields';
    131: Result := 'GS1 DataBar Omnidirectional (normal or stacked)';
    132: Result := 'GS1 DataBar Expanded (normal or stacked)';
    133: Result := 'Interleaved 2 of 5 check digit verified and transmitted';
    134: Result := 'GS1 DataBar Limited';
    135: Result := 'Ames Code';
    136: Result := 'Matrix 2 of 5';
    137: Result := 'Code 39 with check character verified and transmitted';
    138: Result := 'Code 39 with Mod 32 check character';
    139: Result := 'Code 39 CIP';
    140: Result := 'Tri-Optic Code 39';
    141: Result := 'ISBT-128';
    142: Result := 'Code 11';
    143: Result := 'MSI Code';
    144: Result := 'Plessey Code';
    145: Result := 'Telepen';
    151: Result := 'Composite Component A.';
    152: Result := 'Composite Component B.';
    153: Result := 'Composite Component C.';
    154: Result := 'TLC-39';
    201: Result := 'PDF 417';
    202: Result := 'Maxicode';
    203: Result := 'Data Matrix';
    204: Result := 'QR Code';
    205: Result := 'Micro QR Code';
    206: Result := 'Aztec';
    207: Result := 'Micro PDF 417';
    208: Result := 'GS1 DataMatrix';
    209: Result := 'GS1 QR Code';
    210: Result := 'Code 49';
    211: Result := 'Code 16K';
    212: Result := 'Codablock A';
    213: Result := 'Codablock F';
    214: Result := 'Codablock 256';
    215: Result := 'Han Xin Code';
  end else
    Result := 'Unknown(' + IntToStr(AType) + ')';
end;