Compartir a través de


Instintos básicos

Inspeccione los objetos COM mediante reflexión

Lucian Wischik

Descarga de código de la Galería de código de MSDN
Examinar el código en línea

Contenido

Escriba las bibliotecas y contenedores A los que se puede llamar en tiempo de ejecución
Cuando un tipo carece de un RCW
Utilizar ITypeInfo
Buscar hacia abajo de referencias de tipo
Obtener los miembros
Primitivos y tipos sintético
La representación de COM de valor
Volcar las propiedades de un objeto COM
Utilizar IDispatch.Invoke
Discusión

Muchos de ustedes han pensó la frustración de tratar de obtener COM para trabajar. También ha pensó la alegría que viene si lo hace correctamente. Un truco común para comprender cómo funciona un objeto es examinar mediante las funciones de reflexión de Microsoft .NET Framework. En algunos casos, la reflexión de .NET también funciona con objetos COM. Examine el código siguiente para ver qué me refiero. El código utiliza la reflexión de .NET para obtener y mostrar una lista de miembros en el objeto

Dim b As New SpeechLib.SpVoice
Console.WriteLine("GETTYPE {0}", b.GetType())
For Each member In b.GetType().GetMembers()
    Console.WriteLine(member)
Next

y genera esta salida en la consola:

GETTYPE SpeechLib.SpVoiceClass
Void Speak(System.String, UInt32, UInt32 ByRef)
Void SetVoice(SpeechLib.ISpObjectToken)
Void GetVoice(SpeechLib.ISpObjectToken ByRef)
Int32 Volume
...

Pero este código no funciona para todos los objetos COM. Para ello, tendrá que utilizar la reflexión COM. Este artículo se explica por qué y cómo.

¿Por qué es posible que desea utilizar la reflexión en un objeto? HE encontrado reflexión para ser útil para depurar y el registro; se puede utilizar para escribir una rutina de volcado con finalidad general que se imprime todo que lo posible acerca de un objeto. El código en esta columna será suficiente para que pueda escribir su propia rutina de "volcado". Una vez que de hecho, lo podría incluso llama desde la ventana Inmediato durante la depuración. Esto es especialmente útil ya que el depurador de Visual Studio siempre no ofrece mucha información acerca de los objetos COM.

Para el uso de producción, la reflexión es útil si ha escrito una aplicación que toma los componentes complementos donde los usuarios colocar sus componentes en un directorio o la lista en el registro y su aplicación tiene que examinar estos componentes y buscar qué clases y métodos que exponen. Por ejemplo, Visual Studio utiliza la reflexión de este modo para rellenar IntelliSense.

Escriba las bibliotecas y contenedores A los que se puede llamar en tiempo de ejecución

Vamos a generar un proyecto para ilustrarlo. En primer lugar, crear el proyecto y agregue una referencia de COM a través de Project > AddReference. Para esta columna VOY a utilizar el SpeechLib "biblioteca de objetos de Microsoft Speech". Figura 1 muestra las entidades relevantes y archivos que se examinan cuando se ejecuta el código de reflexión que se ha visto anteriormente.

fig01.gif

Figura 1 reflecting en SpeechLib

Sapi.dll es la DLL que contiene SpeechLib. Resulta viven en windir%\system32\speech\common\sapi.dll %. Este archivo DLL contiene tanto la implementación de la clase COM SpVoice y un Type­Library que contiene todos los información de reflexión para él. Type­Libraries son opcionales, pero casi todos los componentes COM en el sistema tendrá uno.

Interop.SpeechLib.dll se generan automáticamente por Visual Studio mediante Project > AddReference. El generador de refleja tras la TypeLibrary y genera un tipo de interoperabilidad para SpVoice. Este tipo es una clase administrada que contiene un método administrado para cada método nativo de COM que se encuentra en la TypeLibrary. También puede generar los ensamblados de interoperabilidad mediante la herramienta de línea de comandos tlbimp.exe desde el SDK de Windows. Una instancia de un tipo de interoperabilidad se denomina un contenedor de que se puede llamar en tiempo de ejecución (RCW), y ajusta un puntero a una instancia de una clase COM.

