Интерфейсы и плагины

Как можно догадаться, это своеобразная реализация метода пузырька, несмотря на то, что модуль называется CSort. Так получилось, а переименовывать достаточно тяжело.И тут же возникает мысль, а дальше? Ну сделаем мы еще пару классов Sort, но понадобится же их как-то опознавать. А при подключении библиотеки можно было видеть, сколько всего есть библиотек типов в системе, не перебирать же их. Разумеется, способ выделить свои классы есть, и называется он Component Categories, то есть категории компонентов. Фактически, это просто способ записи группы интерфейсов в реестр, для которого есть встроенный механизм. И, разумеется, этот механизм основан на интерфейсах. Остается его только задействовать, и лучше всего регистрировать класс в категории одновременно с регистрацией сервера. На самом деле, это даже проще: при регистрации сервера ActiveX в системе вызываются методы UpdateRegistry всех имеющихся фабрик классов. Фабрика, разумеется, у нас создается, обратите внимание на секцию initialization выше. Нужно создать потомок этой фабрики, перекрыв метод UpdateRegistry:

unit SortFactory;
interface
uses COMObj;
type
 TSortAutoObjectFactory = class(TAutoObjectFactory)
 public
  procedure UpdateRegistry(Register: boolean); override;
 end;
implementation
uses ActiveX, SysUtils, SortConsts;
{ TSortAutoObjectFactory }
procedure TSortAutoObjectFactory.UpdateRegistry(Register: boolean);
var
 CatReg: ICatRegister;
 CatInfo: TCATEGORYINFO;
begin
 inherited;
 OleCheck(CoCreateInstance(CLSID_StdComponentCategoryMgr, nil,
  CLSCTX_INPROC_SERVER, ICatRegister, CatReg));
 if Register then
 begin
  CatInfo.catid := CATID_SortServer;
  CatInfo.lcid := $0419;
  StringToWideChar(Sort_CatDesc, CatInfo.szDescription,
  Length(Sort_CatDesc) + 1);
  OleCheck(CatReg.RegisterCategories(1, @CatInfo));
  OleCheck(CatReg.RegisterClassImplCategories(ClassID, 1, @CATID_SortServer));
 end else
 begin
  OleCheck(CatReg.UnRegisterClassImplCategories(ClassID, 1, @CATID_SortServer));
  DeleteRegKey(Format('CLSID\%s\Implemented Categories', [GUIDToString(ClassID)]));
 end;
end;
end.
Разумеется, категория тоже распознается по ее GUID, но кроме этого у нее есть и описание на нормальном языке. Обе этих константы я вынес в отдельный файл, по той причине, что все приложения должны знать, в какой категории искать классы сортировки:
unit SortConsts;
interface
const
 CATID_SortServer: TGUID = '{782D841E-BB21-11DA-B666-00508B0973BE}';
 Sort_CatDesc = 'Библиотеки сортировки. Тестовый пример';
implementation
end.
Теперь остается подменить тип библиотеки в модуле CSort:
initialization
 TSortAutoObjectFactory.Create(ComServer, TSort, Class_Sort,
  ciMultiInstance, tmApartment);
end.
Вот и все. Вторая библиотека, SelSortLib, создана аналогично, я покажу только метод Sort:
procedure TSort.Sort(const Vector: IVector);
var
 I, J: Integer;
begin
 for I := 0 to Vector.Count - 2 do
  for J := Vector.Count - 1 downto I + 1 do
  if Vector.Compare(I,J) = crGreater then
  Vector.Exchange(I,J);
end;
Если честно, третью мне создавать уже как-то и не хочется, тем более что для иллюстрации материала вполне достаточно. Перейдем сразу к тестовому приложению. Создаем обычное приложение с формой, и делаем Import Type library, выбрав из списка SortIntf... Хотя погодите. Для окончательной иллюстрации мне еще хочется реализовать вектор действительных чисел. Сделаю это я прямо в этом приложении. Впрочем, импорт ничему не мешает, сделал так сделал. Выбираем опять создание Automation Object, прямо в приложении, подключаем SortIntf, и реализуем IVector. Вот только создан-то объект, к которому можно обратиться снаружи, а мне это не нужно. Ничего, как говорится, нет Factory - нет кокласса (я немного переиначил). Но отключить ее не совсем правильно, нужно сделать еще пару действий: во-первых, поменять тип кокласса на TAutoIntfObject. Это класс, который реализует интерфейсы - потомки IDispatch (у нас именно такие), но при этом у него нет фабрики, то есть, кокласс должен создаваться непосредственно внутри приложения. Во-вторых, как ни странно, убрать type library с коклассом и все воспоминания о ней, то есть использование COMServ и использование модуля импорта библиотеки. И, разумеется, убрать строчку {$R *.tlb} из проекта. Этого достаточно. Теперь есть класс вектора, который недоступен снаружи, но полностью доступен самому приложению. Вот какой кокласс у меня получился:
unit CDVector;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
 ComObj, ActiveX, StdVcl, SortIntf_TLB;
