Title: for...in...do on COM Collections
Question: Delphi 2005 finally let's us work with the simple for...in...do loop construct, however it does not support COM enumerations. This is an introduction to the for..in...do extension and how to provide support for any COM Collection.
Answer:
Note: this article is valid for the Delphi.Win32 personality of Delphi 2005, not the Delphi.NET personality!
Delphi 2005 finally introduced the long missing for...in...do construct.
A short introduction to the new construct can be found at Danny Thorpe's Blog, look for the entry New For Loop Syntax. Therefore I will not elaborate in the Syntax itself, but on how to provide support for third party collections where you do not have access to the sources.
Where trouble starts
Delphi's new for loop syntax misses support for COM Enumerations (IEnumVariant), on of the worst to implement collections under Delphi. However, there is a rather simple solution to it that can be used on any COM Collection that is provided through the IEnumVariant interface. This will affect more than 95% of those available. For others the code can be adjusted easily.
How does Delphi do for...in loops
Before going to provide the code needed for COM Collections I want to first introduce you to the inner workings of Delphi when using the for...in loop syntax with objects.
Providing you use a simple string list, let's first take a look at how to use the syntax.
procedure TForm1.Button1Click(Sender: TObject);
var
S: String;
Strings: TStringList;
begin
Strings := TStringList.Create;
try
Strings.Add('1');
Strings.Add('2');
Strings.Add('3');
for S in Strings do
ShowMessage(S);
finally
Strings.Free;
end;
end;
After filling the StringList with some strings, we simply iterate it using the for...in loop syntax. But how does that work? Following two lines we have to inspect a little more:
for S in Strings do
ShowMessage(S);
Those two lines will internally be expanded to something similar the following:
StringEnum := Strings.GetEnumerator;
try
while StringEnum.MoveNext do
begin
S := StringEnum.Current;
ShowMessage(S);
end;
finally
StringEnum.Free;
end;
First Delphi will get an Enumerator object that will iterate the string list. As long as the enumerator object can move on, Delphi will assign the current value to our variable "S." Now, we can access "S."
Note: S will be read-only within the Delphi for...in loop, it is however, not read-only in the sample that explains the Delphi logic.
So, Delphi will create a new object to enumerate the class. Next it will create a try...finally section to ensure that the object is freed after iterating all items. Therefore an enumerator can be used once only for one iteration.
Providing an enumerator for COM Collections
When creating the enumerator for COM Collections I took advantage of the fact that Delphi frees the object automatically. However, since we cannot easily extend existing COM interfaces I created a workaround. A "helper function" named GetCOMEnumerator() will create the enumerator object which in return will be passed to the for..in loop. The enumerator object implements the method GetEnumerator which will be called by Delphi and simply returns itself. Now Delphi can go ahead an iterate the COM Collection in a simple for...in loop.
The MoveNext method of the enumerator is implemented as follows:
function TComEnumerator.MoveNext: Boolean;
var
OleCurrent: OleVariant;
Fetched: Cardinal;
begin
if FEnum nil then
begin
// fetch the next element from the collection list
FEnum.Next(1, OleCurrent, Fetched);
if Fetched = 1 then
begin
// another object was fetched
FCurrent := OleCurrent;
Result := True;
end
else
begin
// no more objects in enumaration
Result := False;
end;
end
else
begin
Result := False;
end;
end;
To use the COM enumerator you can easily implement it as follows:
// iterate all drives
for Drive in GetCOMEnumerator(Drives._NewEnum) do
// write the drive letter to the console
WriteLn((Drive as IDrive).DriveLetter);
The whole project can be downloaded at Borland's CodeCentral, entry ID: 22263
Regards and have fun,
Daniel Wischnewski