2010-11-11 20 views
5

Je me connecte à un programme via COM et je reçois System .__ ComObject. Je sais que plusieurs méthodes, donc je peux le faire comme ceci:Comment énumérer des membres de l'objet COM en C#?

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

et comme ce

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

Les deux méthodes fonctionne très bien. Mais comment puis-je déterminer les informations de type interne de l'objet com et énumérer à travers tous ses membres?

J'ai essayé ceci:

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

Mais le t est nulle à la fin. Quelqu'un peut-il m'aider?

Répondre

5

Vous ne pouvez pas obtenir un type pour l'objet COM. Cela vous obligerait à créer une bibliothèque interopérationnelle pour le composant COM. Ce qui est certainement le point faible de la douleur si le serveur COM a une bibliothèque de types, il suffit d'ajouter une référence ou d'exécuter l'utilitaire Tlbimp.exe. S'il est présent, la bibliothèque de types est généralement intégrée à la DLL. Lorsque vous avez obtenu cela, l'éditeur et le navigateur d'objets obtiennent beaucoup plus intelligemment sur la méthode et les propriétés disponibles sur la classe COM.

En voyant le travail de distribution IDispatch, il est probable qu'une bibliothèque de types est également disponible. Il est assez trivial pour l'auteur du serveur COM d'en créer un. Un autre outil que vous pouvez utiliser pour jeter un coup d'œil à la bibliothèque de type est OleView.exe, View + Typelib.

Si cela ne fonctionne pas, vous pouvez en effet extraire des données de IDispatch. Votre déclaration semble louche, le troisième argument pour IDispatch :: GetTypeInfo est ITypeInfo, une interface COM. Aucun besoin d'un marshaller personnalisé, ITypeInfo est disponible dans l'espace de noms System.Runtime.InteropServices.ComTypes. Vous pouvez extraire la déclaration IDispatch du code du framework avec Reflector.

Et bien sûr, il n'y a pas de substitut à une documentation décente. Vous devriez pouvoir en obtenir quand vous avez une licence pour utiliser ce composant.

+0

Merci. En fait, ce composant est une application métier avec un langage scriptable à l'intérieur. La liste complète de ses membres est déterminée dans le runtime. Et il n'a pas de bibliothèque de type. –

+2

@HansPassant J'ai couru dans une explication pour le troisième paramètre fishy ici: https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2

17

Je viens de publier un article CodeProject sur la façon de faire Reflection with IDispatch-based COM objects. L'article fournit une petite classe d'aide C# DispatchUtility facile à inclure dans d'autres projets. En interne, il utilise une déclaration personnalisée de IDispatch et TypeToTypeInfoMarshaler .NET pour convertir ITypeInfo de IDispatch en une instance .NET Type riche.

Dans votre exemple, vous pouvez appeler DispatchUtility.GetType(obj, true) pour récupérer une instance de type .NET, sur laquelle vous pouvez ensuite appeler GetMembers.

FWIW, DispatchUtility La déclaration de IDispatch.GetTypeInfo est presque identique à la vôtre. Cependant, lors de l'appel de GetTypeInfo, il transmet LOCALE_SYSTEM_DEFAULT (2048) plutôt que 0 pour le paramètre lcid. Peut-être que GetTypeInfo a renvoyé un échec HRESULT pour votre appel disp.GetTypeInfo(0, 0, out t). Puisque vous l'avez déclaré avec [PreserveSig], vous devez vérifier son résultat (par exemple, en appelant le Marshal.ThrowExceptionForHR).

Voici une version de la classe DispatchUtility avec la plupart des commentaires supprimés:

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

Ce CustomMarshaler est incroyable! J'avais construit des classes TypeInfo et TypeLibraryInfo managées pour encapsuler ITypeInfo et ITypeLib. Cela remplace ces grandes classes entières avec une fonction que j'appelle GetCOMType. Vous pouvez faire toutes les invocations et GetValues ​​que vous souhaitez à partir de l'objet COM. Merci de nous montrer cette technique géniale. – Mike

+0

+1 vrai J'ai modifié un peu le code original, mais ça fonctionne comme du charme. Quand je reçois le type t, je peux énumérer tous les membres de ce type, peu importe si ce type était de type .Net ou de type __ComObject (même si TypeInfo existe seulement en mémoire). Cela devrait être marqué comme une réponse correcte (et non pas celle qui est saisissante, nous ne pouvons pas, quand nous pouvons évidemment) – SoLaR