Mega Code Archive

 
Categories / Delphi / COM
 

For...in...do on COM Collections

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