程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> Visual Basic語言 >> VB.NET >> Visual Basic:運用反射反省COM對象

Visual Basic:運用反射反省COM對象

編輯:VB.NET

Visual Basic:運用反射反省COM對象。本站提示廣大學習愛好者:(Visual Basic:運用反射反省COM對象)文章只能為提供參考,不一定能成為您想要的結果。以下是Visual Basic:運用反射反省COM對象正文


本文配套源碼

內容

類型庫和運轉時可調用包裝

當某個類型短少 RCW 時

運用 ITypeInfo

找出類型援用

取得成員

基元類型和綜合類型

值的 COM 表示方式

轉儲 COM 對象的屬性

運用 IDispatch.Invoke

討論

很多人在嘗試讓 COM 發揚作用時都有點受挫的覺得 。當然在成功時,也會感到興奮無比。在理解對象的任務原理時,常常需求費一番周折的是運用 Microsoft .NET Framework 的反射功用對其停止反省。在某些狀況下,.NET 反射還會對 COM 對象起作 用。看看上面的代碼您就會明白我的意思。此代碼運用 .NET 反射來獲取並顯示該對象中的成員列表

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

並在控制台中發生以下輸入:

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

但此代碼並不是對一切 COM 對象都起作用。對有些對象,必需使 用 COM 反射。本專欄將為大家引見其緣由以及完成方式。

為什麼想要對某個對象運用反射?我 發現反射關於調試和記載十分有用;您可以運用它來編寫通用“轉儲”例程,以輸入關於某 個對象的一切內容。本專欄中的代碼足以讓您可以編寫自己的“轉儲”例程。編寫完成後, 您甚至可以在調試時從即時窗口中對其停止調用。由於 Visual Studio 調試器並不是一直都提供有關 COM 對象的足夠多信息,因而這一點十分有用。

關於消費運用,假如您編寫的使用順序采用插件 組件,並且用戶將其組件放置在某個目錄中或將其列在注冊表中,而您的使用順序必需反省這些組件並 找出它們所地下的類和辦法,那麼反射也十分有用。例如,Visual Studio 經過這種方式運用反射來填 充 IntelliSense。

類型庫和運轉時可調用包裝

讓我們構建一個項目以供闡明之用。首先 ,創立項目並經過“Project”(項目)>“AddReference”(添加援用)命令 添加一個 COM 援用。在本專欄中,我將運用 "Microsoft Speech Object Library" SpeechLib。圖 1 顯示了在運轉您先前看到的反射代碼時需求反省的相關實體和文件。

圖 1 關於 SpeechLib 的反射

Sapi.dll 是包括 SpeechLib 的 DLL。它恰恰位於 %windir% system32speechcommonsapi.dll 中。此 DLL 不但包括 SpVoice COM 類的完成,還包括一個 TypeLibrary(其中包括它的一切反射信息)。雖然 TypeLibrarie 是可選的,但零碎中的簡直一切 COM 組件都會有一個。

Interop.SpeechLib.dll 是 Visual Studio 經過“Project”(項 目)>“AddReference”(添加援用)命令自動生成的。此生成器將反射 TypeLibrary 並 為 SpVoice 生成一個互操作類型。此類型是一個托管類,其中含有在 TypeLibrary 中找到的每個本機 COM 辦法的托管辦法。您也可以運用 Windows SDK 中的 tlbimp.exe 命令行工具自己生成互操作順序集 。互操作類型的實例被稱為“運轉時可調用包裝”(Runtime Callable Wrapper, RCW),它封 裝了一個指向 COM 類實例的指針。

運轉以下 Visual Basic 命令將創立一個 RCW(互操作類型 的實例)以及 SpVoice COM 類的一個實例:

Dim b As New SpeechLib.SpVoice

變量 "b" 會援用 RCW,因而當代碼反射 "b" 時, 它實踐上反射的是從 TypeLibrary 結構的托管等效項。

