Thursday, May 9, 2013

Delphi enumerator for IEnumVariant

IEnumVariant is the common way to enumerate collections in COM interfaces. However this interface is very inconvenient to use in Delphi. So I decided to write simple generic wrapper, to make possible to enumerate such collections in Delphi style, like this:

var
  I: ICollectionItem;
begin
  for I in EnumVariant do
    I.DoSomething();
end;

To do this we need two objects, first one returns our Enumerator:
  TOleEnumerable<T: IInterface> = record
  private
    FEnum: IUnknown;
  public
    constructor Create(ANewEnum: IUnknown);
    function GetEnumerator: IOleEnumerator<T>;
  end;

constructor TOleEnumerable<T>.Create(ANewEnum: IInterface);
begin
  FEnum := ANewEnum;
end;

function TOleEnumerable<T>.GetEnumerator: IOleEnumerator<T>;
begin
  Result := TOleEnumerator<T>.Create(FEnum);
end;


As you see, I made the GetEnumerator function returning interface, so that we don't need to free wrappers manually.

The second is our enumerator interface and its implementation:

  IOleEnumerator<T: IInterface> = interface
    function MoveNext: Boolean;
    function GetCurrent: T;
    procedure Reset;
    property Current: T read GetCurrent;
  end;

  TOleEnumerator<T: IInterface> = class(TInterfacedObject, IOleEnumerator<T>)
  private
    FEnum: IEnumVARIANT;
    FCurrent: T;
  public
    constructor Create(ANewEnum: IUnknown);
    function GetCurrent: T;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: T read GetCurrent;
  end;

constructor TOleEnumerator<T>.Create(ANewEnum: IInterface);
begin
  FCurrent := nil;
  if not Supports(ANewEnum, IEnumVariant, FEnum) then
    raise Exception.Create('Enumeration type not supported');
end;

function TOleEnumerator<T>.GetCurrent: T;
begin
  Result := FCurrent;
end;

function TOleEnumerator<T>.MoveNext: Boolean;
var
  OleVar: OleVariant;
  Num: Cardinal;
begin
  Result := FEnum.Next(1, OleVar, Num) = S_OK;
  if Result then
    IUnknown(OleVar).QueryInterface(GetTypeData(TypeInfo(T)).Guid, FCurrent);
end;

procedure TOleEnumerator<T>.Reset;
begin
  FEnum.Reset;
end;
Thats it! Now you could iterate your COM collections like this:
var
  Man: INetworkManager;
  I: INetworkConnection;
begin
  Man := CoNetworkManager.Create;
  for I in TOleEnumerable<INetworkConnection>.Create(Man._NewEnum) do
    I.DoSomething();
end;

How to query generic interface

Suppose you have an object, and would like to query interface from it, but interface is generic type T: IInterface. You could query an interface using TypeInfo:

  Obj.QueryInterface(GetTypeData(TypeInfo(T)).Guid, Result);