//
you're reading...
.NET/COM/ActiveX Interop

Exposing an Enumerator from Managed Code to COM.

1. Introduction.

1.1 An enumerator allows iterations over an unordered collection of objects of a specific type.

1.2 In COM, enumerators are expressed as IEnumXXX interfaces (e.g. IEnumUnknown, IEnumBstr, IEnumVARIANT, etc).

1.3 This blog presents a simple way to expose an enumerator from managed code to COM.

2. Exposing an Enumerator to COM from a Managed Class.

2.1 To expose an enumerator to COM from a managed class, the class must, at minimum, expose a method with the following signature :

IEnumerator GetEnumerator();

This is sufficient to produce a COM interface method with the following signature :

HRESULT _stdcall GetEnumerator([out, retval] IEnumVARIANT** pRetVal);

2.2 However, in order for a COM class to be deemed enumerable (by COM), its GetEnumerator() method must be marked with a dispatch id of -4. By being deemed as enumerable by COM, we mean that it is able to participate in enumeration programming constructs like VB6.0’s For Each Next statement (see an example in point 4.3).

2.3 Now having a dispatch id by itself implies that the interface which contains the GetEnumerator() method must be IDispatch-based. For this reason, the managed class (from which the COM class is derived), must be attributed with the  ClassInterfaceAttribute with ClassInterfaceType.AutoDual or ClassInterfaceType.AutoDispatch as the argument. The following is an example :

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class MyEnumerableClass
{
    public IEnumerator GetEnumerator()
    {
      ...
    }
}

The above code will produce the following IDL definition for the MyEnumerableClass class’ class interface _MyEnumerableClass :

    [
      ...
    ]
    interface _MyEnumerableClass : IDispatch
    {
        ...
        [id(0xfffffffc)]
        HRESULT GetEnumerator([out, retval] IEnumVARIANT** pRetVal);
    };

Notice that the GetEnumerator() is given the dispatch ID of 0xfffffffc which is -4. This dispid is set automatically by the Type Library Exporter.

2.4 Alternatively, the managed class could also have been derived from an interface that exposes a GetEnumerator() method that returns IEnumerator. In this case, the interface must be marked with the InterfaceTypeAttribute with either ComInterfaceType.InterfaceIsDual or ComInterfaceType.InterfaceIsIDispatch set as the argument, e.g. :

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IEnumerableClass
{
    IEnumerator GetEnumerator();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MyEnumerableClass : IEnumerableClass
{
    public IEnumerator GetEnumerator()
    {
      ...
    }
}

The above code will produce the following IDL interface definition for IEnumerableClass :

    [
      ...
    ]
    interface IEnumerableClass : IDispatch
    {
        [id(0xfffffffc)]
        HRESULT GetEnumerator([out, retval] IEnumVARIANT** pRetVal);
    };

The dispid value of -4 is again set by the Type Library Exporter automatically.

2.5 Yet another way to expose a COM-compatible enumerator is to manually mark the class’ or interface’s GetEnumerator() method with the DispIdAttribute with a value of -4, e.g. :

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class MyEnumerableClass
{
    [DispId(-4)]
    public IEnumerator GetEnumerator()
    {
      ...
    }
}

However, this technique usually applies to GetEnumerator() methods that return something other than IEnumerator, e.g. a strongly-typed IEnumerator interface. Manual setting of the dispid is not necessary for a GetEnumerator() method that returns IEnumerator. This is done automatically by the Type Library Exporter.

2.6 A final approach is to derive the class or the interface itself from the IEnumerable interface, e.g. :

[ComVisible(true)]
public class MyEnumerableClass : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
      ...
    }
}

This would make the MyEnumerableClass COM class expose the IEnumerable interface in COM. The IEnumerable COM interface is listed below (from mscorlib.tlb) :

    [
      odl,
      uuid(496B0ABE-CDEE-11D3-88E8-00902754C43A),
      version(1.0),
      dual,
      oleautomation,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "System.Collections.IEnumerable")    

    ]
    interface IEnumerable : IDispatch {
        [id(0xfffffffc)]
        HRESULT GetEnumerator([out, retval] IEnumVARIANT** pRetVal);
    };

Note that the IEnumerable::GetEnumerator() method is already marked with dispid -4. Being derived from IEnumerable, the MyEnumerableClass’ GetEnumerator() method will be marked with dispid -4.

