简体   繁体   中英

How to enumerate members of COM object in C#?

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?

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 (eg, 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.
    }
}

You cannot get a Type for the COM object. That would require you creating an interop library for the COM component. Which is certainly the low pain point if the COM server has a type library, just add a reference to it or run the Tlbimp.exe utility. If it is present then the type library is usually embedded inside the DLL. When you got that, both the editor and the Object Browser get a lot smarter about the method and properties available on the COM class.

Seeing the IDispatch cast work makes it quite likely that a type library is available as well. It is pretty trivial for the COM server author to create one. Another tool you can use to peek at the type library is OleView.exe, View + Typelib.

If that doesn't work then you can indeed dig stuff out of IDispatch. Your declaration looks fishy, the 3rd argument for IDispatch::GetTypeInfo is ITypeInfo, a COM interface. No need for a custom marshaller, ITypeInfo is available in the System.Runtime.InteropServices.ComTypes namespace. You can dig the IDispatch declaration out of the framework code with Reflector.

And of course, there's no substitute for decent documentation. You should be able to get some when you got a license to use this component.

You can use: http://www.nektra.com/products/deviare-api-hook-windows/

It has a complete API provided as COM objects that can be used to get information and functions of all registered COM objects and intercept them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM