Com对象

在.Net 中枚举COM对象的方法和属性名称在.Net 中枚举COM对象的方法和属性名称vv

 

.net 中枚举COM对象的方法和属性名称

Author
Zee

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

一般来说,一个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 UpStrength + 3, Life + 5,必杀技 dll import 习得。 :) 下一个任务:取得TypeLib
l         
取得TypeLib
为访问TypeLibCOM 提供了二个接口:ITypeLib ITypeInfo,其中ITypeLib 提供对 TypeLib 的访问,而ITypeInfo 则表示TypeLib中定义的某一项ITypeInfo
要获得ITypeInfoCOM有二个函数可以做这件事情:LoadTypeLib LoadRegTypeLib。其中 LoadTypeLib 需要 TypeLib 文件的路径作为参数,而LoadRegTypeLib 则根据TypeLibTypeLib IDTypeLib的版本号取得 ITypeLib。在这里,我们用LoadRegTypeLib来取得ITypeLib 接口。
先来准备需要的参数:TypeLibIDTypeLib的版本号,这些信息需要从注册表里得到:


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 FramworkSystem.Runtime.InteropServices 命名空间里,定义了一些常用COM Interface.Net托管定义,虽然不多,但幸运的我们要用到的ITypeLib ITypeInfo这二个接口都有,也就是System.Runtime.InteropService.UCOMITypeLib System.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描述的函数数目,而每个函数的描述则通过ITypeInfoGetFuncDesc方法取得的FUNCDESC结构描述的。


if(typeattr.cFuncs > 0)
{
for(int i=0;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方法,而名称这个东西,我们还得求助于ITypeInfoGetNames 方法获取具有指定成员ID的成员名称,它的返回一个string数组,对方法而言,这个数组第一个元素方法名称,后面的元素则方法的参数名,而对属性而言,属性名称也出现在数组的第一个元素。
好了,现在除了一件事情,该说的偶已经都说了,我们已经知道了如何从ITypeInfo获得方法/属性的名称,至于如何如何先建立二个空的Collection分别用于存放方法和属性名称;如何如何遍历ITypeInfo的所有FuncDesc,根据每个不同的函数类型向对应的Collection中插入元素,这些简单操作偶就不想多说了。我们剩下的唯一的问题:对所有VB生成的COM对象,按照上面的步骤走下来,我们什么方法属性也看不到。
原因嘛,用OleView看一下这些dllTypeLib就明白了:VB会生成一个名为"_"+类名的类接口,这个接口继承自IDispatch,所有的方法属性都在这个接口上定义,而实现类只是简单的实现这个接口,在它的TypeLib里,真正对应ClassID的实现类里没有任何成员。
既然知道了原因,办法也就有了:我们在枚举一个COM对象的所有方法/属性时,不应该只枚举仅对应它自己ClassIDTypeInfo,这个TypeInfo继承的所有其他接口中定义的方法/属性也要照样拿出来,而要定位到它继承的其他接口,我们要做的事情其实和遍历这个ITypeInfo的所有FUNCDESC差不多:


if(typeattr.cImplTypes > 0)
{
for(int i=0;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实现的接口数目,ITypeInfoGetRefTypeOfImplType 方法获取对某个已实现接口的句柄的引用,而GetRefTypeInfo 方法从这个句柄的引用获取该接口的ITypeInfo。很明显,我们可以写一个递归函数来走遍所有COM对象实现的接口,而且我们可以确信这个递归有出口的:因为COM里所有的接口归根到底都派生自"我不知道"接口 ^-^
最后,我想在大多数情况下,你不会希望在COM对象的方法列表里看到QueryInterface或者AddRef这类IUnknown接口的方法,而IDispatch接口那些类似Invoke之类的方法想来有兴趣的人也不多,不过反正这种底层方法就那么几个,在你遍历的时候尽可以判断一下过滤掉这些方法名称。

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

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值