Ejecutar el comando siguiente de Visual Basic crea un RCW (una instancia del tipo de interoperabilidad) y también una instancia de la clase COM SpVoice:

Dim b As New SpeechLib.SpVoice

La variable "b" hace referencia el RCW, por lo que cuando el código se refleja en "b" realmente se reflejan en el equivalente administrado que tenía ha construido a partir del TypeLibrary.

Los usuarios que implementación sus ConsoleApplication1.exe también tendrá que implementar Interop.SpeechLib.dll. (Sin embargo, 2010 de Visual Studio se permite el tipo de interoperabilidad para copiarse directamente en ConsoleApplication1.exe. Esto simplifica enormemente implementación. La característica se denomina "no-principal de interoperabilidad de ensamblado", o "no-PIA" para abreviar.)

Cuando un tipo carece de un RCW

¿Qué sucede si no dispone de un ensamblado de interoperabilidad para un objeto COM? Por ejemplo, ¿qué ocurre si se creó el objeto de COM propio mediante CoCreateInstance, o si, tan a menudo ocurre, llama a un método en un objeto COM y devuelve un objeto COM cuyo tipo no se conoce de antemano? ¿Qué sucede si ha escrito un administrado complemento para una aplicación no administrada y la aplicación asignó un objeto COM? ¿Qué ocurre si se descubre el objeto COM para crear buscando en el registro?

Cada uno de estos elementos le dará una referencia de IntPtr al objeto COM en lugar de una referencia de objeto a su RCW. Cuando se solicita un RCW alrededor de dicho IntPtr, se obtiene lo que se ilustra en la figura 2 .

fig02.gif

La Figura 2 obtener un tiempo de ejecución contenedor al que se puede llamar

En la figura 2 verá que el CLR proporciona un RCW predeterminado, una instancia de la interoperabilidad predeterminado escriba "System.__ComObject". Si refleje de este tipo por lo que

Dim b = CoCreateInstance(CLSID_WebBrowser, _
                   Nothing, 1, IID_IUnknown)
Console.WriteLine("DUMP {0}", b.GetType())
For Each member In b.GetType().GetMembers()
    Console.WriteLine(member)
Next

encontrará que no tiene ningún miembro que son útil para usted; sólo tiene estos:

DUMP System.__ComObject
System.Object GetLifetimeService()
System.Object InitializeLifetimeService()
System.Runtime.Remoting.ObjRef CreateObjRef(System.Type)
System.String ToString()
Boolean Equals(System.Object)
Int32 GetHashCode()
System.Type GetType()

Para obtener reflexión útil sobre dicho un objeto COM, debe reflejar en su TypeLibrary usted mismo. Puede hacerlo así mediante ITypeInfo.

Pero en primer lugar, una nota breve: si un proporciona método hacer una seguridad de un objeto o IDispatch o ITypeInfo o otra clase de .NET o interfaz, se ha concedido una referencia a los RCW y .NET se ocupará de liberarlo para usted. Pero si el método le da al IntPtr, significa tiene una referencia al objeto COM propio, y casi siempre es necesario llamar a Marshal.Release en ella (depende la semántica precisa de cualquier método proporcionado por dicho IntPtr). He aquí cómo:

   Dim com As IntPtr = ...
   Dim rcw = Marshal.GetObjectForIUnknown(com)
   Marshal.Release(com)

Pero es mucho más comunes para declarar la función con cálculo de referencias para que llama el contador de referencias a GetObjectForI­Unknown y versión automáticamente, como en la declaración de CoCreateInstance, que aparece en La figura 3 .

La figura 3 CoCreateInstance

<DllImport("ole32.dll", ExactSpelling:=True, PreserveSig:=False)> _
Function CoCreateInstance( _
    ByRef clsid As Guid, _
    <MarshalAs(UnmanagedType.Interface)> ByVal punkOuter As Object, _
    ByVal context As Integer, _
    ByRef iid As Guid) _
    As <MarshalAs(UnmanagedType.Interface)> Object
End Function

