Exposing IEnumerables and events via IDispatch

No doubt in my mind. The world would be a better place without COM (sorry @donbox). Just spent some time relearning s the best practices for exposing an enumeration to IDispatch affected applications (like Dynamics NAV). One of the gotchas is .NET exposes the IEnumerable.GetEnumerator() as DispId(-4), that returns IEnumVARIANT, which doesn't implement IDispatch.

[ComVisible(true),  
    Guid("6BBEFFA7-4215-40B1-AC27-65C093C1D9A8"),
    InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IClientEnumerator  
{
    [DispId(0x01)]
    Client Current
    { get; }
    [DispId(0x02)]
    bool MoveNext();
    [DispId(0x03)]
    void Reset();
    [DispId(0x04)]
    int Count
    { get; }
}

[ComVisible(true),
    Guid("8008EE9E-5C2B-4F17-BD81-21B199550BD2"),
    ProgId("ClientEnumerator"),
    ClassInterface(ClassInterfaceType.None),
    ComDefaultInterface(typeof(IClientEnumerator))]
public class ClientEnumerator : IClientEnumerator, IEnumerator  
{
    IEnumerator<Client> m_e;
    Func<int> m_count;
    internal ClientEnumerator(IEnumerable<Client> e)
    {            
        m_e = e.GetEnumerator();
        m_count = () => e.Count();
    }
    public int Count
    {
        get { return m_count(); }
    }
    public bool MoveNext()
    {
        return m_e.MoveNext();
    }
    public void Reset()
    {
        m_e.Reset();
    }       
    public Client Current
    {
        get { return m_e.Current; }
    }
    object IEnumerator.Current
    {
        get { return this.Current; }
    }
}   

Also remember to put a GuidAttribute on the assembly scope. The AssemblyDescriptionAttribute is used for typelib title i.e. "MyTypelib 1.0 Type Library", and the assembly name set in project properties defines the type library name used for "typelibid.progid".

Events, or so called connection point, can be exposed by defining a dispatch interface like:

[ComVisible(true),  
    CLSCompliant(false),
    Guid("B9C6A999-CD39-4C8D-B072-639069CFA8B6"),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface _ClientsEvents  
{
    [DispId(0x01)]
    void AddCompleted(object sender, AsyncCompletedEventArgs e);
}

And adding a [ComSourceInterfaces(typeof(_ClientsEvents))] attribute to the class with an event which name and delegate corresponds exactly to each method in the interface.

Note that generic types cannot be exported, and ALL types that i marshalled (even if they are only exposed though an interface) must have ComVisible(true).

Google
m@kli.dk @klinkby RSS feed  GitHub