部署 ConsoleApplication1.exe 的用戶 還必需部署 Interop.SpeechLib.dll。(但是,Visual Studio 2010 將允許互操作類型直接在 ConsoleApplication1.exe 外部停止復制。這將大大簡化部署進程。此功用被稱為“無次要互操作 順序集”(No-Primary-Interop-Assembly) 或簡稱為 "No-PIA"。)

當某個類型 短少 RCW 時

假如您沒有 COM 對象的互操作順序集,這時該怎樣辦?例如,假如您經過 CoCreateInstance 創立了 COM 對象自身,或許假如像往常一樣,您調用了 COM 對象的一個辦法,而它 前往了一個事前並不知道其類型的 COM 對象,這時該怎樣辦?假如您為非托管使用順序編寫了一個托管 插件,而該使用順序為您提供了一個 COM 對象,這時該怎樣辦?假如您經過通查注冊表發現了要創立的 COM 對象,這時該怎樣辦?

每種狀況都將為您提供對 COM 對象的 IntPtr 援用,而不是對其 RCW 的對象援用。當您圍繞該 IntPtr 懇求 RCW 時,您將取得圖 2 中所示的內容。

圖 2 取得運轉時 可調用包裝

在圖 2 中,您將會看到 CLR 提供了一個默許 RCW,即默許互操作類型 "System.__ComObject" 的實例。假如按如下方式反射此內容

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

您將會發現它沒有任何 對您有用的成員,它只包括以下內容:

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

要獲取此類 COM 對象的有用反射,必需自行反射其 TypeLibrary。您可以運用 ITypeInfo 來完成此操作。

但首先要提示您留意:假如某個辦法返給您一個 Object、Idispatch、 ITypeInfo 或其他 .NET 類或接口,則標明它已為您提供了對 RCW 的援用,而 .NET 將擔任為您釋放它 。但假如該辦法返給您一個 IntPtr,則意味著您有一個對 COM 對象自身的援用,而您簡直無法防止地 必需要在此對象上調用 Marshal.Release(這取決於為您提供該 IntPtr 的辦法的准確語義)。命令如 下:

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

但更為罕見的是運用封送處置聲明此函數,這樣封送 拆收器就會自動調用 GetObjectForIUnknown 和 Release,如圖 3 中的 CoCreateInstance 聲明所示。

圖 3CoCreateInstance

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

運用 ITypeInfo

ITypeInfo 等效於 COM 類和接口中的 System.Type。運用它您可以枚舉某個類 或接口的成員。在本例中,我計劃輸入它們;但是,您可以運用 ITypeInfo 在運轉時查找成員,然後調 用它們或經過 Idispatch 獲取其屬性值。圖 4 顯示了 ITypeInfo 應該如何使用以及您將需求運用的所 有其他構造。

圖 4 ITypeInfo 和類型信息

第一步是獲取給定 COM 對象的 ITypeInfo。假如您可以使 用 rcw.GetType(),那就更好了,但是需求留意的是,這會前往有關 RCW 自身的 System.Type 信息。 假如可以運用內置函數 Marshal.GetITypeInfoForType(rcw),那也沒有任何問題,但遺憾的是,這只對 來自互操作順序集的 RCW 起作用。因而,您必需手動獲取 ITypeInfo。

以下代碼對這兩種狀況 均無效,無論 RCW 是來自 mscorlib 中的存根,還是來自適當的互操作順序集:

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)

此代碼 運用 IDispatch 接口。此接口未在 .NET Framework 中的任何中央定義,因而您必需自己定義它,如圖 5 所示。我將 GetIDsOfNames 函數保存為空,由於目前不需求運用它;但您需求參加一個有關它的條目 ,由於此接口必需按正確的順序列出正確的辦法數。

圖 5 定義 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

您能夠想知道為什麼 IDispatch 將其 InterfaceType 屬性設置為 ComInterfaceType.InterfaceIsUnknown,而不是設置為 ComInterfaceType.InterfaceIsIDisapatch。這是由於 InterfaceType 屬性表示的是該接口的承繼來源 ,而不是表示它終究是什麼。