Dim IID_NULL As Guid = New Guid("00000000-0000-0000-C000-000000000000")
Dim IID_IUnknown As Guid = New _
    Guid("00000000-0000-0000-C000-000000000046")
Dim CLSID_SpVoice As Guid = New _
    Guid("96749377-3391-11D2-9EE3-00C04F797396")

Dim b As Object = CoCreateInstance(CLSID_SpVoice, Nothing, 1, _ 
    IID_IUnknown)

Utilizar ITypeInfo

ITypeInfo es el equivalente de System.Type para las clases COM e interfaces. Con él puede enumerar los miembros de una clase o interfaz. En este ejemplo, voy a imprimirlos; sin embargo, se puede utilizar ITypeInfo para buscar miembros en tiempo de ejecución y, a continuación, invocar a u obtener sus valores de propiedad a través de IDispatch. Figura 4 se muestra cómo encaja ITypeInfo, así como todas las otras estructuras que tendrá que utilizar.

fig04.gif

La figura 4 ITypeInfo e información de tipo

El primer paso es obtener el ITypeInfo de un objeto COM especificado. Sería interesante si se puede utilizar rcw.GetType(), pero lamentablemente Esto devuelve la información de System.Type sobre el RCW sí mismo. También sería interesante si puede utilizar la función integrada Marshal.GetITypeInfoForType(rcw), pero desafortunadamente Esto sólo funciona para RCW que proceden de los ensamblados de interoperabilidad. En su lugar, tendrá que obtener el ITypeInfo manualmente.

En el siguiente código funcionará para ambos de estos casos, si el RCW incluida en el desprendible en mscorlib, o desde un ensamblado de interoperabilidad adecuado:

Dim idisp = CType(rcw, IDispatch)
Dim count As UInteger = 0
idisp.GetTypeInfoCount(count)
If count < 1 Then Throw New Exception("No type info")
Dim _typeinfo As IntPtr
idisp.GetTypeInfo(0, 0, _typeinfo)
If _typeinfo = IntPtr.Zero Then Throw New Exception("No ITypeInfo")
Dim typeInfo = CType(Marshal.GetTypedObjectForIUnknown(_typeinfo, _
                     GetType(ComTypes.ITypeInfo)), ComTypes.ITypeInfo)
Marshal.Release(_typeinfo)

Este código utiliza la interfaz IDispatch. La interfaz no se define en cualquier parte en .NET Framework, para que tenga que definir usted mismo, tal como se observa en figura 5 . Deja la función GetIDsOfNames vacía porque no es necesaria para el propósito actual; se necesita incluir una entrada, sin embargo, porque la interfaz tiene que mostrar el número correcto de métodos en el orden correcto.

La figura 5 la definición de la interfaz IDispatch

