使用反射检查 COM 对象

JDBC为什么使用反射加载驱动 一直不太明白在使用JDBC的时候用Class.forName("com.mysql.jdbc.Driver")去加载驱动,就谷歌了一下,得到如下答案:前言JDBC源码地址 还有 在解释具体原因之前先简单看下Class.forName做了什么。假设一个类以前从来没有被装进内存过,Class.forName(String className)这个方法会做以下几件事情: 1、装载。将字节码读入内存, 阅读详情
Lucian Wischik
代码下载位置:MSDN 代码库
在线浏览代码
很多人在尝试让 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%/system32/speech/common/sapi.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 声明所示。
<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 函数保留为空,因为目前不需要使用它;但您需要加入一个有关它的条目,因为此接口必须按正确的顺序列出正确的方法数。
''' <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,用于获取有关类型本身的信息。
''' <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 中(从托管代码访问此类非托管数组通常不会很顺畅,因为您必须执行指针算法)。
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 进行查找。
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 所示。
''' <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 程序员少很多!
' 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 对象即可。
' 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 类型进行反射。但最终,至少是对我的项目而言,这种包装需要付出大量的工作而收益却微乎其微。
有关反射功能的详细信息,请参阅“ 避开常见的性能缺陷来创建高速应用程序”和“ CLR 全面透彻解析:反射之反思”。
诚挚地感谢 Eric Lippert、Sonja Keserovic 和 Calvin Hsia 对本专栏的大力协助。

 

请将您想询问的问题和提出的意见发送至 instinct@microsoft.com。

 

Lucian Wischik 是 Visual Basic 规范的发起人。自从加入 Visual Basic 编译器团队以来,他一致致力于研究与类型推断、lambda 和泛型协变有关的新功能。他还参与过 Robotics SDK 和并发操作的研究工作,发表过多篇有关该主题的学术文章。Lucian 拥有剑桥大学并发理论博士学位。
from:MSDN
C# 反射与动态编程 CSharp 反射与动态编程知识点 阅读详情

相关推荐

C# 通过反射调用com组件

C# 通过反射调用com组件,封装了几个简单的方法方便使用反射调用com。 var obj=new ComObj("excel.application"); obj["Visible"]=true; obj. InvokeMethod("Application.Workbooks.Open", fileName);

C#中COM操作---实例化

原文出处http://blog.csdn.net/bindsang/archive/2008/08/08/2788574.aspx用C#做WinForm程序,时间长了难免会遇到和COM组件打交道的地方,用什么方式创建COM对象也成了我们必须面对的一个问题.据我所知道的创建COM对象的方法一共有以下几种:1 使用.NET包装COM组件    这是最简单的就是导入COM组件所在的DLL,让ID

Mark的专栏 2343

用Codex自动化挖掘电梯行业外贸客户:从关键词到22维深度分析

在B2B外贸客户开发中,精准定位垂直行业潜在客户是核心挑战。传统手动搜索效率低下,而数据驱动的工作流正成为解决方案。其原理在于将模糊的业务需求转化为机器可识别的结构化查询,通过组合行业核心关键词(如“Elevator Manufacturer”、“Elevator Maintenance”)与商业角色描述,系统性地从海量数据中筛选目标。这种自动化抓取技术的价值在于,它不仅生成客户名单,更通过多维数据分析(如公司规模、业务标签、数字足迹)构建初步的客户画像,为后续的优先级排序和个性化触达提供情报基础。在工业制

weixin_34054866的博客 455

Microsoft Speech_Note 语音识别模块使用

现今语音识别已经做的十分成熟,微软的Cortana,苹果的Siri,还有讯飞、百度语音等。但是说到不花钱,单机运行还是Microsoft的Speech_Note语音识别系统比较省事。项目原因,最近做了个小的语音识别软件,一路心酸记录下来,以资备用。 开发平台:Visual Studio 2015 下载相关SDK首先,得下载两个包: 1)SpeechSDK51.exe

kiba_zwei的博客 5754

STC8G1K08A 实战应用:从配置到功能实现

本文详细介绍了STC8G1K08A单片机的实战应用,从开发环境搭建、GPIO配置、ADC模块使用到定时器中断处理和PWM输出等核心功能。重点讲解了外设模块的协同工作配置技巧,并分享了实际项目开发中的经验教训,帮助开发者快速掌握STC8G1K08A的高效应用。

n4o5p6q7r的博客 391

深入解析Dataloader中shuffle机制与随机种子对模型训练的影响

本文深入探讨了Dataloader中shuffle机制与随机种子对机器学习模型训练的影响。通过实际案例分析,详细解释了数据打乱顺序的必要性、随机种子的正确设置方法以及两者协同工作的原理,帮助开发者避免常见陷阱并提升模型训练效果。

weixin_29166013的博客 141

axis2生成wsdl回执参数首字母大小写问题

在跟局方对接接口的时候,局方回执我的wsdl接口,发现收不到同步回执,怀疑问题为回执参数首字母小写导致 代码中的参数对象首字母确实是大写,但生成的wsdl文件确变成了小写,目前是用axis2生成的 参考 :https://bbs.csdn.net/topics/390457284发现了变为小写的原因,选择使用xFire 并参考了https://blog.csdn.net/weixin_45326188/article/details/108371268 运行过程中发现: xfire-sp...

m0_37729339的博客 1029

Win10 / Win11 启用 Hyper-V 虚拟机详细教程(图文+命令行全攻略)

摘要:本文详细介绍Windows10/11系统启用Hyper-V虚拟化平台的方法,包含专业版/企业版的图形界面和PowerShell命令行操作,以及家庭版通过脚本强制启用的特殊方案。内容涵盖版本支持对比、操作步骤图解、常见问题解答和性能优化建议,帮助开发者快速搭建虚拟化环境进行多系统测试。附微软官方文档参考链接和详细操作示意图。

ElioXu 7068

通过反射调用COM对象

声明:原作者小李菜刀(caidaoli)通过反射调用COM对象 发一篇以前写的一个旧文,申请Blog好久了,要是再不发东西可就被Kill了。      在.net中使用Com对象大家第一个反应就是在工程中引用Com对象,让vs.net自动生成一个包装过的.net类库。这种方法虽然方便,但是有很明显的缺点,最致命的就是开发的机器上安装的Com对象的版本比客户机器上安装的高,开发的程序

comerliang的专栏 1294

【AUTOSAR-Nm】-2.2-通过CAN/Lin...信号报告Nm状态机的跳转

如何通过Can/Lin/Fr/Udp/J1939信号,获取每个通道对应状态机的跳转信息:“前一次状态”和“当前状态”。

「汽车电子助手」的博客 2464

SAP Group Reporting入门指南(1)--开篇及系统环境搭建

文章介绍了SAP GR的发展历程、与BPC的对比优势,数据收集的多种方式(实时同步、外部导入等)以及合并整体流程,并推出《SAP Group Reporting入门指南》专栏。

bflyfish的博客 282

[翻译] python Tutorial 之四

Tutorial 3: IronPython 和 COM 互操作性COM 互操作性是.NET 框架的重要部分. 为在.NET应用中使用COM 对象, 要求一个包含.NET metadata的COM对象的interop编译.本tutorial 将会描绘如创建interop assemblies的轮廓 (在这个情况下,它们不是被COM 对象构造者所提供) ,同时会示范在IronP

daizhj的专栏 943

SAP UI5 控件剖析系列之 sap.m.DatePicker:单日选择的工程化用法、最佳实践与常见坑

在 SAP UI5 的输入控件族里,是最常被使用的日期选择控件。它把一个可编辑的输入框与一个弹出的日历整合在一起,允许用户用触摸、鼠标或键盘在本地化格式下选择某一天;控件既可以从弹出的日历里点选,也可以直接在输入框里键入日期。官方 Fiori 设计规范对它的定位就是:完成单个日期的选择,不牵涉时间,也不负责跨天的范围选择。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 343

Exposing COM Components to the .NET Framework之二Create COM types in managed Code.

Using COM Types in Managed Code COM types defined in an assembly are like any other managed type. Managed clients can create a new instance of a COM type in the usual way and obtain class informati...

weixin_30449239的博客 144

详解【计算机类&面试真题】军队文职考试——第7期:C盘格式化需要注意什么?| 两台笔记本连起来后ping不同,可能存在哪些问题?| 模拟信号到数字信号是如何转化的?| 计算机由哪些部件组成?

这是我分享的系列面试知识——军队文职(计算机类)面试真题详解(第7期)。本期分享:两台笔记本连起来后ping不同,你觉得可能存在哪些问题?模拟信号到数字信号是如何转化的?谈一谈计算机由哪些部件组成?如何加强计算机网络安全管理?若通过域名无法访问,而通过IP地址能访问。这是怎么回事?你将如何解决?谈一谈C盘格式化需要注意什么?开放类题目:如果应聘的岗位和你想的差距很远,你怎么办?

追光者♂:记录、分享、总结、提升,现象级专栏《Python从入门到人工智能》作者,无惧黑暗,坚信曙光 1922

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C#中反射和动态关键字有什么区别?

目录 介绍 什么是反射以及我们为什么需要它? 我们如何实现反射? 第1步 第2步 反射的实际用途是什么? Dynamic关键字的用途是什么? Dynamic关键字的实际用途是什么? 反射和Dynamic有什么区别? 介绍 在本文中,我们将尝试理解两个术语:反射和dynamic关键字。许多开发人员对它们感到困惑,因为它们都帮助我们进行动态调用。 在本文中,我们将尝解释差异,看看我们将在哪些场景中使用她们。不过在开始对比之前,我们先分别了解一下,然后在文章的最后再做一个全面的对比。 .

寒冰屋的专栏 257

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

第十六章 反射与注解

如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。所有 Java 类均继承了 bjet 类,在 Object 类中定义了一个 getClass0方法,该回一个类型为Class的对象。利用Class类的对象textFieldC,可以访问用来返回该对象的 textField 对象的描述信息。可的主要描述信息如表16.1所示。例如,判断对象constructor 所代表的构造方法是否被 private 修饰,以及以字符形式获的该构造方。如果是访问指定的成员变,可以通过该成员变量的名称来访问。

2302_76534780的博客 112

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Java反射API:Class对象、Constructor、Method与Field操作(四)

反射API作为一种强大的编程工具,其核心功能与价值体现在动态类型检查、元数据访问、动态创建和跨语言互操作等方面。在实际项目中,应合理运用反射技术,兼顾灵活性、性能、安全与可读性。随着编程技术的发展,反射将在更多高级主题和扩展内容中发挥重要作用,值得开发者持续关注和探索。

m0_61635718的博客 1400

使用C#中的反射从字符串获取属性值

我正在尝试在代码中使用Reflection 1示例实现数据转换 。 GetSourceValue函数具有比较各种类型的开关,但是我想删除这些类型和属性,并让GetSourceValue仅使用

p15097962069的博客 1726

在C++实现反射

<br />反射这个特性在C++中是没有的。所谓反射,自己的认为就是通过一个名字就可创建、调用、获取信息等一系列的操作,这个在脚本语言里面是比较常见的,COM组件也类似,知道个ID名,就可以做很多的工作了。<br />看看JAVA中的描述:<br />JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为JAVA语言的反射机制。<br />JAVA反射机制主要提供了以下功能:在运行时判

七彩狻猊 1万+

利用反射调用COM组件

最常见的调用com组件的方法就是添加对这个组件的引用,但是这会在最终可执行文件边生成一个伴随的dll如MyCom.Interop.dll等,这给程序的分发带来相当程度的不便,而且添加引用是我们只能引用某个特定版本的com组件,而我们往往无法确定程序所运行的机器上是否有相应版本的com组件,这可能造成我们的程序无法运行。我们要如何解决这个问题呢?        首先要解决com组件的版本问题,

xyj052的专栏 899

Alpha 对象检查器:深入 iOS 运行时对象分析的强大工具

Alpha 对象检查器是 iOS 调试框架 Alpha 的核心功能之一,它提供了**运行时对象分析**的强大能力,让开发者能够实时查看和修改应用中的任何对象。无论您是新手还是经验丰富的 iOS 开发者,这个工具都能显著提升调试效率 🚀。 Alpha 是下一代 iOS 调试框架,它通过**运行时反射机制**让您能够深入探索应用中的对象结构、属性和方法。对象检查器作为 Alpha 框架的重要组成部

gitblog_00979的博客 883

反射机制之Class对象

反射 * 反射:将类的各个组成部分封装为其他对象,这就是反射机制 * 优点: 1. 可以在程序运行过程中,操作这些对象。 2. 可以解耦,提高程序的可扩展性。 获取Class对象的方式: 1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 * 多用于配置文件,将类名定义在配置文件中。读取文件,加载类 2. 类名.class:通过类名的属性cla...

Eamon 598

Java反射学习总结二(用反射调用对象的私有属性和方法)

大家都知道正常的方法和属性调用是不可以访问private

大苞米的专栏 3909

利用反射创建User类的对象

package com.bjpowernode; public class User { private int age; public String name; public void m1() { System.out.println("我是m1方法"); }} ---------------------- package com.bjpowernode; /** *...

a8153285的博客 480

[原]反射学习整理

一、MSDN上面找的学习资料 反射提供了封装程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。 反射通常具有以下用途: · 使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的...

weixin_33834910的博客 266
上一篇: 嗨,Scripting Guy!脚本存档
下一篇: VB6结合GDI+实现内存(Stream)压缩/解压缩JPG(JPEG)图像
迈克揉索芙特
博客等级 码龄24年 865粉丝 71原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值