2010-11-11 88 views
5

我通过COM连接到某个程序并接收System .__ ComObject。我知道它的几种方法,这样我就可以这样做:如何在C#中枚举COM对象的成员?

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

像这样

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

这两种方法都工作正常。但是,如何确定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); 

但t是空结尾。谁能帮我?

回答

5

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

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

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

当然,没有任何替代体面的文件。当您获得使用此组件的许可时,您应该可以获得一些。

+0

谢谢。事实上,这个组件是一个内置脚本语言的商业应用程序。其成员的完整列表在运行时确定。它没有类型库。 – 2010-11-13 06:45:31

+2

@HansPassant我碰到了这里的腥第三个参数的解释:https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2 2017-02-11 13:41:13

17

我刚刚发表了一篇关于如何做的CodeProject文章。本文提供了一个小巧的C#DispatchUtility辅助类,它很容易包含在其他项目中。在内部,它使用IDispatch和.NET的TypeToTypeInfoMarshaler的自定义声明将IDispatch的ITypeInfo转换为丰富的.NET Type实例。

在你的例子中,你可以调用DispatchUtility.GetType(obj, true)来取回一个.NET Type实例,然后你可以调用GetMembers。

FWIW,DispatchUtility对IDispatch.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. 
    } 
} 
+0

这CustomMarshaler是惊人的!我一直在构建托管的TypeInfo和TypeLibraryInfo类来封装ITypeInfo和ITypeLib。这用一个函数来代替那些整个大类,我调用GetCOMType。您可以执行COM对象所需的所有Invokes和GetValues。感谢您向我们展示这种令人敬畏的技术。 – Mike 2016-03-30 14:09:36

+0

+1真我已经调整了一点原始代码,但它的作用像魅力。当我得到Type t时,无论该类型是.Net类型还是__ComObject类型(即使TypeInfo仅在内存中存在),我都可以枚举该类型的所有成员。这应该被标记为正确的答案(而不是当我们明显可以的时候,我们不能这样做) – SoLaR 2017-02-19 23:07:23