''' <summary>
''' IDispatch: this is a managed version of the IDispatch interface
''' </summary>
''' <remarks>We don't use GetIDsOfNames or Invoke, and so haven't 
''' bothered with correct signatures for them.</remarks>
<ComImport(), Guid("00020400-0000-0000-c000-000000000046"), _
 InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
 Interface IDispatch
    Sub GetTypeInfoCount(ByRef pctinfo As UInteger)
    Sub GetTypeInfo(ByVal itinfo As UInteger, ByVal lcid _
      As UInteger, ByRef pptinfo As IntPtr)
    Sub stub_GetIDsOfNames()
    Sub Invoke(ByVal dispIdMember As Integer, ByRef riid As Guid, _
               ByVal lcid As UInteger, ByVal dwFlags As UShort, _
               ByRef pDispParams As ComTypes.DISPPARAMS, _
               ByRef pVarResult As [VARIANT], ByRef pExcepInfo As IntPtr, _
               ByRef pArgErr As UInteger)
End Interface

Es posible que preguntan por qué IDispatch tiene el atributo de InterfaceType establecido en ComInterfaceType.InterfaceIsUnknown en lugar de establecer en ComInterfaceType.InterfaceIsIDisapatch. Eso que el atributo InterfaceType indica lo que la interfaz hereda de, no lo es.

Tiene ITypeInfo. Ahora es el momento para iniciar la lectura de él. Toma un vistazo a la figura 6 porque la función voy a implementar para volcar la información de tipo se muestra no existe. Para GetDocumentation, el primer parámetro es un MEMBERID, es decir, Get­Documentation pretende devolver información acerca de cada miembro del tipo. Pero también puede pasar en MEMBERID_NIL, que tiene el valor-1, para obtener información sobre el propio tipo.

Figura 6 DumpTypeInfo

''' <summary>
''' DumpType: prints information about an ITypeInfo type to the console
''' </summary>
''' <param name="typeInfo">the type to dump</param>
Sub DumpTypeInfo(ByVal typeInfo As ComTypes.ITypeInfo)

    ' Name:
    Dim typeName = "" : typeInfo.GetDocumentation(-1, typeName, "", 0, "")
    Console.WriteLine("TYPE {0}", typeName)

    ' TypeAttr: contains general information about the type
    Dim pTypeAttr As IntPtr : typeInfo.GetTypeAttr(pTypeAttr)
    Dim typeAttr = CType(Marshal.PtrToStructure(pTypeAttr, _
                         GetType(ComTypes.TYPEATTR)), ComTypes.TYPEATTR)
    typeInfo.ReleaseTypeAttr(pTypeAttr)
    ...

End Sub

Tenga en cuenta cómo funciona la conversión. Cuando se llama a typeInfo.GetTypeAttr, asigna un bloque de memoria no administrado y devuelve a usted la pTypeAttr de puntero. A continuación, Marshal.PtrToStructure copia de este bloque no administrado en un bloque administrado (que, a continuación, será recolección). Por lo que es bien llamar inmediatamente a type­Info.ReleaseTypeAttr.

Como se muestra anteriormente, deberá typeAttr para saber cómo muchos miembros y hay interfaces implementadas son (typeAttr.cFuncs, typeAttr.cVars y typeAttr.cImplTypes).

Buscar hacia abajo de referencias de tipo

La primera tarea que debe realizarse es obtener una lista de interfaces implementa o hereda. (En COM, uno nunca hereda de otra clase). Este es el código:

' Inheritance:
For iImplType = 0 To typeAttr.cImplTypes - 1
    Dim href As Integer
    typeInfo.GetRefTypeOfImplType(iImplType, href)
    ' "href" is an index into the list of type descriptions within the
    ' type library.
    Dim implTypeInfo As ComTypes.ITypeInfo
    typeInfo.GetRefTypeInfo(href, implTypeInfo)
    ' And GetRefTypeInfo looks up the index to get an ITypeInfo for it.
    Dim implTypeName = ""
    implTypeInfo.GetDocumentation(-1, implTypeName, "", 0, "")
    Console.WriteLine("  Implements {0}", implTypeName)
Next

Hay una capa de direccionamiento indirecto aquí. GetRefTypeOfImplType no da la ITypeInfo de los tipos implementados directamente: en su lugar, proporciona un identificador a ITypeInfo. La función GetRefType­Info es lo que busca ese identificador. A continuación, puede utilizar el GetDocumentation(-1) familiar para obtener el nombre de ese tipo implementado. HABLARÉ sobre identificadores a ITypeInfo nuevo más tarde.

Obtener los miembros

Para reflejar en los miembros de campo, cada campo tiene un VARDESC para describirlo. Una vez nuevo, el objeto typeInfo asigna un pVarDesc de bloque de memoria no administrada y que las referencias a un varDesc bloque administrado y suelte el bloque no administrado:

' Field members:
For iVar = 0 To typeAttr.cVars - 1
    Dim pVarDesc As IntPtr : typeInfo.GetVarDesc(iVar, pVarDesc)
    Dim varDesc = CType(Marshal.PtrToStructure(pVarDesc, _
                        GetType(ComTypes.VARDESC)), ComTypes.VARDESC)
    typeInfo.ReleaseVarDesc(pVarDesc)
    Dim names As String() = {""}
    typeInfo.GetNames(varDesc.memid, names, 1, 0)
    Dim varName = names(0)
    Console.WriteLine("  Dim {0} As {1}", varName, _
                      DumpTypeDesc(varDesc.elemdescVar.tdesc, typeInfo))
Next

No se sienten por la función "GetNames". Concordaría cada miembro puede tener varios nombres. Es lo suficientemente simplemente para obtener el primero.

El código para reflejar en los miembros de función es generalmente similar (consulte la figura 7 ). El tipo de retorno es funcDesc.elemdescFunc.tdesc. El número de parámetros formales se le da por funcDesc.cParams y los parámetros formales se almacenan en el funcDesc.lprgelemdescParam de matriz. (No es tener acceso a una matriz no administrada como éste desde código administrado ya que tiene que realizar aritmética de puntero agradable.)

La figura 7 reflejarse en los miembros de función

For iFunc = 0 To typeAttr.cFuncs - 1

   ' retrieve FUNCDESC:
   Dim pFuncDesc As IntPtr : typeInfo.GetFuncDesc(iFunc, pFuncDesc)
   Dim funcDesc = CType(Marshal.PtrToStructure(pFuncDesc, _
                         GetType(ComTypes.FUNCDESC)), ComTypes.FUNCDESC)
   Dim names As String() = {""}
   typeInfo.GetNames(funcDesc.memid, names, 1, 0)
   Dim funcName = names(0)

   ' Function formal parameters:
   Dim cParams = funcDesc.cParams
   Dim s = ""
   For iParam = 0 To cParams - 1
        Dim elemDesc = CType(Marshal.PtrToStructure( _
                  New IntPtr(funcDesc.lprgelemdescParam.ToInt64 + _
                  Marshal.SizeOf(GetType(ComTypes.ELEMDESC)) * iParam), _
                  GetType(ComTypes.ELEMDESC)), ComTypes.ELEMDESC)
        If s.Length > 0 Then s &= ", "
        If (elemDesc.desc.paramdesc.wParamFlags And _
            Runtime.InteropServices.ComTypes.PARAMFLAG.PARAMFLAG_FOUT) _
             <> 0 Then s &= "out "
        s &= DumpTypeDesc(elemDesc.tdesc, typeInfo)
   Next

   ' And print out the rest of the function's information:
   Dim props = ""
   If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYGET) _
      <> 0 Then props &= "Get "
   If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) _
      <> 0 Then props &= "Set "
   If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) _
     <> 0 Then props &= "Set "
   Dim isSub = (FUNCDESC.elemdescFunc.tdesc.vt = VarEnum.VT_VOID)
   s = props & If(isSub, "Sub ", "Function ") & funcName & "(" & s & ")"
   s &= If(isSub, "", " as " & _
     DumpTypeDesc(funcDesc.elemdescFunc.tdesc, typeInfo))
   Console.WriteLine("  " & s)
   typeInfo.ReleaseFuncDesc(pFuncDesc)
Next

Existen otros indicadores como así como PARAMFLAG_FOUT, indicadores de en, retval, opcional y así sucesivamente. La información de tipo de campos y los miembros se almacena en una estructura TYPEDESC, y invoca una función DumpTypeDesc se se imprimen. Puede parecer sorprendente que TYPEDESC se utiliza en lugar de ITypeInfo. Le ampliar a continuación.

Primitivos y tipos sintético

COM utiliza TYPEDESC para describir algunos tipos y ITypeInfo para describir otros usuarios. ¿Cuál es la diferencia? COM utiliza ITypeInfo sólo para las clases y interfaces definidas en bibliotecas de tipos. Y TYPEDESC se utiliza para tipos primitivos como Integer o valor de tipo String y también para tipos compuestos como SpVoice de matriz o referencia de IUnknown.

Éste es diferente de .NET: en primer lugar, en. NET, incluso el primitivo tipos como valor de tipo Integer y String se representan mediante las clases o estructuras de System.Type; en segundo lugar, en .NET el compuestos tipos como matriz de tipo Integer se representan mediante System.Type.

El código que necesite cavar a través de un TYPEDESC es bastante sencillo (consulte la figura 8 ). Tenga en cuenta que la VT_USERDEFINED caso nuevo utiliza un controlador para una referencia, que debe buscar a través de GetRefTypeInfo.

Figura 8 examinando TYPEDESC

Function DumpTypeDesc(ByVal tdesc As ComTypes.TYPEDESC, _
  ByVal context As ComTypes.ITypeInfo) As String
    Dim vt = CType(tdesc.vt, VarEnum)
    Select Case vt

        Case VarEnum.VT_PTR
            Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, _
                          GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
            Return "Ref " & DumpTypeDesc(tdesc2, context)

        Case VarEnum.VT_USERDEFINED
            Dim href = CType(tdesc.lpValue.ToInt64 And Integer.MaxValue, Integer)
            Dim refTypeInfo As ComTypes.ITypeInfo = Nothing
            context.GetRefTypeInfo(href, refTypeInfo)
            Dim refTypeName = ""
            refTypeInfo.GetDocumentation(-1, refTypeName, "", 0, "")
            Return refTypeName

        Case VarEnum.VT_CARRAY
            Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, _
                          GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
            Return "Array of " & DumpTypeDesc(tdesc2, context)
            ' lpValue is actually an ARRAYDESC structure, which also has
            ' information on the array dimensions, but alas .NET doesn't 
            ' predefine ARRAYDESC.

        Case Else
            ' There are many other VT_s that I haven't special-cased, 
            ' e.g. VT_INTEGER.
            Return vt.ToString()
    End Select
End Function

La representación de COM de valor

El siguiente paso es realmente volcar un objeto COM, es decir, imprima los valores de sus propiedades. Esta tarea es fácil si sabe los nombres de dichas propiedades debido a que sólo puede utilizar una llamada enlazada en tiempo de ejecución en Visual Basic:

Dim com as Object : Dim val = com.SomePropName

El compilador traduce en una llamada en tiempo de ejecución de IDispatch:: Invoke para recuperar el valor de la propiedad. Pero en el caso de reflexión, no es posible que sabe el nombre de la propiedad. Quizás todo lo que es MEMBER­ID, por lo que hay llamar IDispatch:: Invoke. Esto no es muy bonitas.

La primera preguntas procede del hecho de que COM y .NET representan valores de maneras muy diferentes. En .NET utilice objetos para representar valores arbitrarios. En COM use la estructura variante, como se muestra en la figura 9 .

Figura 9 con VARIANT

''' <summary>
''' VARIANT: this is called "Object" in Visual Basic. It's the universal ''' variable type for COM.
''' </summary>
''' <remarks>The "vt" flag determines which of the other fields have
''' meaning. vt is a VarEnum.</remarks>
<System.Runtime.InteropServices.StructLayoutAttribute( _
           System.Runtime.InteropServices.LayoutKind.Explicit, Size:=16)> _
Public Structure [VARIANT]
    <System.Runtime.InteropServices.FieldOffsetAttribute(0)> Public vt As UShort
    <System.Runtime.InteropServices.FieldOffsetAttribute(2)> _
      Public wReserved1 As UShort
    <System.Runtime.InteropServices.FieldOffsetAttribute(4)> _
      Public wReserved2 As UShort
    <System.Runtime.InteropServices.FieldOffsetAttribute(6)> _
      Public wReserved3 As UShort
    '
    <System.Runtime.InteropServices.FieldOffsetAttribute(8)> Public llVal As Long
    <System.Runtime.InteropServices.FieldOffsetAttribute(8)> Public lVal As Integer
    <System.Runtime.InteropServices.FieldOffsetAttribute(8)> Public bVal As Byte
    ' and similarly for many other accessors
    <System.Runtime.InteropServices.FieldOffsetAttribute(8)> _
      Public ptr As System.IntPtr

    ''' <summary>
    ''' GetObject: returns a .NET Object equivalent for this Variant.
    ''' </summary>
    Function GetObject() As Object
        ' We want to use the handy Marshal.GetObjectForNativeVariant.
        ' But this only operates upon an IntPtr to a block of memory.
        ' So we first flatten ourselves into that block of memory. (size 16)
        Dim ptr = Marshal.AllocCoTaskMem(16)
        Marshal.StructureToPtr(Me, ptr, False)
        Try : Return Marshal.GetObjectForNativeVariant(ptr)
        Finally : Marshal.FreeCoTaskMem(ptr) : End Try
    End Function
