基于IDispatch的COM对象的反射

目录

介绍

背景

使用代码

兴趣点


介绍

.NET的反射API提供有关托管类型的属性、方法和事件的丰富信息。但是,它不适用于非托管COM类型。COM最接近反射的是IDispatch返回ITypeInfo的能力,并且.NET的反射API不会自动为基于IDispatchCOM对象使用ITypeInfo。通常可以获得丰富的类型信息,但是通过使用.NET内置的TypeToTypeInfoMarshaler自定义IDispatch声明需要一些额外的工作。

背景

如果您正在使用引用了互操作程序集(例如,PIA或由TlbImp.exe生成的程序集)的强类型COM对象,则可以通过运行时可调用包装器的反射自动获得丰富的类型信息。但是,如果您刚刚被传递了一个未知类型的对象(例如,由非托管代码或由 Activator.CreateInstance创建的对象),那么对其使用反射可能会令人失望。如果对象是非托管COM对象,则默认反射结果将针对该System.__ComObject类型,该类型是在mscorlib.dll中定义的内部类型。例如:

Type fsoType = Type.GetTypeFromProgID("Scripting.FileSystemObject");
object fso = Activator.CreateInstance(fsoType);
Console.WriteLine("TypeName: {0}", fso.GetType().FullName);
foreach (MemberInfo member in fso.GetType().GetMembers())
{
    Console.WriteLine("{0} -- {1}", member.MemberType, member);
}

产生输出:

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

获取.NET System.__ComObject的类型信息很少有用。从底层COM对象的IDispatch实现中获取类型信息要好得多,但这需要更多的工作。该类DispatchUtility(在附加的示例代码中)使用IDispatch的接口ID (IID)私下声明了IDispatchInfo接口,但它只声明了IDispatch的前三个方法,因为这就是我们获取类型信息所需的全部内容:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00020400-0000-0000-C000-000000000046")]
private interface IDispatchInfo
{
    // Gets the number of Types that the object provides (0 or 1).
    [PreserveSig]
    int GetTypeInfoCount(out int typeInfoCount);

    // Gets the Type information for an object if GetTypeInfoCount returned 1.
    void GetTypeInfo(int typeInfoIndex, int lcid,
        [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef =
        typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))]
        out Type typeInfo);

    // Gets the DISPID of the specified member name.
    [PreserveSig]
    int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, 
        out int dispId);

    // NOTE: The real IDispatch also has an Invoke method next, but we don't need it.
}

.NET收集和转换类型信息的实际工作是由IDispatchInfo.GetTypeInfo的最后一个参数上的TypeToTypeInfoMarshaler处理的。原始IDispatch接口(在 Windows SDKOAIdl.idl文件中声明)使用.NET ITypeInfo的输出声明GetTypeInfo,但.NET TypeToTypeInfoMarshaler会将其转换为丰富的.NET类型实例。

IDispatchInfo还提供了一次仅适用于一个名称的IDispatch.GetIDsOfNames简化。相反,它声明了该GetDispId方法,并调整了参数声明,以便它们对单个ID和名称正确工作。