您有一個 ITypeInfo。如今是讀取它的時分了。請看一下圖 6,其 中顯示了我將要完成用來轉儲類型信息的函數。關於 GetDocumentation,第一個參數是 MEMBERID,即 GetDocumentation 的用處是前往有關該類型的每個成員的信息。但您也可以傳入 MEMBERID_NIL,它的 值為 -1,用於獲取有關類型自身的信息。

圖 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

請留意封送處置的任務原理。當調用 typeInfo.GetTypeAttr 時,它會分配一個非托管內 存塊並為您前往指針 pTypeAttr。然後 Marshal.PtrToStructure 將從這一非托管塊復制到托管塊中( 之後它將被作為渣滓回收)。因而,最好立刻調用 typeInfo.ReleaseTypeAttr。

如前所示,您 需求運用 typeAttr 來理解終究有多少成員和已完成的接口(typeAttr.cFuncs、typeAttr.cVars 和 typeAttr.cImplTypes)。

找出類型援用

必需完成的第一個義務是獲取已完成/承繼接口 的列表。(在 COM 中,一個類絕不會承繼自另一個類)。以下是相關代碼:

' 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

這裡有一個 直接層。GetRefTypeOfImplType 不會直接為您提供所完成類型的 ItypeInfo:相反,它會為您提供 ItypeInfo 的句柄。函數 GetRefTypeInfo 的作用就是查找該句柄。然後,您可以運用相似的 GetDocumentation(-1) 來獲取該完成類型的稱號。稍後我會再次討論 ITypeInfo 的句柄。

取得 成員

關於字段成員的反射,每個字段都有一個 VARDESC 來描繪它。異樣,typeInfo 對象會分配 一個非托管內存塊 pVarDesc,然後您需求將其封送到托管塊 varDesc 並釋放該非托管塊:

' 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

函數 "GetNames" 比擬奇異。可以想像,每個成員能夠擁有多個稱號。但 只需獲取第一個稱號就足夠了。

反射函數成員的代碼通常很類似(請參見圖 7)。前往類型為 funcDesc.elemdescFunc.tdesc。形參的數量由 funcDesc.cParams 指定,形參均存儲在數組 funcDesc.lprgelemdescParam 中(從托管代碼訪問此類非托管數組通常不會很順暢,由於您必需執行指 針算法)。

圖 7 函數成員的反射

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

還有其他標志以及 PARAMFLAG_FOUT——用於 in、retval、optional 等的標志。字段和成員的類型信息都存儲在 TYPEDESC 構造中,我經過調用函數 DumpTypeDesc 來輸入 它。運用 TYPEDESC 而不運用 ItypeInfo,這似乎有些令人詫異。上面我將對此詳加論述。

基元 類型和綜合類型

COM 運用 TYPEDESC 來描繪某些類型,而運用 ITypeInfo 來描繪其他類型。這 有何區別?COM 僅對在類型庫中定義的類和接口運用 ITypeInfo。它對基元類型(如整數型或字符串) 以及復合類型(如 SpVoice 數組或 IUnknown 援用)運用 TYPEDESC。

這與 .NET 是不同的:首 先,在 .NET 中,即便是基元類型(如整數型和字符串)也是由類或構造經過 System.Type 來表示的; 其次,在 .NET 中,復合類型(如 Integer 數組)是經過 System.Type 來表示的。

您需求在 TYPEDESC 中深化發掘的代碼十分復雜(請參見圖 8)。請留意,VT_USERDEFINED 案例再次運用了某個 援用的句柄,它必需經過 GetRefTypeInfo 停止查找。

圖 8 檢查 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

值的COM 表示方式

下一步是實踐轉儲 COM 對象,即輸入其屬性 的值。假如知道這些屬性的稱號,則此義務會十分復雜,由於您可以只運用前期綁定調用:

復制代碼

Dim com as Object : Dim val = com.SomePropName

編譯器會將其轉換成 IDispatch::Invoke 的運轉時調用,以提取屬性的值。但關於反射,您能夠不知道屬性稱號。或許您所 掌握的只是 MEMBERID,因而必需自行調用 IDispatch::Invoke。這並不是很方便。

