zoukankan      html  css  js  c++  java
  • 在C# 中枚举COM对象的方法和属性名称

    以前满世界问过这个问题,没有人理偶的说,还是自己动手搞定比较好。

    一般来说,一个COM对象在提供的时候,通常还会提供一个类型库,在其中定义了COM对象的所有方法名称、参数名称、属性名称等等信息。我们要做的就是从类型库中取出这些信息。
    当然,某些只供C++程序员使用的COM对象没有类型库,而代之以C++的头文件和/或idl文件,对这种情况,一般没有办法在程序中枚举出对象的方法属性:毕竟去找C++头文件不太现实,何况在非开发环境下,根本就没有头文件的说。
    因此,我们将讨论当COM对象存在TypeLib的情况下,枚举方法/属性名称的问题。
    从COM对象定位到TypeLib
    在一般情况下,COM对象的TypeLib信息存储在注册表中:在HK_CLASSROOT\CLSID\{ClassID}\的注册表项下,有一个名为TypeLib的子项,其中定义了这个COM对象类型库的ID;而在HK_CLASSROOT\TypeLib 注册表项下,列举了系统中所有TypeLib。
    看看我们首先要做什么:从ProgID 取得 ClassID,这个工作可以通过调用COM 基础库的 CLSIDFromProgID 函数来完成,在Platform SDK中,该函数的定义如下:

    HRESULT CLSIDFromProgID(
      LPCOLESTR lpszProgID,
      LPCLSID pclsid

    );

    为了在.Net中使用这个函数,我们用DllImport Attribute 把这个函数引入.Net 中:


    class UnsafeNativeMethods{
    [DllImport("ole32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
    public static extern void CLSIDFromProgID([In,MarshalAs(UnmanagedType.BStr)] string lpszProgID,[Out]out Guid pclsid);
    ………

    然后,我们可以在.Net 中调用这个函数取得ClassID了:


    Guid clsid;
    UnsafeNativeMethods.CLSIDFromProgID(progID,out clsid);

    OK, 升级宝物Class ID 入手,Level Up!Strength + 3, Life + 5,必杀技 dll import 习得。 :) 下一个任务:取得TypeLib。
    l         取得TypeLib。
    为访问TypeLib,COM 提供了二个接口:ITypeLib 和 ITypeInfo,其中ITypeLib 提供对 TypeLib 的访问,而ITypeInfo 则表示TypeLib中定义的某一项ITypeInfo。
    要获得ITypeInfo,COM有二个函数可以做这件事情:LoadTypeLib 和 LoadRegTypeLib。其中 LoadTypeLib 需要 TypeLib 文件的路径作为参数,而LoadRegTypeLib 则根据TypeLib的TypeLib ID和TypeLib的版本号取得 ITypeLib。在这里,我们用LoadRegTypeLib来取得ITypeLib 接口。
    先来准备需要的参数:TypeLibID和TypeLib的版本号,这些信息需要从注册表里得到:


    RegistryKey regKey = Registry.ClassesRoot;
    regKey = regKey.OpenSubKey("CLSID\\{" + clsid.ToString() + "}\\TypeLib");
    Guid typeLibID = new Guid(regKey.GetValue("").ToString());
    //Get TypeLib Versions
    short iMajorVer,iMinusVer;
    regKey = Microsoft.Win32.Registry.ClassesRoot;
    regKey = regKey.OpenSubKey("TypeLib\\{" + typeLibID.ToString() + "}");
    string[] aryTemp = regKey.GetSubKeyNames();
    string sVersion = aryTemp[0];
    aryTemp = sVersion.Split('.');
    iMajorVer = short.Parse(aryTemp[0],System.Globalization.NumberStyles.AllowHexSpecifier);
    iMinusVer = short.Parse(aryTemp[1] ,System.Globalization.NumberStyles.AllowHexSpecifier);

    这里要注意一点:在注册表里记录的TypeLib版本号是以十六进制格式表示的,运气好的话,你会发现类似”1.a”之类的版本号,所以我们最好把它们看成16进制来转换。
    现在可以调用LoadRegTypeLib 了,和CLSIDFromProgID一样,先import进来:
    这是LoadRegTypeLib 的函数原型:

    HRESULT LoadRegTypeLib( 
      REFGUID  rguid,             
      unsigned short  wVerMajor,  
      unsigned short  wVerMinor,  
      LCID  lcid,                 
      ITypeLib FAR* FAR*  pptlib  

    );

    恩,在.Net里,这个函数是这个样子的:


    [DllImport("oleaut32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
    [LCIDConversion(3)]
    public static extern UCOMITypeLib LoadRegTypeLib(ref Guid rguid, [In,MarshalAs(UnmanagedType.U2)]short wVerMajor, [In,MarshalAs(UnmanagedType.U2)]short wVerMinor);

    哈,一个小小的技巧:在.Net 的定义里,偶没有定义原型里 lcid 这个参数,这是因为偶应用了LCIDConversionAttribute,这个Attribute 意思就是说这个方法需要一个LCID做参数,参数的位置嘛:[LCIDConversion(3)]——第三个参数。这样在调用这个方法的时候,.Net 的封送拆收器将自动提供 LCID 参数。不错把:)
    另外,在.Net Framwork的System.Runtime.InteropServices 命名空间里,定义了一些常用COM Interface的.Net托管定义,虽然不多,但幸运的是我们要用到的ITypeLib和 ITypeInfo这二个接口都有,也就是System.Runtime.InteropService.UCOMITypeLibSystem.Runtime.InteropService.UCOMITypeInfo。这就省下了我们自己定义接口的工作。
    好了,接下来的事情狠简单了:


    UCOMITypeLib typeLib;
    typeLib = UnsafeNativeMethods.LoadRegTypeLib(ref typeLibID,iMajorVer,iMinusVer);

    Bingo!Mission Complete!只剩下最后一个任务:定位到对应我们的COM对象的ITypeInfo,并从中取出我们需要的信息。
    ITypeInfo接口的GetITypeInfo方法和GetITypeInfoCount方法一起提供了遍历TypeLib中所有ITypeInfo的能力,不过既然我们手上有COM对象的ClassID,利用GetITypeInfoOfGuid 方法就可以获得COM对象的ITypeInfo了。


    UCOMITypeInfo ITypeInfo;
    typeLib.GetITypeInfoOfGuid(ref clsid,out ITypeInfo);

    拿到ITypeInfo之后,首先我们需要看看这个ITypeInfo里有多少方法/属性,这需要我们调用它的GetTypeAttr 方法获得TYPEATTR结构。


    TYPEATTR typeattr;
    IntPtr p_typeattr = IntPtr.Zero;
    ITypeInfo.GetTypeAttr(out p_typeattr);
    typeattr = (TYPEATTR)Marshal.PtrToStructure(p_typeattr,typeof(TYPEATTR));

    获得TYPEATTR结构有那么一点点麻烦,因为 .Net的不支持非托管签名的 TYPEATTR** 参数,所以只有使用引用 IntPtr 参数定义 GetTypeAttr。然后我们需要用Marshal.PtrToStructure将数据从非托管内存块封送到托管对象。在TYPEATTR结构中,cFuns字段表示当前TrpeInfo描述的函数数目,而每个函数的描述则是通过ITypeInfo的GetFuncDesc方法取得的FUNCDESC结构描述的。


    if(typeattr.cFuncs > 0)
    {
    for(int i=0;i<typeattr.cFuncs;++i)
    {
    //Get FUNCDESC struct
    FUNCDESC funcdesc;
    IntPtr p_funcDesc;
    ITypeInfo.GetFuncDesc(i,out p_funcDesc);
    funcdesc = (FUNCDESC)Marshal.PtrToStructure(p_funcDesc,typeof(FUNCDESC));
    ……

    和TYPEATTR一样,FUNCDESC结构也需要Marshal.PtrToStructure处理一下,偶就不多说了。
    讨厌的是:FUNCDESC结构里并没有函数的名称,我们只能通过它的memid字段和invkind字段知道这个函数的成员ID和函数的类型。函数的类型是我们需要的:它告诉我们这个函数是一个方法或者是一个属性的Get/Set方法,而名称这个东西,我们还得求助于ITypeInfo:GetNames 方法获取具有指定成员ID的成员名称,它的返回一个string数组,对方法而言,这个数组第一个元素是方法名称,后面的元素则是方法的参数名,而对属性而言,属性名称也出现在数组的第一个元素。
    好了,现在除了一件事情,该说的偶已经都说了,我们已经知道了如何从ITypeInfo获得方法/属性的名称,至于如何如何先建立二个空的Collection分别用于存放方法和属性名称;如何如何遍历ITypeInfo的所有FuncDesc,根据每个不同的函数类型向对应的Collection中插入元素,这些简单操作偶就不想多说了。我们剩下的唯一的问题是:对所有VB生成的COM对象,按照上面的步骤走下来,我们什么方法属性也看不到。
    原因嘛,用OleView看一下这些dll的TypeLib就明白了:VB会生成一个名为”_”+类名的类接口,这个接口继承自IDispatch,所有的方法属性都在这个接口上定义,而实现类只是简单的实现这个接口,在它的TypeLib里,真正对应ClassID的实现类里没有任何成员。
    既然知道了原因,办法也就有了:我们在枚举一个COM对象的所有方法/属性时,不应该只枚举仅对应它自己ClassID的TypeInfo,这个TypeInfo继承的所有其他接口中定义的方法/属性也要照样拿出来,而要定位到它继承的其他接口,我们要做的事情其实和遍历这个ITypeInfo的所有FUNCDESC差不多:


    if(typeattr.cImplTypes > 0)
    {
    for(int i=0;i<typeattr.cImplTypes;++i)
    {
           int href;
           UCOMITypeInfo imptypeinfo;
           typeinfo.GetRefTypeOfImplType(i,out href);
           typeinfo.GetRefTypeInfo(href,out imptypeinfo);
           //Now we can do the same thing to the imptypeinfo like typeinfo
    ……
    }
    }


    TYPEATTR的cImplTypes字段表示这个ItypeInfo实现的接口数目,ITypeInfo的GetRefTypeOfImplType 方法获取对某个已实现接口的句柄的引用,而GetRefTypeInfo 方法从这个句柄的引用获取该接口的ITypeInfo。很明显,我们可以写一个递归函数来走遍所有COM对象实现的接口,而且我们可以确信这个递归是有出口的:因为COM里所有的接口归根到底都派生自“我不知道”接口 。^-^
    最后,我想在大多数情况下,你不会希望在COM对象的方法列表里看到QueryInterface或者AddRef这类IUnknown接口的方法,而IDispatch接口那些类似Invoke之类的方法想来有兴趣的人也不多,不过反正这种底层方法就那么几个,在你遍历的时候尽可以判断一下过滤掉这些方法名称。

    免责声明:
    在本文中,为了清晰起见,所有给出的代码中都没有错误处理。如果你在你的代码中使用本文中的部分代码,由此造成的诸如程序出错、系统宕机、走路撞树、手机爆炸、洪水毁堤、地球毁灭等等一切后果,本人概不负责。

  • 相关阅读:
    回发或回调参数无效。下拉菜单中使用ajax,联动菜单引起的问题解决方案
    jquery获取,赋值img的src值..
    jquery select set selectindex
    DateTime.ToString("dd/MM/yyyy");后,不能直接Convert.ToDateTime的解决:
    SQL Server根据查询结果,生成XML文件
    SQL生成XML
    SQLXML
    .net 点击刷新验证码问题
    C# 根据字节数截取字符串(区别汉字,英文,数字)
    c#中对xml文件的基本操作
  • 原文地址:https://www.cnblogs.com/hackpig/p/1668489.html
Copyright © 2011-2022 走看看