End Structure

Un valor de COM utiliza el campo vt para indicar qué tipo es. Podría ser VarEnum.VT_INT, VarEnum.VT_PTR o cualquiera de los tipos de VarEnum 30 o por lo que. Conocer su tipo, puede averiguar cuál de los demás campos para buscar en una instrucción Select Case gigante. Afortunadamente, esa instrucción Select Case se ya ha implementado en la función Marshal.GetObjectForNativeVariant.

Volcar las propiedades de un objeto COM

Deseará volcar las propiedades de su objeto COM, más o menos como la ventana de "Inspección rápida" en Visual Studio:

DUMP OF COM OBJECT #28114988
ISpeechVoice.Status = System.__ComObject   As Ref ISpeechVoiceStatus
ISpeechVoice.Rate = 0   As Integer
ISpeechVoice.Volume = 100   As Integer
ISpeechVoice.AllowAudioOutputFormatChangesOnNextSet = True   As Bool
ISpeechVoice.EventInterests = 0   As SpeechVoiceEvents
ISpeechVoice.Priority = 0   As SpeechVoicePriority
ISpeechVoice.AlertBoundary = 32   As SpeechVoiceEvents
ISpeechVoice.SynchronousSpeakTimeout = 10000   As Integer

La es, hay muchos tipos en COM. Sería agotar para escribir código para controlar correctamente todos los casos y difíciles ensamblar suficiente casos de prueba para probar todos los. Aquí le liquidar para volcar un pequeño conjunto de tipos que sabe que puede controlar correctamente.