2.7 Note that in all cases above, the return type in COM (for the .NET IEnumerator interface) is IEnumVARIANT. This is COM’s equivalent of IEnumerator.

3. Sample Class that Returns an Enumerator.

3.1 The approach that I recommend is for the managed class (to be exposed to COM) to derive from a COM-visible interface that derives from IEnumerable. There are 2 advantages here :

  • The fact that the interface is meant to be enumerable is clearly documented.
  • With the use of a COM-visible interface, the class need not expose any class interface (class interfaces are volatile and are subject to change whenever the class’ methods and properties change).

3.2 The following is a full listing of a C# class library project code that contains such a sample class (MyEnumerableClass) :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MyEnumeratorClassLib
{
    [ComVisible(true)]
    [Guid("FFAF4B74-31E5-4b11-AFBE-51A7BD3639E2")]
    // The interface type should be either :
    // ComInterfaceType.InterfaceIsDual
    // or ComInterfaceType.InterfaceIsIDispatch
    // otherwise the GetEnumerator() method
    // will not be marked with dispid -4.
    // Without this dispid, this method
    // will not be recognized by COM as
    // returning an enumerator. It will not thus
    // not be usable in a VB6.0 For Each Next
    // statement.
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IEnumerableClass : IEnumerable
    {
        new IEnumerator GetEnumerator();
    }

    [ComVisible(true)]
    [Guid("95859D60-C971-4ad7-9BA7-AC49D699DFEC")]
    [ProgId("MyEnumeratorClassLib.MyEnumerableClass")]
    [ClassInterface(ClassInterfaceType.None)]
    public class MyEnumerableClass : IEnumerableClass
    {
        public IEnumerator GetEnumerator()
        {
            List<string> name_list = new List<string>();

            name_list.Add("John Lennon");
            name_list.Add("Paul McCartney");
            name_list.Add("George Harrison");
            name_list.Add("Ringo Starr");

            return name_list.GetEnumerator();
        }
    }
}

Here are some pertinent points about the MyEnumerableClass.GetEnumerator() method :

  • It instantiates a List<T> class with the “string” type being the type argument.
  • This creates a list of strings.
  • After building up the list of strings, it uses List<string> class to great effect by setting the return value of the GetEnumerator() method of the List<string> class as its own return value.

The class library will compile into MyEnumeratorClassLib.dll.

4. Sample Client.

4.1 I shall demonstrate the use of MyEnumerableClass from inside a VB6.0 client application.

4.2 When the MyEnumeratorClassLib.dll is referenced inside a VB6.0 client project, the following can be viewed from the Object Borwser :

4.3 The following is a sample client code written in VB6.0 :

Option Explicit
Option Base 0
Dim MyEnumerableClassObj As MyEnumerableClass

Private Sub Form_Load()
  Set MyEnumerableClassObj = New MyEnumerableClass
  Dim v As Variant

  For Each v In MyEnumerableClassObj
    Dim str As String

    str = v

    MsgBox str
  Next
End Sub

The following is a summary of the VB6.0 code :

  • An instance of the MyEnumerableClass is created (MyEnumerableClassObj).
  • Now, the MyEnumerableClass is declared to be enumerable.
  • This means that an instance of this class can intrinsically be treated as if it is a collection of some sort.
  • We know, of course, that this collection contains strings.
  • A For Each Next statement is used to enumerate the collection of the MyEnumerableClass instance.
  • On each iteration, a Variant is returned and is stored in “v”.
  • We know that each “v” contains a string and we thus convert it into a string and display it.

5. In Conclusion.

5.1 I hope the reader has benefitted from this discussion and noted how easy it is to return collections to COM.

5.2 The various generic collection classes of the System.Collections.Generic namespace, e.g. List<T>, HashSet<T>, LinkedList<T>, Stack<T> etc all have ready implementations of IEnumerable<T>.

5.3 We can take great advantage of this by using a choice collection class to fill up objects and then return a ready to use IEnumerator implementation by calling the GetEnumerator() function of the collection class.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

One thought on “Exposing an Enumerator from Managed Code to COM.

  1. This was an awesome post and helped me out a bunch with understanding how COM works for this stuff. Thanks!

    Posted by Tridus | July 18, 2016, 6:21 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: