2010-11-11 8 views
5

Łączę się z jakimś programem przez COM i otrzymuję System .__ ComObject. Znam kilka sposobów, więc można zrobić tak:Jak wyliczyć członków obiektu COM w języku C#?

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

i jak tego

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

obu metod działa dobrze. Ale jak mogę określić informacje o typie wewnętrznym obiektu COM i wyliczyć wszystkich jego członków?

Próbowałem to:

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

Ale t ma wartość null na końcu. Czy ktoś może mi pomóc?

Odpowiedz

5

Nie można uzyskać typu obiektu COM. Wymagałoby to utworzenia biblioteki interop dla komponentu COM. Co jest z pewnością słabym punktem, jeśli serwer COM ma bibliotekę typów, wystarczy dodać odniesienie do niej lub uruchomić narzędzie Tlbimp.exe. Jeśli jest obecny, biblioteka typów jest zwykle umieszczana wewnątrz biblioteki DLL. Kiedy już to zrobisz, zarówno edytor, jak i przeglądarka obiektów uzyskają dużo mądrzejszą informację o metodzie i właściwościach dostępnych w klasie COM.

Zobaczenie pracy z odlewu IDispatch sprawia, że ​​jest całkiem prawdopodobne, że dostępna jest również biblioteka typów. Dla autora serwera COM tworzenie banału jest dość banalne. Innym narzędziem, za pomocą którego można zerknąć do biblioteki typów, jest OleView.exe, View + Typelib.

Jeśli to nie zadziała, możesz rzeczywiście wykopać rzeczy z IDispatch. Twoja deklaracja wygląda podejrzanie, trzecim argumentem dla IDispatch :: GetTypeInfo jest ITypeInfo, interfejs COM. Nie ma potrzeby stosowania niestandardowego programu sprzęgającego, ITypeInfo jest dostępny w przestrzeni nazw System.Runtime.InteropServices.ComTypes. Możesz wykopać deklarację IDispatch z kodu ramowego za pomocą Reflectora.

Oczywiście nie można zastąpić przyzwoitej dokumentacji. Powinieneś być w stanie uzyskać niektóre, gdy masz licencję na korzystanie z tego składnika.

+0

Dziękuję. W rzeczywistości składnik ten jest aplikacją biznesową z wewnątrz skryptowym językiem. Pełna lista członków jest określana w środowisku wykonawczym. I nie ma biblioteki typów. –

+2

@HansPassant Natknąłem się na wyjaśnienie dla trzeciego podejrzanego parametru: https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2

17

Właśnie opublikowałem artykuł CodeProject o tym, jak wykonać Reflection with IDispatch-based COM objects. Artykuł zawiera małą pomocniczą klasę C# DispatchUtility, którą można łatwo dołączyć do innych projektów. Wewnętrznie używa niestandardowej deklaracji IDispatch i TypeToTypeInfoMarshaler .NET do konwersji ITypeInfo IDispatch do bogatej instancji typu .NET.

W tym przykładzie można zadzwonić pod numer DispatchUtility.GetType(obj, true), aby uzyskać instancję .NET Type, którą można następnie wywołać w programie GetMembers.

FWIW, DispatchUtility - deklaracja IDispatch.GetTypeInfo jest prawie identyczna z twoją. Jednak, wywołując metodę GetTypeInfo, przechodzi ona do LOCALE_SYSTEM_DEFAULT (2048) zamiast 0 dla parametru lcid. Być może GetTypeInfo zwrócił błąd HRESULT dla twojego połączenia disp.GetTypeInfo(0, 0, out t). Ponieważ zadeklarowałeś to pod numerem [PreserveSig], musisz sprawdzić jego wynik (np. Dzwoniąc pod numer Marshal.ThrowExceptionForHR).

Oto wersja klasy z większością uwag DispatchUtility usunięte:

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

Ten CustomMarshaler jest niesamowity! Budowałem zarządzane klasy TypeInfo i TypeLibraryInfo, aby hermetyzować ITypeInfo i ITypeLib. Zastępuje to całe duże klasy jedną funkcją, którą nazywam GetCOMType. Możesz wykonać wszystkie Invokes i GetValues, które chcesz z obiektu COM. Dziękujemy za pokazanie nam tej niesamowitej techniki. – Mike

+0

+1 true Poprawiłem mały oryginalny kod, ale działa jak urok. Kiedy otrzymam Type t, mogę wyliczyć wszystkie elementy tego typu, niezależnie od tego, czy był to typ .Net, czy typ __ComObject (nawet jeśli TypeInfo istnieje tylko w pamięci). To powinno być oznaczone jako poprawna odpowiedź (a nie ta, która mówi, nie możemy, kiedy możemy oczywiście) – SoLaR