type
 TDVector = class(TAutoIntfObject, IVector)
 private
  FArray: array of Double;
 protected
  function Compare(Index1, Index2: Integer): CompareResult; safecall;
  function Get_Count: Integer; safecall;
  function Get_Elem(Index: Integer): OleVariant; safecall;
  procedure Exchange(Index1, Index2: Integer); safecall;
  procedure Set_Count(Value: Integer); safecall;
  procedure Set_Elem(Index: Integer; Value: OleVariant); safecall;
  { Protected declarations }
 public
  procedure FillRandom(Size: integer);
 end;
implementation
function TDVector.Compare(Index1, Index2: Integer): CompareResult;
begin
 Result := crLower;
 if FArray[Index1] > FArray[Index2] then
  Result := crGreater;
 if FArray[Index1] = FArray[Index2] then
  Result := crEqual;
end;
function TDVector.Get_Count: Integer;
begin
 Result := Length(FArray);
end;
function TDVector.Get_Elem(Index: Integer): OleVariant;
begin
 Result := FArray[Index];
end;
procedure TDVector.Exchange(Index1, Index2: Integer);
var
 T: Double;
begin
 T := FArray[Index1];
 FArray[Index1] := FArray[Index2];
 FArray[Index2] := T;
end;
procedure TDVector.Set_Count(Value: Integer);
begin
 if Value <> Length(FArray) then
  SetLength(FArray, Value);
end;
procedure TDVector.Set_Elem(Index: Integer; Value: OleVariant);
begin
 FArray[Index] := Value;
end;
procedure TDVector.FillRandom(Size: integer);
var
 i: integer;
begin
 SetLength(FArray, Size);
 for i := Low(FArray) to High(FArray) do
  FArray[i] := Random;
end;
end.
Я просто скопировал все методы из CVector, поменяв пару слов. Кроме того, мне показалось скучным полностью повторять реализацию, объект-то доступен. Поэтому я добавил метод, который заполняет массив случайными числами, для иллюстрации того, что кокласс все же является обычным классом Delphi.Осталось сделать простейший интерфейс:clip0042 Это - форма уже работающего приложения. А вот ее код:
unit FSort;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, Spin, SortIntf_TLB;
type
 TForm1 = class(TForm)
  Memo1: TMemo;
  ListBox1: TListBox;
  btnInt: TButton;
  btnDouble: TButton;
  SpinEdit1: TSpinEdit;
  procedure FormCreate(Sender: TObject);
  procedure btnDoubleClick(Sender: TObject);
  procedure btnIntClick(Sender: TObject);
 private
  procedure ShowSort(Vector: IVector);
  { Private declarations }
 public
  { Public declarations }
 end;
var
 Form1: TForm1;
implementation
uses SortConsts, CDVector, COMObj, ActiveX, VectorLib_TLB;
procedure ListSortServers(List: TStrings);
var
 EnumGUID: IEnumGUID;
 Fetched: Cardinal;
 Guid: TGUID;
 CatInfo: ICatInformation;
begin
 List.Clear;
 OleCheck(CoCreateInstance(CLSID_StdComponentCategoryMgr, nil,
  CLSCTX_INPROC_SERVER, ICatInformation, CatInfo));
 OleCheck(CatInfo.EnumClassesOfCategories(1, @CATID_SortServer, 0, nil, EnumGUID));
 while EnumGUID.(1, Guid, Fetched) = S_OK do
 begin
  //Переводим CLSID в более понятный вид
  List.Add(ClassIDToProgID(Guid));
 end;
end;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
 ListSortServers(ListBox1.Items);
 if ListBox1.Items.Count > 0 then
  ListBox1.ItemIndex := 0;
end;
procedure TForm1.btnDoubleClick(Sender: TObject);
var
 TypeLib: ITypeLib;
 Vector: IVector;
 VecObj: TDVector;
 i: integer;
begin
 //Нужна библиотека типов, в которой описан IVector
 OleCheck(LoadRegTypeLib(LIBID_SortIntf, 1, 0, 0, TypeLib));
 VecObj := TDVector.Create(TypeLib, IVector);
 VecObj.FillRandom(SpinEdit1.Value);
 Vector := VecObj as IVector;
 VecObj := nil; //От греха
 ShowSort(Vector);
end;
procedure TForm1.btnIntClick(Sender: TObject);
var
 Vector: IVector;
 i: integer;
begin
 Vector := CoVector.Create;
 Vector.Count := SpinEdit1.Value;
 for i := 0 to Vector.Count - 1 do
  Vector.Elem[i] := Random(300);
 ShowSort(Vector);
