2010-11-11 6 views
5

Ich verbinde mich über COM mit einem Programm und empfange System .__ ComObject. Ich kenne mehrere Methoden davon, so kann ich tun, wie folgt:Wie Mitglieder des COM-Objekts in C# aufgelistet werden?

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

und ähnliche

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

Beide Verfahren funktioniert gut. Aber wie kann ich innere Typinformationen des com-Objekts bestimmen und durch alle seine Mitglieder aufzählen?

Ich versuchte dies:

[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); 

Aber das t am Ende null. Kann mir jemand helfen?

Antwort

5

Sie können keinen Typ für das COM-Objekt abrufen. Dazu müssten Sie eine Interop-Bibliothek für die COM-Komponente erstellen. Was sicherlich der Schwachpunkt ist, wenn der COM-Server eine Typbibliothek hat, fügen Sie einfach einen Verweis hinzu oder führen Sie das Tlbimp.exe-Dienstprogramm aus. Wenn es vorhanden ist, wird die Typbibliothek normalerweise in der DLL eingebettet. Wenn Sie das haben, werden sowohl der Editor als auch der Objekt-Browser viel intelligenter über die Methode und die Eigenschaften, die in der COM-Klasse verfügbar sind.

Die IDispatch-Cast-Arbeit macht es sehr wahrscheinlich, dass auch eine Typbibliothek verfügbar ist. Es ist ziemlich trivial für den COM-Server-Autor, einen zu erstellen. Ein anderes Tool, das Sie verwenden können, um die Typenbibliothek zu sehen, ist OleView.exe, View + Typelib.

Wenn das nicht funktioniert, dann können Sie in der Tat Dinge aus IDispatch graben. Ihre Deklaration sieht fischig aus, das dritte Argument für IDispatch :: GetTypeInfo ist ITypeInfo, eine COM-Schnittstelle. Ein benutzerdefinierter Marshaller ist nicht erforderlich. ITypeInfo ist im System.Runtime.InteropServices.ComTypes-Namespace verfügbar. Sie können die IDispatch-Deklaration mit Reflector aus dem Framework-Code herausholen.

Und natürlich gibt es keinen Ersatz für anständige Dokumentation. Sie sollten in der Lage sein, einige zu bekommen, wenn Sie eine Lizenz erhalten, diese Komponente zu verwenden.

+0

Danke. Diese Komponente ist eine Geschäftsanwendung mit einer skriptfähigen Sprache. Die vollständige Liste seiner Mitglieder wird zur Laufzeit festgelegt. Und es hat keine Typbibliothek. –

+2

@HansPassant Ich stieß hier auf eine Erklärung für den fischigen dritten Parameter: https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2

17

Ich habe gerade einen CodeProject Artikel über die Vorgehensweise Reflection with IDispatch-based COM objects veröffentlicht. Der Artikel enthält eine kleine C# DispatchUtility Hilfsklasse, die einfach in andere Projekte eingefügt werden kann. Intern wird eine benutzerdefinierte IDispatch-Deklaration und .NET TypeToTypeInfoMarshaler verwendet, um IDispatchs ITypeInfo in eine umfangreiche .NET-Typ-Instanz zu konvertieren.

In Ihrem Beispiel könnten Sie DispatchUtility.GetType(obj, true) aufrufen, um eine .NET-Typ-Instanz zurückzuholen, mit der Sie dann GetMembers aufrufen können.

FWIW, DispatchUtility Erklärung von IDispatch.GetTypeInfo ist fast identisch mit Ihrer. Wenn GetTypeInfo aufgerufen wird, übergibt es jedoch LOCALE_SYSTEM_DEFAULT (2048) lieber als 0 für den Parameter Lcid. Vielleicht hat GetTypeInfo einen Fehler HRESULT für Ihren disp.GetTypeInfo(0, 0, out t) Aufruf zurückgegeben. Da Sie es mit [PreserveSig] deklariert haben, müssen Sie das Ergebnis überprüfen (z. B. indem Sie Marshal.ThrowExceptionForHR aufrufen).

Hier ist eine Version der DispatchUtility Klasse mit den meisten Kommentaren entfernt:

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

Dieser CustomMarshaler ist erstaunlich! Ich habe verwaltete Klassen TypeInfo und TypeLibraryInfo erstellt, um ITypeInfo und ITypeLib einzukapseln. Dies ersetzt diese ganzen großen Klassen durch eine Funktion, die ich GetCOMType nenne. Sie können alle Aufrufe und GetValues ​​ausführen, die Sie von dem COM-Objekt wünschen. Danke, dass du uns diese tolle Technik gezeigt hast. – Mike

+0

+1 wahr Ich habe ein wenig Original-Code optimiert, aber es funktioniert wie Charme. Wenn ich Type t erhalte, kann ich alle Member dieses Typs aufzählen, unabhängig davon, ob der Typ .Net-Typ oder __ComObject-Typ ist (auch wenn TypeInfo nur im Speicher vorhanden ist). Dies sollte als richtige Antwort markiert werden (und nicht die, die wir sagen können, wenn wir es können) – SoLaR

Verwandte Themen