¿Y más allá de eso, lo que sería útil para volcado? Además de las propiedades, también sería útil obtener un volcado de cualquier cosa expuestas mediante funciones de (efecto de liberar) puras, como IsTall(). Pero no desea invocar las funciones como AddRef(). Para discriminate entre estas dos, reckon que cualquier función nombre como " es * " es feria juego para volcar (consulte la figura 10 ). Resulta que los programadores de COM parecen utilizar es * funciona con mucha menos frecuencia que los programadores que utilizan .NET!

Figura 10 examinando Get * y se * métodos

' We'll only try to retrieve things that are likely to be side-effect-
' free properties:

If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYGET) = 0 _
   AndAlso Not funcName Like "[Gg]et*" _
   AndAlso Not funcName Like "[Ii]s*" _
   Then Continue For
If funcDesc.cParams > 0 Then Continue For
Dim returnType = CType(funcDesc.elemdescFunc.tdesc.vt, VarEnum)
If returnType = VarEnum.VT_VOID Then Continue For
Dim returnTypeName = DumpTypeDesc(funcDesc.elemdescFunc.tdesc, typeInfo)

' And we'll only try to evaluate the easily-evaluatable properties:
Dim dumpableTypes = New VarEnum() {VarEnum.VT_BOOL, VarEnum.VT_BSTR, _
           VarEnum.VT_CLSID, _ 
           VarEnum.VT_DECIMAL, VarEnum.VT_FILETIME, VarEnum.VT_HRESULT, _
           VarEnum.VT_I1, VarEnum.VT_I2, VarEnum.VT_I4, VarEnum.VT_I8, _
           VarEnum.VT_INT, VarEnum.VT_LPSTR, VarEnum.VT_LPWSTR, _
           VarEnum.VT_R4, VarEnum.VT_R8, _
           VarEnum.VT_UI1, VarEnum.VT_UI2, VarEnum.VT_UI4, VarEnum.VT_UI8, _
           VarEnum.VT_UINT, VarEnum.VT_DATE, _
           VarEnum.VT_USERDEFINED}
