C# – How to enumerate members of COM object in C#

ccomnet

I connect to some program via COM and receive System.__ComObject. I know several methods of it, so I can do like this:

object result = obj.GetType().InvokeMember("SomeMethod", BindingFlags.InvokeMethod, null, obj, new object[] { "Some string" });

and like this

dynamic dyn = obj;
dyn.SomeMethod("Some string");

Both methods works fine. But how can I determine inner type information of com object and enumerate through all its members?

I tried this:

[ComImport, Guid("00020400-0000-0000-C000-000000000046"),
  InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDispatch
{
  void Reserved();
  [PreserveSig]
  int GetTypeInfo(uint nInfo, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))] out System.Type typeInfo);
}

...

IDispatch disp = (IDispatch)obj;
Type t;
disp.GetTypeInfo(0, 0, out t);

But the t is null at the end. Can anyone help me?

Best Answer

I've just published a CodeProject article about how to do Reflection with IDispatch-based COM objects. The article provides a small C# DispatchUtility helper class that's easy to include in other projects. Internally, it uses a custom declaration of IDispatch and .NET's TypeToTypeInfoMarshaler to convert IDispatch's ITypeInfo into a rich .NET Type instance.

In your example, you could call DispatchUtility.GetType(obj, true) to get back a .NET Type instance, which you could then call GetMembers on.

FWIW, DispatchUtility's declaration of IDispatch.GetTypeInfo is nearly identical to yours. However, when calling GetTypeInfo, it passes in LOCALE_SYSTEM_DEFAULT (2048) rather than 0 for the lcid parameter. Perhaps GetTypeInfo returned a failure HRESULT for your disp.GetTypeInfo(0, 0, out t) call. Since you declared it with [PreserveSig], you'd need to check its result (e.g., by calling Marshal.ThrowExceptionForHR).

Here's a version of the DispatchUtility class with most comments removed:

using System;
using System.Runtime.InteropServices;
using System.Reflection;

public static class DispatchUtility
{
    private const int S_OK = 0; //From WinError.h
    private const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800

    public static bool ImplementsIDispatch(object obj)
    {
        bool result = obj is IDispatchInfo;
        return result;
    }

    public static Type GetType(object obj, bool throwIfNotFound)
    {
        RequireReference(obj, "obj");
        Type result = GetType((IDispatchInfo)obj, throwIfNotFound);
        return result;
    }

    public static bool TryGetDispId(object obj, string name, out int dispId)
    {
        RequireReference(obj, "obj");
        bool result = TryGetDispId((IDispatchInfo)obj, name, out dispId);
        return result;
    }

    public static object Invoke(object obj, int dispId, object[] args)
    {
        string memberName = "[DispId=" + dispId + "]";
        object result = Invoke(obj, memberName, args);
        return result;
    }

    public static object Invoke(object obj, string memberName, object[] args)
    {
        RequireReference(obj, "obj");
        Type type = obj.GetType();
        object result = type.InvokeMember(memberName,
            BindingFlags.InvokeMethod | BindingFlags.GetProperty,
            null, obj, args, null);
        return result;
    }

    private static void RequireReference<T>(T value, string name) where T : class
    {
        if (value == null)
        {
            throw new ArgumentNullException(name);
        }
    }

    private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound)
    {
        RequireReference(dispatch, "dispatch");

        Type result = null;
        int typeInfoCount;
        int hr = dispatch.GetTypeInfoCount(out typeInfoCount);
        if (hr == S_OK && typeInfoCount > 0)
        {
            dispatch.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out result);
        }

        if (result == null && throwIfNotFound)
        {
            // If the GetTypeInfoCount called failed, throw an exception for that.
            Marshal.ThrowExceptionForHR(hr);

            // Otherwise, throw the same exception that Type.GetType would throw.
            throw new TypeLoadException();
        }

        return result;
    }

    private static bool TryGetDispId(IDispatchInfo dispatch, string name, out int dispId)
    {
        RequireReference(dispatch, "dispatch");
        RequireReference(name, "name");

        bool result = false;

        Guid iidNull = Guid.Empty;
        int hr = dispatch.GetDispId(ref iidNull, ref name, 1, LOCALE_SYSTEM_DEFAULT, out dispId);

        const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); //From WinError.h
        const int DISPID_UNKNOWN = -1; //From OAIdl.idl
        if (hr == S_OK)
        {
            result = true;
        }
        else if (hr == DISP_E_UNKNOWNNAME && dispId == DISPID_UNKNOWN)
        {
            result = false;
        }
        else
        {
            Marshal.ThrowExceptionForHR(hr);
        }

        return result;
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("00020400-0000-0000-C000-000000000046")]
    private interface IDispatchInfo
    {
        [PreserveSig]
        int GetTypeInfoCount(out int typeInfoCount);

        void GetTypeInfo(int typeInfoIndex, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler,
            MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))] out Type typeInfo);

        [PreserveSig]
        int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, out int dispId);

        // NOTE: The real IDispatch also has an Invoke method next, but we don't need it.
    }
}