Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Esigenze primarie

Analisi di oggetti COM mediante Reflection

Lucian Wischik

Download codice disponibile dalla Raccolta di codice di MSDN
Selezionare il codice in linea

Contenuto

Digitare le librerie e Runtime Callable Wrapper
Quando un tipo non dispone di un RCW
Utilizzo ITypeInfo
Ricerca dei riferimenti di tipo verso il basso
Recupero membri
Primitiva e tipi sintetico A
La rappresentazione COM del valore
Scaricare le proprietà di un oggetto COM
Utilizzo IDispatch.Invoke
Discussione

Molti si hanno lamentato involontaria di tentativo di recupero COM per l'utilizzo. Anche stato pensato joy incluso quando si è completata. Un trucco comune per conoscenza funzionamento di un oggetto è di analisi utilizzando le funzionalità di reflection di Microsoft .NET Framework. In alcuni casi, reflection di .NET funziona anche per gli oggetti COM. Esaminare il codice seguente per visualizzare il significato. Il codice utilizza .NET reflection per ottenere e visualizzare un elenco di membri nell'oggetto

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

e produce questo output sulla console:

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

Ma questo codice non funziona per tutti gli oggetti COM. Per, è necessario utilizzare la reflection di COM. Questo articolo viene spiegato perché e come.

Motivo per cui potrebbe desideri utilizzare reflection su un oggetto? HO trovato la reflection per essere utile per il debug e la registrazione ed è possibile utilizzarla per scrivere una routine di immagine generico stampato tutto ciò che è possibile su un oggetto. Il codice in questa colonna sarà sufficiente per scrivere una propria routine "Dettagli". Dopo che è completato, è possibile anche richiamare dalla finestra controllo immediata durante il debug. È particolarmente utile poiché il debugger di Visual Studio non fornire sempre molto informazioni sugli oggetti COM.

Per l'utilizzo di produzione, è utile se si scrive un'applicazione che utilizza componenti plug-in in cui gli utenti rilasciare i relativi componenti in una directory o un elenco nel Registro di sistema e l'applicazione deve esaminare i componenti e individuare quali classi e metodi che espongono funzionalità di reflection. Visual Studio, ad esempio, utilizza la reflection in questo modo per popolare IntelliSense.

Digitare le librerie e Runtime Callable Wrapper

Si genera un progetto per Illustrazione. Innanzitutto creare il progetto e aggiungervi un riferimento COM tramite Project > AddReference. Per la colonna utilizzerò la SpeechLib "Microsoft Speech Object Library". La figura 1 Mostra le entità rilevanti e il file che vengono esaminati quando si esegue il codice di reflection che precedente si è visto.

fig01.gif

Figura 1 Reflecting su SpeechLib

Sapi.dll è la DLL che contiene SpeechLib. Accade risiedono in windir%\system32\speech\common\sapi.dll %. Questa DLL contiene sia l'implementazione della classe COM SpVoice e un Type­Library che contiene tutte le informazioni di reflection per è. Type­Libraries sono facoltativi, ma quasi ogni componente COM nel sistema avrà uno.

Interop.SpeechLib.DLL è stato generato automaticamente da Visual Studio tramite Project > AddReference. Il generatore riflette al momento il TypeLibrary e produce un tipo di interoperabilità per SpVoice. Questo tipo è una classe gestita che contiene un metodo gestito per ogni metodo COM nativo trovato nel TypeLibrary. È inoltre possibile generare utilizzando lo strumento della riga di comando di tlbimp.exe di Windows SDK gli assembly di interoperabilità. Un'istanza di un tipo di interoperabilità viene chiamata un runtime callable wrapper (RCW), ed esegue il wrapping di un puntatore a un'istanza di una classe COM.

