2010-11-11 20 views
5

Me conecto a algún programa a través de COM y recibo System .__ ComObject. Conozco a varios métodos de la misma, por lo que puedo hacer como esto:¿Cómo se enumeran los miembros del objeto COM en C#?

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

y como este

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

Ambos métodos funciona bien. Pero, ¿cómo puedo determinar la información de tipo interno del objeto com y enumerar a través de todos sus miembros?

yo probamos este:

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

Pero el t es nulo al final. ¿Alguien puede ayudarme?

Respuesta

5

No se puede obtener un tipo para el objeto COM. Eso requeriría la creación de una biblioteca de interoperabilidad para el componente COM. Lo cual es sin duda el punto menos problemático si el servidor COM tiene una biblioteca de tipos, simplemente agregue una referencia o ejecute la utilidad Tlbimp.exe. Si está presente, la biblioteca de tipos generalmente está integrada dentro de la DLL. Cuando lo tienes, tanto el editor como el Object Browser se vuelven mucho más inteligentes sobre el método y las propiedades disponibles en la clase COM.

Ver el trabajo de reparto IDispatch hace que sea bastante probable que una biblioteca de tipos también esté disponible. Es bastante trivial para el autor del servidor COM crear una. Otra herramienta que puede usar para echar un vistazo a la biblioteca de tipos es OleView.exe, View + Typelib.

Si eso no funciona, entonces puede sacar cosas de IDispatch. Su declaración parece sospechosa, el tercer argumento para IDispatch :: GetTypeInfo es ITypeInfo, una interfaz COM. No es necesario un marcador personalizado, ITypeInfo está disponible en el espacio de nombres System.Runtime.InteropServices.ComTypes. Puede extraer la declaración IDispatch del código de marco con Reflector.

Y, por supuesto, no hay sustituto para la documentación decente. Debería poder obtener algunos cuando obtuvo una licencia para usar este componente.

+0

Gracias. De hecho, este componente es una aplicación de negocios con un lenguaje de secuencias de comandos dentro. La lista completa de sus miembros se determina en el tiempo de ejecución. Y no tiene una biblioteca de tipos. –

+2

@HansPassant Me encontré con una explicación para el tercer parámetro sospechoso aquí: https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2

17

Acabo de publicar un artículo de CodeProject sobre cómo hacer Reflection with IDispatch-based COM objects. El artículo proporciona una pequeña clase de ayuda C# DispatchUtility que es fácil de incluir en otros proyectos. Internamente, utiliza una declaración personalizada de IDispatch y TypeToTypeInfoMarshaler de .NET para convertir ITypeInfo de IDispatch en una instancia rica de tipo .NET.

En su ejemplo, puede llamar al DispatchUtility.GetType(obj, true) para recuperar una instancia de tipo .NET, que luego puede llamar a GetMembers.

FWIW, DispatchUtility La declaración de IDispatch.GetTypeInfo es casi idéntica a la suya. Sin embargo, al llamar a GetTypeInfo, pasa en LOCALE_SYSTEM_DEFAULT (2048) en lugar de 0 para el parámetro lcid. Quizás GetTypeInfo devolvió un error HRESULT para su llamada disp.GetTypeInfo(0, 0, out t). Como lo declaró con [PreserveSig], deberá verificar su resultado (por ejemplo, llamando al Marshal.ThrowExceptionForHR).

Aquí hay una versión de la clase DispatchUtility con la mayoría de los comentarios eliminados:

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

¡Este CustomMarshaler es increíble! Había estado creando clases administradas TypeInfo y TypeLibraryInfo para encapsular ITypeInfo e ITypeLib. Esto reemplaza esas grandes clases completas con una función a la que llamo GetCOMType. Puede hacer todas las invocaciones y valores de obtención que desee del objeto COM. Gracias por mostrarnos esta increíble técnica. – Mike

+0

+1 cierto He modificado un pequeño código original, pero funciona a las mil maravillas. Cuando obtengo Type t, puedo enumerar todos los miembros de ese tipo, independientemente de si ese tipo es .Net type o __ComObject type (incluso si TypeInfo existe solo en la memoria). Esto debe marcarse como la respuesta correcta (y no la que dice, no podemos, cuando podemos, obviamente) – SoLaR

Cuestiones relacionadas