Dim typeIsDumpable = dumpableTypes.Contains(returnType)
If returnType = VarEnum.VT_PTR Then
    Dim ptrType = CType(Marshal.PtrToStructure( _
      funcDesc.elemdescFunc.tdesc.lpValue, _
                        GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
    If ptrType.vt = VarEnum.VT_USERDEFINED Then typeIsDumpable = True
End If

En este código, el tipo final de dumpable que tener en cuenta es una VT_PTR a un tipo VT_USERDEFINED. Esto cubre el caso común de una propiedad que devuelve una referencia a otro objeto.

Utilizar IDispatch.Invoke

El paso final es leer la propiedad que ha identificado por su MEMBERID o invocar la función. Puede ver el código para hacerlo en la figura 11 . El método clave aquí es IDispatch.Invoke. Su primer argumento es el identificador de miembro de la propiedad o función que está invocando. La variable dispatchType es 2 para una propiedad de get o 1 para un function-invoke. Si se invoca una función que tuvieron argumentos, a continuación, podría también configurar la dispParams estructura. Por último, el resultado vuelve en varResult. Como antes, puede llamar sólo a Get­Object en él para convertir la variante en un objeto. NET.

Figura 11 la propiedad de lectura o de invocación de la función

' Here's how we fetch an arbitrary property from a COM object, 
' identified by its MEMBID.
Dim val As Object
Dim varResult As New [VARIANT]
Dim dispParams As New ComTypes.DISPPARAMS With {.cArgs = 0, .cNamedArgs = 0}
Dim dispatchType = If((funcDesc.invkind And _
   ComTypes.INVOKEKIND.INVOKE_PROPERTYGET)<>0, 2US, 1US)
idisp.Invoke(funcDesc.memid, IID_NULL, 0, dispatchType, dispParams, _
   varResult, IntPtr.Zero, 0)
val = varResult.GetObject()
If varResult.vt = VarEnum.VT_PTR AndAlso varResult.ptr <> IntPtr.Zero _ 
   Then
   Marshal.Release(varResult.ptr)
End If

Tenga en cuenta la llamada a Marshal.Release. Se trata de un modelo universal en COM que si una función manos alguien un puntero, llama primero al AddRef en él y, es responsabilidad del llamador llamar a versión en el. De esta forma me incluso mejor que .NET tenga recolección de elementos no utilizados.

Por cierto, podría ha utilizar ITypeInfo.Invoke en lugar de IDispatch.Invoke. Pero recibe un poco confuso. Supongamos que tiene una variable, "com", que señala a la interfaz IUnknown de un objeto COM. Y suponga que ITypeInfo de com es un SpeechLib.SpVoice que ocurre con una propiedad con el id de miembro 12. No se puede llamar a ITypeInfo.Invoke(com,12) directamente; en primer lugar debe llaman Query­Interface para obtener SpVoice interfaz de com y, a continuación, llamar a ITypeInfo.Invoke en la. Al final, es más fácil utilizar IDispatch.Invoke.

Ahora ha visto cómo reflejar objetos COM a través de IType­Info. Esto es útil para las clases COM que carecen de tipos de interoperabilidad. Y ha visto cómo utilizar IDispatch.Invoke para recuperar los valores de COM, almacenado en una estructura VARIANT.

Pregunte sobre la creación de un contenedor completo ITypeInfo y TYPEDESC, que hereda de System.Type. Con esto, los usuarios podría utilizar el mismo código para la reflexión en tipos COM como .NET tipos. Pero al final, al menos para mi proyecto, este tipo de contenedor era demasiado trabajo para obtener insignificante.

Para obtener más información en el reflejo puede hacer por usted, consulte" Dodge riesgos de rendimiento comunes que diseñe aplicaciones Speedy "y" CLR Inside Out: reflejos de reflexión ."

Sincere gracias a Eric Lippert, Sonja Keserovic y Hsia Calvin por su ayuda con esta columna.

Envíe sus preguntas y comentarios a instinct@microsoft.com .

Lucian Wischik es el responsable de especificación de Visual Basic. Desde unirse al equipo del compilador de Visual Basic ha trabajado en nuevas características relacionadas con la inferencia de tipo, lambdas y covarianza genérica. También ha trabajado en el SDK de Robotics y simultaneidad y publica varios documentos académicas en el asunto. Lucian contiene un PhD en teoría de simultaneidad de la Universidad de Cambridge.