Il comando di Visual Basic seguente crea un RCW (un'istanza del tipo di interoperabilità) e inoltre un'istanza della classe COM SpVoice:

Dim b As New SpeechLib.SpVoice

La variabile "b" fa riferimento l'RCW, in modo che quando il codice applicate al "b" realtà è stato riproduzione al momento l'equivalente gestito è stato costruito dal TypeLibrary.

Anche gli utenti che distribuiscono i ConsoleApplication1.exe dovranno distribuire Interop.SpeechLib.dll. (Tuttavia, Visual Studio 2010 consentirà il tipo di interoperabilità da essere copiati direttamente all'interno di ConsoleApplication1.exe. Questo viene notevolmente semplificata distribuzione. La funzionalità è denominata "non principale-interoperabilità-assembly" o assembly di interoperabilità primario non breve.)

Quando un tipo non dispone di un RCW

Cosa succede se non si dispone di un assembly di interoperabilità per un oggetto COM? Ad esempio, cosa accade se è creato l'oggetto COM stesso tramite CoCreateInstance o se, come spesso accade, si chiama un metodo su un oggetto COM e restituisce un oggetto COM cui tipo non è noto in anticipo? Cosa succede se si scrive un gestito plug-in per un'applicazione non gestita e se l'applicazione ha assegnato un oggetto COM? Cosa succede se si scoperto quale oggetto COM da creare eseguendo la ricerca mediante il Registro di sistema?

Ogni uno di questi elementi sarà un riferimento di IntPtr all'oggetto COM anziché un riferimento oggetto relativi RCW. Quando si richiede un RCW attorno a tale IntPtr, si ottiene che cosa è illustrato nella Figura 2 .

fig02.gif

Nella figura 2 visualizzazione un Runtime Callable Wrapper

Nella Figura 2 è necessario verificare che Common Language RUNTIME fornito un wrapper RCW predefinito, un'istanza di interoperabilità predefinito digitare "System.__ComObject". Se la reflection su questo tipo in modo

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

si scoprirà che tutti i membri sono utili per l'utente non ha; solo è questi:

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()

Per visualizzare la reflection utile di un oggetto COM, è necessario riflettere il TypeLibrary manualmente. Puoi eseguire questa utilizza ITypeInfo.

Ma prima, una breve nota: se è specificato si fornisce un metodo è eseguire un oggetto o IDispatch o ITypeInfo o altra classe .NET o interfaccia, quindi è un riferimento alla RCW e .NET occuperà di rilasciare per l'utente. Ma se il metodo consente nuovamente un IntPtr, non è presente un riferimento all'oggetto COM stesso e avere quasi sempre chiamare Marshal.Release in esso (ciò dipende la semantica precisa di qualsiasi metodo assegnato tale IntPtr). Ecco come:

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

Ma è molto più comune per dichiarare la funzione con marshalling in modo che il gestore di marshalling chiama GetObjectForI­Unknown e rilascia automaticamente, come nella dichiarazione di CoCreateInstance che vedi nella Nella figura 3 .

Nella 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)

Utilizzo ITypeInfo

ITypeInfo è l'equivalente di System.Type per le classi COM e interfacce. Con è possibile enumerare i membri di una classe o interfaccia. In questo esempio, per stampare le; tuttavia, è possibile utilizzare ITypeInfo per cercare i membri in fase di esecuzione e quindi richiamare tali o ottenere i valori di proprietà tramite IDispatch. Nella figura 4 viene illustrato come ITypeInfo rientri, come pure tutte le altre strutture che è necessario utilizzare.

fig04.gif

Nella figura 4 ITypeInfo e informazioni sui tipi

Il primo passaggio è quasi ITypeInfo per un oggetto COM specificato. Sarebbe utile se è possibile utilizzare rcw.GetType() ma alas questo restituisce le informazioni di System.Type l'RCW stesso. Inoltre sarebbe utile se è possibile utilizzare la funzione incorporata Marshal.GetITypeInfoForType(rcw), ma purtroppo questo funziona solo per RCW che provengono da un assembly di interoperabilità. Al contrario, sarà necessario ottenere l'interfaccia ITypeInfo manualmente.

Il codice riportato di seguito verrà illustrate per entrambi questi casi, se l'RCW fornita dallo stub in mscorlib diversa o da un assembly di interoperabilità appropriato:

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)

Questo codice utilizza l'interfaccia IDispatch. L'interfaccia non è definita in un punto qualsiasi in .NET Framework, è necessario definirlo manualmente, come illustrato nella Figura 5 . A sinistra la funzione GetIDsOfNames vuoto perché non è necessaria per gli scopi correnti e sarà necessario includere una voce, tuttavia, poiché l'interfaccia dispone elencare il numero corretto di metodi nell'ordine corretto.

Nella figura 5 definizione dell'interfaccia 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

Ci si potrebbe chiedere perché IDispatch ha relativo attributo di InterfaceType impostare ComInterfaceType.InterfaceIsUnknown anziché impostare ComInterfaceType.InterfaceIsIDisapatch. Significa che l'attributo InterfaceType indica che l'interfaccia eredita da non che cos'è.

È necessario un ITypeInfo. Ora è tempo di partenza durante la lettura. Hanno un'occhiata figura 6 perché la funzione verrà implementata da Scarica le informazioni sul tipo per viene visualizzato sono. Per GetDocumentation, il primo parametro è un MEMBERID, ovvero Get­Documentation deve restituire le informazioni relative a ogni membro del tipo. Ma è anche possibile passare MEMBERID_NIL il cui valore-1, per ottenere informazioni relative al tipo stesso.

Nella 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