第一個頭疼 的問題源於這樣一個現實,即 COM 和 .NET 表示值的方式大相徑庭。在 .NET 中,運用 Object 來表示 恣意值。而在 COM 中,運用的是 VARIANT 構造,如圖 9 所示。

圖 9 運用 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

COM 值運用 vt 字段來表示其類型。 它能夠是 VarEnum.VT_INT 或 VarEnum.VT_PTR,也能夠是 30 個左右的 VarEnum 類型中的任何一個。 知道其類型後,您可以在少量的 Select Case 語句中指出要查找的其他字段。僥幸的是,Select Case 語句曾經在 Marshal.GetObjectForNativeVariant 函數中完成。

轉儲COM對象的屬性

您能夠會希望轉儲 COM 對象的屬性,或多或少相似於 Visual Studio 中的“Quick Watch” (疾速監視)窗口:

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

問題是 COM 中存在許多 不同的類型。經過編寫代碼來正確處置每個案例會讓人精疲力竭,而且很難集合足夠的測試案例停止全 面的測試。上面我只轉儲一小組類型,而且我知道我能正確處置它們。

除此之外,還有什麼會有 助於轉儲呢?除了屬性以外,經過純(無反作用)函數(如 IsTall())將轉儲內容地下也會十分有用。 但您能夠不希望調用 AddRef() 之類的函數。要區分這兩種狀況,我以為任何函數稱號(如 "Is*")都是在轉儲時要思索的要素(請參見圖 10)。現實標明,COM 順序員運用 Is* 函數 的頻率似乎比 .NET 順序員少很多!

圖 10 檢查 Get* 和 Is* 辦法

' 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

在此代 碼中,您思索的最後一種可轉儲類型是 VT_PTR 到 VT_USERDEFINED 類型。通常狀況下這會觸及某個屬 性(此屬性將前往對其他對象的援用)。

運用 IDispatch.Invoke

最後一個步驟是讀取已 經過其 MEMBERID 標識的屬性或調用該函數。您可以看到圖 11 中的代碼完成了這一點。此處的關鍵方 法是 IDispatch.Invoke。它的第一個參數是屬性的成員 id 或您所調用的函數。變量 dispatchType 是 2(關於 property-get)或 1(關於 function-invoke)。假如您調用了承受參數的函數,則還需設置 dispParams 構造。最後,後果將在 varResult 中前往。像以前一樣,您只需對其調用 GetObject 並將 VARIANT 轉換為 .NET 對象即可。

圖 11 讀取屬性或調用函數

' 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

請留意對 Marshal.Release 的調用。COM 中的通用形式是,假如某個函數向某人傳 遞指針,則它首先會對其調用 AddRef,然後由調用方擔任對其調用 Release。.NET 的渣滓搜集功用可 以讓我省很多事。

特地說一下,我原本可以運用 ITypeInfo.Invoke 來替代 IDispatch.Invoke 。但它有點讓人迷惑。假定您有一個變量 "com",它指向 COM 對象的 IUnknown 接口。假定 com 的 ITypeInfo 為 SpeechLib.SpVoice,它恰恰有一個屬性的 member-id 為 12。您不能直接調用 ITypeInfo.Invoke(com,12);必需先調用 QueryInterface 來獲取 com 的 SpVoice 接口,然後再對其 調用 ITypeInfo.Invoke。最後一點,運用 IDispatch.Invoke 會更容易一些。

如今您曾經看到 了如何經過 ITypeInfo 來反射 COM 對象。這關於短少互操作類型的 COM 類十分有用。而且您也理解了 如何運用 IDispatch.Invoke 來從 COM 檢索存儲在 VARIANT 構造中的值。

我的確思索過圍繞 ITypeInfo 和 TYPEDESC(承繼自 System.Type)創立一個完好的包裝。經過它,用戶可以運用與 .NET 類型相反的代碼對 COM 類型停止反射。但最終,至多是對我的項目而言,這種包裝需求付出少量的任務 而收益卻微乎其微。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved