DELPHI FAQ: Pipeline Components.


Pipeline Components.
Previous  Home  Next


Эта область разработки возникла в моем текущем проекте. Pipeline components - это COM-объекты, которые выполняются в pipeline, который в свою очередь вызывается на выполнение обычно через ASP. Pipeline представляет собой цепочку pipeline component, выполняющихся последовательно один за одним. На вход pipeline передается объект IDictionary, который передается всем компонентам в цепочке. Результатом работы этих компонент может быть видоизмененный IDictionary, либо еще чего-нибудь.


Описание.

Pipeline компоненты должны поддерживать интерфейс IPipelineComponent, а также несколько других. Обо всех будет рассказано поподробнее ниже.

Представим себе, что мы хотим создать компонент, который сбрасывает содержимое IDictionary в xml-файл на диск. Причем мы хотим иметь возможность задавать имя этого файла в Properties Page внутри Pipeline Editor. Для ознакомления с Pipeline Editor советую обратиться на сайт Microsoft.

В первую очередь, для создания компонента в Delphi необходимо создать ActiveX Library. Для этого выполним команду File|New -> Activex tabsheet -> ActiveX Library. Затем там добавим Automation Object. Назовем объект DumpOrderToXml. Добавим методы SetXmlFilename и GetXmlFilename. Результатом должны быть следующие объявления:

function
 SetXmlFilename(XmlFileName: WideString): HResult [dispid $00000001]; stdcall;

function
 GetXmlFileName(retval XmlFileName: WideString): HResult [dispid $00000002]; stdcall;

Для дальнейшей успешной работы Вы должны иметь на диске следующие файлы: COMMERCELib_TLB.pas, MSCSAspHelpLib_TLB.pas, MSCSCoreLib_TLB.pas, PIPELINELib_TLB.pas. Их можно сгенерировать с помощью tipe library editor, предоставляемого Delphi, либо скачать у меня. Также необходимо иметь на диске ComPUtil.pas и PipeConsts.pas файлы, которые есть у меня.

Delphi поможет Вам создать макет модуля с классом TDumpOrderToXml. В объявление этого класса добавьте дополнительные интерфейсы и соответсвующие методы для их реализации:

type
  TDumpOrderToXml = class
(TAutoObject, IDumpOrderToXml, IPipelineComponent, ISpecifyPropertyPages, IPersistStreamInit)
  private

    FXmlFileName: WideString;
  protected

{ IDumpOrderToXml methods }

    function
 GetXmlFileName(out XmlFileName: WideString): HResult; stdcall;
    function
 SetXmlFilename(const XmlFileName: WideString): HResult; stdcall;
{ IPipelineComponent methods }

    function
 EnableDesign(fEnable: Integer): HResult; stdcall;
    function
 Execute(const pdispOrder, pdispContext: IDispatch;
      lFlags: Integer; out
 plErrorLevel: Integer): HResult; stdcall;
{ ISpecifyPropertyPages methods }

    function
 GetPages(out pages: TCAGUID): HResult; stdcall;
{ IPersistStreamInit methods }

    function
 GetClassID(out classID: TCLSID): HResult; stdcall;
    function
 IsDirty: HResult; stdcall;
    function
 Load(const stm: IStream): HResult; stdcall;
    function
 Save(const stm: IStream; fClearDirty: BOOL): HResult; stdcall;
    function
 GetSizeMax(out cbSize: Largeint): HResult; stdcall;
    function
 InitNew: HResult; stdcall;
  end
;

Интерфейс IDumpOrderToXml предоставляет нам возможность задавать и получать имя xml-файла для хранения на диске. Интерфейс IPipelineComponent - стержневой для класса, он позволяет запустить компонент на выполнение с помощью метода Execute. Интерфейс ISpecifyPropertyPage позволяет задать classid для Property Page нашего нового класса. Интерфейс IPersistStreamInit позволяет хранить введеные параметры с помощью Pipeline Editor в файле .pcf.

Приступим к реализации этих методов. Методы GetXmlFilename и SetXmlFilename достаточно просты - они просто читают (пишут) значение из (в) поле FXmlFileName. Метод EnableDesing вызывается для уведомления класса, что редактор переводит его в режим дизайна. В принципе крутые компоненты могут что-либо делать в этот момент. Нам это не нужно, поэтому просто вернем S_OK. Точно также поступим с методами InitNew и IsDirty. Это несущественные методы, которые в принципе можно реализовать более детально, но не для нас.

Методы Save и Load позволяют записать в поток наш параметр - имя xml-файла. В принципе ничего сложного в них нет, поэтому привожу код без комментариев

function TDumpOrderToXml.Save(const stm: IStream;
  fClearDirty: BOOL): HResult;
var
 OleStream: TOleStream;
  FileNameLen: Byte;