Si noti come funziona il marshalling. Quando si chiama typeInfo.GetTypeAttr, viene alloca un blocco di memoria non gestito e viene restituito all'utente la pTypeAttr puntatore. Quindi Marshal.PtrToStructure copiato in questo blocco non gestito in un blocco gestito (che quindi verrà sottoposto alla procedura di garbage collection). Pertanto, va bene chiamare immediatamente type­Info.ReleaseTypeAttr.

Come illustrato in precedenza, è necessario typeAttr per sapere come molti membri e implementate le interfacce sono (typeAttr.cFuncs, typeAttr.cVars e typeAttr.cImplTypes).

Ricerca dei riferimenti di tipo verso il basso

L'attività prima che deve essere completata consiste nell'ottenere un elenco di interfacce implementate o ereditate. (In COM, uno non eredita da un'altra classe). Di seguito è riportato il codice:

' 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

Non c'è un livello di riferimento indiretto qui. GetRefTypeOfImplType non consentono ITypeInfo dei tipi implementati direttamente: invece consente un handle a un ITypeInfo. La funzione GetRefType­Info è l'aspetto dei punto di manipolazione. È quindi possibile utilizzare GetDocumentation(-1) familiare per ottenere il nome del tipo implementato. Tratterò gli handle ITypeInfo in un secondo momento.

Recupero membri

Per eseguire la reflection sui membri di campo, ogni campo ha un VARDESC descrizione. Ancora una volta, l'oggetto typeInfo alloca pVarDesc un blocco di memoria non gestita, nonché il marshalling in varDesc un blocco gestito e rilasciare il blocco non gestito:

' 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

La funzione "GetNames" è sapere. Eventuali ogni membro potrebbe essere più nomi. È sufficiente solo per ottenere la prima.

Il codice per eseguire la reflection sui membri di funzione è in genere simile (vedere la Figura 7 ). Il tipo restituito è funcDesc.elemdescFunc.tdesc. Il numero di parametri formali è determinato dai funcDesc.cParams e parametri formali sono memorizzati in funcDesc.lprgelemdescParam la matrice. (Non è ottimale accede a una matrice non gestita simile al seguente da codice gestito poiché è necessario eseguire l'aritmetica dei puntatori.)

Nella figura 7 operando una reflection su membri funzione

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

Esistono altri flag nonché PARAMFLAG_FOUT, i flag per in retval, facoltativo e così via. Informazioni sul tipo per campi e i membri è stati memorizzati in una struttura TYPEDESC e richiamata una funzione DumpTypeDesc per stampare. Potrebbe sembrare surprising che TYPEDESC viene utilizzato al posto dell'interfaccia ITypeInfo. È possibile verrà elaborare successivamente.

Primitiva e tipi sintetico A

COM utilizza TYPEDESC per descrivere alcuni tipi e ITypeInfo per descrivere gli altri. Qual è la differenza? COM utilizza ITypeInfo solo per classi e interfacce definite in librerie dei tipi. E utilizza TYPEDESC per tipi primitivi quali Integer o String e anche per i tipi compositi quali array di SpVoice o riferimento IUnknown.

Questo è diverso dal .NET: innanzitutto, in .NET anche i primitivi tipi come valore integer e string sono rappresentate da classi o strutture mediante System.Type, in secondo luogo, in .NET il composti tipi come matrice di integer sono rappresentati tramite System.Type.

Il codice che occorre analisi tramite un TYPEDESC è abbastanza semplice (vedere la Figura 8 ). Nota che VT_USERDEFINED case nuovamente un handle per un riferimento, è necessario cercare tramite GetRefTypeInfo.

Nella figura 8 esaminando 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 rappresentazione COM del valore

Il passaggio successivo è effettivamente dump di un oggetto COM, vale a dire stampare i valori delle relative proprietà. Questa operazione è semplice se si conoscono i nomi di tali proprietà poiché è possibile utilizzare solo una chiamata ad associazione tardiva in Visual Basic:

Dim com as Object : Dim val = com.SomePropName

Il compilatore traduce questo in una chiamata di Common Language runtime di IDispatch:: Invoke per ottenere il valore della proprietà. Ma nel caso di reflection, non può sapere il nome della proprietà. Forse è MEMBER­ID, pertanto è necessario chiamare della IDispatch:: Invoke. Questa operazione non è molto breve.

Headache prima proviene dal fatto che COM e .NET rappresentano valori in modi molto diversi. In .NET è possibile utilizzare oggetti per rappresentare valori arbitrari. In COM è possibile utilizzare la struttura VARIANT, come illustrato nella Figura 9 .

Nella Figura 9 tramite 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 valore COM utilizza il campo vt per indicare il tipo è. Potrebbe essere VarEnum.VT_INT, VarEnum.VT_PTR o e dei tipi VarEnum 30 o in modo. Conoscere il tipo, è possibile scoprire quali gli altri campi per cercare in un'istruzione Select Case enorme. Fortunatamente, tale istruzione Select Case è già stato implementato nella funzione Marshal.GetObjectForNativeVariant.

Scaricare le proprietà di un oggetto COM

È opportuno Scarica le proprietà dell'oggetto COM, più o meno come la finestra Controllo immediato in 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 cosa è, esistono molti tipi diversi in COM. Sarebbe exhausting scrivere codice per gestire correttamente ogni singolo caso e difficile da assemblare sufficiente test case per verificare tutte le. È necessario liquidare per scaricare un piccolo insieme di tipi che è possibile sapere che possibile gestire correttamente.

E oltre, ciò che sarebbe utile dump? Oltre alle proprietà, anche sarebbe utile per ottenere un dump di qualsiasi elemento esposte tramite pure funzioni (effetto collaterale, liberare) ad esempio IsTall(). Ma non si desidera richiamare funzioni quali AddRef(). Per discriminate tra questi due, è possibile reckon che qualsiasi funzione nome, ad esempio " È * " è il gioco fiera per scaricare (vedere la Figura 10 ). Si scopre che i programmatori COM sembrano utilizzare È * funzioni molto meno frequente rispetto i programmatori che utilizzano .NET!

Nella figura 10 esaminando Get * e is * metodi

' 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

In questo codice, il tipo finale di tipo dumpable che è prendere in considerazione è un VT_PTR a un tipo VT_USERDEFINED. Ciò viene illustrato il caso comune di una proprietà che restituisce un riferimento a un altro oggetto.

Utilizzo IDispatch.Invoke

Il passaggio finale consiste nel leggere la proprietà è stato identificato dal relativo MEMBERID o richiamare la funzione. È possibile visualizzare il codice per eseguire nella Figura 11 . Di seguito il metodo chiave è IDispatch.Invoke. Il primo argomento è l'ID membro della proprietà o funzione è richiamare. DispatchType variabile è 2 per una proprietà get o 1 per un function-invoke. Se si sono stati chiama una funzione che accetta argomenti, sarà possibile inoltre impostare la struttura dispParams. Infine, il risultato viene restituita in varResult. Come prima, è possibile semplicemente chiamare Get­Object su di esso per convertire il VARIANT in un oggetto. NET.

Figura 11 della proprietà di lettura o la funzione di richiamo

' 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

Si noti la chiamata a Marshal.Release. Si tratta di un modello universale in COM che se una funzione mani di qualcuno un puntatore, chiama innanzitutto AddRef su di esso ed è responsabilità del chiamante a chiamare Release su di esso. In questo modo mi anche happier che .NET ha procedura di garbage collection.

Per inciso, Impossibile hanno utilizzato ITypeInfo.Invoke anziché IDispatch.Invoke. Ma viene ottenuto un po'di confusione. Si supponga di che avere una variabile, "COM", che punta all'interfaccia IUnknown di un oggetto COM. E si supponga che ITypeInfo del com sia un SpeechLib.SpVoice che dispongono di una proprietà con id membro non in 12. Si non chiamano direttamente ITypeInfo.Invoke(com,12), è necessario chiamare prima Query­Interface per ottenere l'interfaccia SpVoice di com, quindi chiamare ITypeInfo.Invoke sul. In conclusione, è più facile da utilizzare IDispatch.Invoke.

Ora aver visto come riflettere gli oggetti COM tramite IType­Info. Ciò è utile per le classi COM che non dispongono di tipi di interoperabilità. E si è visto come utilizzare IDispatch.Invoke per recuperare valori da COM, memorizzati in una struttura VARIANT.

È domandarsi sulla creazione di un intero wrapper ITypeInfo e TYPEDESC, che eredita da System.Type. Con questo, è possibile che gli utenti utilizzare lo stesso codice per la reflection su tipi COM come tipi di .NET. Ma alla fine, almeno per il progetto, questo tipo di wrapper è eccessiva lavoro per profitti trascurabile.

Per ulteriori informazioni su quali reflection procedere per l'utente, vedere" Dodge inconvenienti comuni di prestazioni per Craft Speedy applicazioni"e" Tutto su CLR: riflessi su Reflection."

Sincere grazie a Lippert Eric, Keserovic Sonja e Hsia Calvin per le informazioni in questa colonna.

Inviare domande e commenti instinct@Microsoft.com.

Wischik Lucian è il lead specifica Visual Basic. Dall'unione team del compilatore di Visual Basic ha lavorato nuove funzionalità relativi all'inferenza del tipo, lambdas e covarianza generica. Ha inoltre ha lavorato nel SDK Robotics e concorrenza e ha pubblicato diversi documenti Academic Search in base all'oggetto. Lucian contiene un PhD in teoria di concorrenza da università di Cambridge.