end;
procedure TForm1.ShowSort(Vector: IVector);
var
 i: integer;
 SortObj: ISort;
 ProgID: string;
begin
 Memo1.Lines.Clear;
 for i := 0 to Vector.Count - 1 do
  Memo1.Lines.Add(FormatFloat('##0.#####', Vector.Elem[i]));
 //Создаем выбранный класс сортировки и сортируем
 ProgID := ListBox1.Items[ListBox1.ItemIndex];
 SortObj := CreateComObject(ProgIDToClassId(ProgID)) as ISort;
 SortObj.Sort(Vector);
 Memo1.Lines.Add('Sorted');
 for i := 0 to Vector.Count - 1 do
  Memo1.Lines.Add(FormatFloat('##0.#####', Vector.Elem[i]));
end;
end.
Здесь, думаю, необходимы пояснения, и довольно подробные. ListSortServers перечисляет все классы, зарегистрированные в нашей категории, и записывает не непонятную строку CLSID, а более-менее воспринимаемую строку ProgID. Это именно та строка, которая подается в функцию CreateOleObject. Да-да, все интерфейсы у нас дуальные, так что вполне возможно позднее связывание, без всякого импорта библиотеки типов. Конечно, при этом вызовы методов идут гораздо дольше, зато не надо делать импорт библиотеки типов. Здесь же я импортировал и подключил библиотеку VectorLib, для того чтобы создать и использовать вектор целых чисел.Вектор же действительных чисел я получаю прямо из объекта, причем достаточно непривычным образом. Рассмотрим код подробно: //Нужна библиотека типов, в которой описан IVector OleCheck(LoadRegTypeLib(LIBID_SortIntf, 1, 0, 0, TypeLib)); VecObj := TDVector.Create(TypeLib, IVector); VecObj.FillRandom(SpinEdit1.Value); Vector := VecObj as IVector; VecObj := nil; //От греха Чтобы создать объект типа TAutoIntfObject, в его конструктор нужно подать ссылку на интерфейс нужной библиотеки типов и IID того интерфейса, который он должен реализовать. Обычно в качестве первого параметра достаточно подать COMServer.TypeLib, но в данном случае это не подходит: библиотеки типов нет, а когда она была, все равно там не было описания IVector. Поэтому нужно непосредственно подать интерфейс SortIntf.tlb. К счастью, она зарегистрирована, и нужно ее просто загрузить с получением ее интерфейса. Это и делается в первой строке. Во второй строке создается экземпляр кокласса (компилятор преобразует интерфейс к его IID во втором параметре), и далее вызывается метод класса для заполнения массива. Затем уже из самого класса получается интерфейс IVector. VecObj := nil я написал специально. Дело в том, что при доступе через интерфейс кокласс, как правило, сам следит за тем, когда ему уничтожится, точнее, этим управляет интерфейс. Поэтому пользоватся одновременно и экземпляром класса, и его интерфейсом нужно крайне аккуратно.Остальные методы практически мало отличаются от первого примера, пожалуй, единственное отличие в том, что clsid получается из идентификатора программы. Если применяется позднее связывание, этого можно и не делать, вот, например, как можно переписать работу с массивом:
procedure TForm1.btnInt2Click(Sender: TObject);
var
 V: variant;
 Sort: Variant;
 i: integer;
begin
 V := CreateOleObject('VectorLib.Vector');
 V.Count := SpinEdit1.Value;
 for i := 0 to V.Count - 1 do
  V.Elem[i] := Random(300);
 Memo1.Lines.Clear;
 for i := 0 to V.Count - 1 do
  Memo1.Lines.Add(FormatFloat('##0.#####', V.Elem[i]));
 Sort := CreateOleObject(ListBox1.Items[ListBox1.ItemIndex]);
 Sort.Sort(V);
 Memo1.Lines.Add('Sorted');
 for i := 0 to V.Count - 1 do
  Memo1.Lines.Add(FormatFloat('##0.#####', V.Elem[i]));
end;
Конечно, позднее связывание менее эффективно, но зато этот метод дает возможность использовать классы в языках программирования, которые не поддерживают раннего связывания, например, в VBScript. К тому же, не требуется импортировать библиотеку типов, что может быть весьма удобным, если нужно написать всего несколько строк с использованием интерфейса.ЗаключениеПодведем итоги. Я показал пару способов организовать приложение в виде отдельных специализированных частей, достаточно активно взаимодействующих друг с другом. Конечно, можно было реализовать эти классы более эффективно и гибко, но я думаю, что и написанного вполне достаточно для понимания общих принципов. Все остальное, при желании, можно найти в интернете. Я также не коснулся вопроса о том, как делать визуальные плагины на основе ActiveX Form, по той причине, что это предмет для особого разговора.Роман Игнатьев, 2006, romkin[at]pochta.ru Взято из http://forum.sources.ru Автор: Romkin

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...