IDispatchInfo省略第四种IDispatch方法(即Invoke),因为在.NET中已经有几种方法可以进行动态调用(例如,通过Type.InvokeMember使用"[DISPID=n]"语法或通过C#dynamic关键字)。由于前三种方法为类型信息和DISPID提供了元数据发现,所以它们就是我们真正需要的。

注意:从现有的非托管COM对象请求信息时,使用此IDispatch部分实现是安全的,因为C#编译器会生成这个接口的vtable,与IDispatch接口的前三个方法相同。但是,在托管对象上实现IDispatchInfo并将其传递给任何需要真正IDispatch的非托管代码是安全的。 如果只有部分IDispatch vtable,如果试图使用后面的vtable成员(Invoke),各种各样的坏情况都可能发生(例如,访问冲突和内存损坏)。这就是在DispatchUtility中将IDispatchInfo声明为private嵌套接口的原因之一。

使用代码

DispatchUtility类提供了static方法以检查对象是否实现IDispatch、获取.NET类型信息、获取DISPID以及通过名称或DISPID动态调用成员的方法。

public static class DispatchUtility
{
    // Gets whether the specified object implements IDispatch.
    public static bool ImplementsIDispatch(object obj)  { ... }

    // Gets a Type that can be used with reflection.
    public static Type GetType(object obj, bool throwIfNotFound) { ... }

    // Tries to get the DISPID for the requested member name.
    public static bool TryGetDispId(object obj, string name, out int dispId) { ... }

    // Invokes a member by DISPID.
    public static object Invoke(object obj, int dispId, object[] args) { ... }

    // Invokes a member by name.
    public static object Invoke(object obj, string memberName, object[] args) { ... }
}

我们可以修改前面的示例代码来使用DispatchUtility.GetType(fso, true)代替fso.GetType()

Type fsoType = Type.GetTypeFromProgID("Scripting.FileSystemObject");
object fso = Activator.CreateInstance(fsoType);
Type dispatchType = DispatchUtility.GetType(fso, true);
Console.WriteLine("TypeName: {0}", dispatchType.FullName);
foreach (MemberInfo member in dispatchType.GetMembers())
{
    Console.WriteLine("{0} -- {1}", member.MemberType, member);
}

这会产生以下输出:

TypeName: Scripting.IFileSystem3
Method -- Scripting.Drives get_Drives()
Method -- System.String BuildPath(System.String, System.String)
Method -- System.String GetDriveName(System.String)
Method -- System.String GetParentFolderName(System.String)
Method -- System.String GetFileName(System.String)
Method -- System.String GetBaseName(System.String)
Method -- System.String GetExtensionName(System.String)
Method -- System.String GetAbsolutePathName(System.String)
Method -- System.String GetTempName()
Method -- Boolean DriveExists(System.String)
Method -- Boolean FileExists(System.String)
Method -- Boolean FolderExists(System.String)
Method -- Scripting.Drive GetDrive(System.String)
Method -- Scripting.File GetFile(System.String)
Method -- Scripting.Folder GetFolder(System.String)
Method -- Scripting.Folder GetSpecialFolder(Scripting.SpecialFolderConst)
Method -- Void DeleteFile(System.String, Boolean)
Method -- Void DeleteFolder(System.String, Boolean)
Method -- Void MoveFile(System.String, System.String)
Method -- Void MoveFolder(System.String, System.String)
Method -- Void CopyFile(System.String, System.String, Boolean)
Method -- Void CopyFolder(System.String, System.String, Boolean)
Method -- Scripting.Folder CreateFolder(System.String)
Method -- Scripting.TextStream CreateTextFile(System.String, Boolean, Boolean)
Method -- Scripting.TextStream OpenTextFile
          (System.String, Scripting.IOMode, Boolean, Scripting.Tristate)
Method -- Scripting.TextStream GetStandardStream(Scripting.StandardStreamTypes, Boolean)
Method -- System.String GetFileVersion(System.String)
Property -- Scripting.Drives Drives

使用DispatchUtility.GetType,我们可以获得丰富的类型信息,例如属性和方法的详细信息以及一个好的接口类型名称。这比获得System.__ComObject的成员要好得多。这是有效的,因为类型库是为我们正在使用的COM类型注册的,所以当DispatchUtility内部调用IDispatch.GetTypeInfo时,它能够返回一个ITypeInfo。然后.NET TypeToTypeInfoMarshalerITypeInfo转换为.NET类型。

如果该IDispatch.GetTypeInfo方法不能返回一个ITypeInfo(例如,如果没有为该对象注册类型库),那么我们将无法获得.NET类型实例。这个限制影响了大多数实现IDispatchEx成员可以在运行时动态添加和删除的“expando”对象(例如,JScript对象)。通常,ITypeInfo只会返回静态类型信息,因此不会为基于IDispatchEx对象的对象报告动态添加的成员。

DispatchUtility类在单个文件中实现,因此很容易集成到现有项目中。该类只需要对System.dllCustomMarshalers.dll的程序集引用,它们是核心.NET Framework的一部分。它应该适用于任何CPU”.NET 2.0或更高版本。在.NET 4.0或更高版本上,不需要LinkDemands for UnmanagedCode权限,可以忽略或删除。

兴趣点

网络上的许多文章错误地说必须引用COM互操作程序集才能在.NET中获取丰富的类型信息,例如Microsoft支持文章320523StackOverflow帖子如何在C#中枚举COM对象的成员?获取属性名称”通过COM对象的反射。一些更高级的文章说您应该直接使用ITypeInfo,例如使用反射检查COM对象从托管代码中获取基于IDispatch的COM对象的类型信息。不幸的是,直接使用ITypeInfo涉及大量手动互操作工作,并且它没有给你一个System.Type实例。但是,使用TypeToTypeInfoMarshaler,正如本文中所讨论的,它更容易,它以标准的.NET类型格式提供丰富的类型信息。

https://www.codeproject.com/Articles/523417/Reflection-with-IDispatch-based-COM-objects

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值