begin

  OleStream := TOleStream.Create(stm);
  try

    FileNameLen := Length(FXmlFileName);
    OleStream.Write
(FileNameLen, 1);
    OleStream.Write
(FXmlFileName[1], FileNameLen * Sizeof(WideChar));
  finally

    OleStream.Free;
  end
;
  Result := S_OK;
end
;

function
 TDumpOrderToXml.Load(const stm: IStream): HResult;
var
 OleStream: TOleStream;
  FileNameLen: Byte;
begin

  OleStream := TOleStream.Create(stm);
  try

    OleStream.Read
(FileNameLen, 1);
    SetLength(FXmlFileName, FileNameLen);
    OleStream.Read
(FXmlFileName[1], FileNameLen * Sizeof(WideChar));
  finally

    OleStream.Free;
  end
;
  Result := S_OK;
end
;

Метод GetClassID позволяет вернуть наш classid для внешнего потребителя. Ниже приведенное решение в принципе универсальное для любого класса.

function TDumpOrderToXml.GetClassID(out classID: TCLSID): HResult;
begin

  classID := Factory.ClassID;
  Result := S_OK;
end
;



Метод GetSizeMax возвращает размер, который наш класс хочет занять в потоке. Пусть это будет 255 widechar-ов.

function TDumpOrderToXml.GetSizeMax(out cbSize: Largeint): HResult;
begin

  cbSize := 255
 * sizeof(WideChar) + 1;
  Result := S_OK;
end
;

Теперь приступим к реализации метода Execute. В первую очередь нам необходимо получить ссылку на IDictionary из параметров метода. Для этого воcпользуемся функцией GetDictFromDispatch из модуля ComPUtil.pas. Затем вызовем функцию ExportDictionaryToXml, сохраним результат во временной строке, представляющей собой xml-текст и запишем эту строку в файл на диске.

function TDumpOrderToXml.Execute(const pdispOrder, pdispContext: IDispatch;
  lFlags: Integer; out
 plErrorLevel: Integer): HResult;
var

  hFile: Integer;
  tmpXML: WideString;
  Order: IDictionary;
  tmpOutXml: string
;
begin

  try

    tmpXML := ''
;
    if
 GetDictFromDispatch(pdispOrder, Order) = S_OK then
      begin

        ExportDictionaryToXML(Order, tmpXML);
        tmpXML := '<SO>'
 + tmpXML + '</SO>';
      end
;
    tmpOutXml := tmpXML;
    hFile := FileCreate(string
(FXmlFileName));
    FileWrite(hFile, tmpOutXml[1
], Length(tmpOutXML));
    FileClose(hFile);
  finally

    Result := S_OK;
    Order := nil
;
  end
;
end
;

Как видим, метод довольно несложный - вся нагрузка ложится на метод ExportDictionaryToXml. Рассмотрим его поподробнее. Как известно, dictionary представляет собой список именованных вариантов. Вариант сам по себе может быть IDictionary, ISimpleList или другой интерфейс. Для перечисления своих элементов dictionary поддерживает интерфейс IEnumVARIANT. Соотвественно, наша задача - взять IEnumVARIANT, пробежаться по его элементам и сохранить их имена и значение в строке.

Result := E_FAIL;
hr := InitKeyEnumInDict(Dict, Enum);
if
 hr = S_OK then
  begin


    repeat

      hr := GetNextKeyInDict(Enum, Key);
      if
 hr <> S_OK then Break;
      hr := GetDictValueVariant(Dict, LPCWSTR(Key), ItemValue);
      if
 hr <> S_OK then Break;

      case
 VarType(ItemValue) of
        ...
      else

        Break;
      end
;

    until
 hr <> S_OK;
  end
;

XmlStr := Res;
Result := S_OK;


Основное место в теле метода занимает оператор case. В нем определяются обычные значения варианта и сложные, такие как интерфейсы. Для обычных типов обработка будет такая:

Res := Res + Format('<%s>%s</%s>', [string(Key), string(ItemValue), string(Key)]);


Для типа varUnknown обработка будет еще проще. Понятно, что для более продвинутой информации эту обработку можно расширить:

Res := Res + Format('<%s>IUnknown</%s>',[string(Key), string(Key)]);

Наиболее сложная обработка для типа varDispatch. Здесь нам необходимо убедится, что элемент является либо IDictionary, либо ISimpleList. Для других случаев используем тоже самое, как для varUnknown:

if GetDictFromDispatch(ItemValue, NewDict) = S_OK then
  begin

    if
 ExportDictionaryToXML(NewDict, NewXml) = S_OK then
      begin

        Res := Res + Format('<%s type="Dictionary">%s</%s>'
,
          [string
(Key), string(NewXml), string(Key)]);
      end

    else

      begin

        Exit;
      end
