2010-11-11 31 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