如何在C#中枚举COM对象的成员?

Ale*_*zin 5 .net c# com

我通过COM连接到某个程序并接收System .__ ComObject.我知道它的几种方法,所以我可以这样做:

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

并且喜欢这个

dynamic dyn = obj;
dyn.SomeMethod("Some string");
Run Code Online (Sandbox Code Playgroud)

两种方法都很好.但是如何确定com对象的内部类型信息并通过其所有成员进行枚举?

我试过这个:

[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);
Run Code Online (Sandbox Code Playgroud)

但是t最后是空的.谁能帮我?

Bil*_*ees 20

我刚刚发布了一篇关于如何使用基于IDispatch的COM对象进行反射的CodeProject文章.本文提供了一个DispatchUtility很容易包含在其他项目中的小型C#帮助程序类.在内部,它使用IDispatch的自定义声明和.NET的TypeToTypeInfoMarshaler将IDispatch的ITypeInfo转换为丰富的.NET Type实例.

在您的示例中,您可以调用DispatchUtility.GetType(obj, true)以获取.NET Type实例,然后可以在其上调用GetMembers.

FWIW,DispatchUtilityIDispatch.GetTypeInfo的声明几乎与你的相同.但是,在调用GetTypeInfo时,它会传入LOCALE_SYSTEM_DEFAULT(2048)而不是0来传递lcid参数.也许GetTypeInfo为您的disp.GetTypeInfo(0, 0, out t)呼叫返回了一个失败的HRESULT .由于您使用了它[PreserveSig],因此您需要检查其结果(例如,通过调用Marshal.ThrowExceptionForHR).

这是DispatchUtility删除了大多数注释的类的一个版本:

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.
    }
}
Run Code Online (Sandbox Code Playgroud)


Han*_*ant 5

您无法获得COM对象的类型.这将要求您为COM组件创建互操作库.如果COM服务器具有类型库,只需添加对它的引用或运行Tlbimp.exe实用程序,这当然是一个低难点.如果它存在,那么类型库通常嵌入在DLL中.当你得到它时,编辑器和对象浏览器都可以更加智能地了解COM类上可用的方法和属性.

看到IDispatch强制转换工作,很可能也可以使用类型库.COM服务器作者创建一个是非常简单的.您可以用来查看类型库的另一个工具是OleView.exe,View + Typelib.

如果这不起作用,那么你确实可以从IDispatch中挖出东西.您的声明看起来很可疑,IDispatch :: GetTypeInfo的第三个参数是ITypeInfo,一个COM接口.无需自定义编组程序,System.Runtime.InteropServices.ComTypes命名空间中提供了ITypeInfo.您可以使用Reflector从框架代码中挖掘出IDispatch声明.

当然,没有什么可以替代体面的文档.当您获得使用此组件的许可时,您应该能够获得一些.

  • @HansPassant我在这里讨论了可疑的第三个参数:https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects (2认同)