;
  end

else
 if GetSimpleListFromDispatch(ItemValue, NewList) = S_OK then
  begin

    if
 ExportSimpleListToXML(NewList, NewXml) = S_OK then
      begin

        Res := Res + Format('<%s type="SimpleList">%s</%s>'
,
          [string
(Key), string(NewXml), string(Key)]);
      end

    else

      begin

        Exit;
      end
;
  end

else

  begin

    Res := Res + Format('<%s>IDispatch</%s>'
,
      [string
(Key), string(Key)]);
  end
;


Поскольку вариант может быть другим IDictionary, то в результате получим рекурсивный алгоритм. Замечу, что в случае ISimpleList вызывается еще один метод - ExportSimpleListToXml. Его реализация достаточно проста. Необходимо пробежаться по элементам списка, каждый из которых IDictionary, и вызывать ExportDictioanryToXml:

Result := E_FAIL;
hr := GetNumItems(List, Count);
if
 hr <> S_OK then Exit;

for
 I := 0 to Count - 1 do
  begin

    if
 GetNthItem(List, I, NewDict) = S_OK then
      begin

        if
 ExportDictionaryToXML(NewDict, NewXml) = S_OK then
          begin

            Res := Res + Format('<LISTITEM%d>'#13#10'%s</LISTITEM%d>'#13#10
,
              [I, string
(NewXml), I]);
          end

        else

          begin

            Exit;
          end
;
      end
;
  end
;

XmlStr := Res;
Result := S_OK;


Вот собственно и вся реализация метода Execute. Для полной красоты картины, нам необходимо научиться редактировать поле FXmlFilename в Pipeline редакторе. Для этого добавим в проект Property Page. На форму добавим из палитры Textbox, Label, Button и SaveDialog.


В обработчик нажатия кнопки добавим код по вызову SaveDialog:

if SaveDialog1.Execute then
  begin

    Edit1.Text := SaveDialog1.FileName;
  end
;


Для реализации поведения Property Page, мы должны реализовать два метода UpdatePropertyPage и UpdateObject. Первый метод восстанавливает значение из объекта в textbox. Второй, наоборот, записывает значение из textbox в объект.

procedure TDumpToXMLPropertyPage.UpdatePropertyPage;
var
 StrXmlFilename: WideString;
begin

{ Update your controls from OleObject }

  (OleObjects.First as
 IDumpOrderToXml).GetXmlFileName(StrXmlFilename);
  Edit1.Text := StrXmlFilename;
end
;

procedure
 TDumpToXMLPropertyPage.UpdateObject;
var
 StrXmlFilename: WideString;
begin

{ Update OleObject from your controls }

  StrXmlFilename := Edit1.Text;
  (OleObjects.First as
 IDumpOrderToXml).SetXmlFileName(StrXmlFilename);
end
;

Для того, чтобы Pipeline Editor знал, что у компонента есть дополнительные property-странички, необходимо реализовать метод GetPages у нашего класса.

function TDumpOrderToXml.GetPages(out pages: TCAGUID): HResult;
begin

  pages.cElems := 1
;
  pages.pElems := CoTaskMemAlloc(sizeof(TGUID));
  if
 pages.pElems = nil then
    begin

      Result := E_OUTOFMEMORY;
    end

  else

    begin

      pages.pElems^[0
] := Class_DumpToXMLPropertyPage;
      Result := S_OK;
    end
;
end
;


Этот метод занимается тем, что наполняет структуру, в которой хранятся все guid-ы наших property-страничек. В нашем случае это одна страничка - Class_DumpToXmlPropertyPage. Этот guid генерируется автоматически средой, когда мы создаем новую property page.

Теперь подошел черед модифицировать .dpr файл. В нем указывается экспортная функция DllRegisterServer, которую надо переделать:

function DllRegisterServer: HResult;
begin

  Result := ComServ.DllRegisterServer;
  if
 Result = S_OK then
    begin


{ Register DumpOrderToXml class }

      Result := RegisterCATID(CLASS
_DumpOrderToXml, CATID_MSCSPIPELINE_COMPONENT);
      if
 Result >= 0 then
        begin

          Result := RegisterCATID(CLASS
_DumpOrderToXml, CATID_MSCSPIPELINE_ANYSTAGE);
        end
;

{ Here you should register others pipeline components }

    end
;
end
;



В этой функции указывается, что надо зарегистрировать в системе pipeline component, и что этот компонент может принадлежать любому pipeline stage.

На этом разработка закончена. Осталось откомпилировать и зарегистриовать dll. Это можно сделать через командную строку: regsvr32 testpipelines.dll

Взято с Исходников.ru http://www.sources.ru






DELPHI FAQ




EOMY TOP 100      Рейтинг@Mail.ru      Rambler's Top100