首页 > 编程 > .NET > 正文

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

2024-07-10 13:04:04
字体:
来源:转载
供稿:网友
在.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 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之类的方法想来有兴趣的人也不多,不过反正这种底层方法就那么几个,在你遍历的时候尽可以判断一下过滤掉这些方法名称。

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