上一篇末留下的一个疑问这一回来作个解答吧。大家看了下面的图就清楚了:
结论就是t1,t2,t3是三个不同的引用,也就是说在.NET里面代表了三种不同的类型,但是三种类型的GUID却是一样的,因为在COM里GUID代表了一个COM类,只要GUID是一样的那么就表示是一个COM类,因此仅从COM类这一角度出发的话,这三种类型就是同一个COM类型。
第1种方式创建的COM对象的.NET包装的类型一般来说就是COM导入的.NET包装程序集里面对应声明的类型.
第2种方式创建的COM对象的.NET包装的类型永远都是__ComObject.
第3种方式创建的COM对象的.NET包装或者是指针经过Marshal类的方法转成的.NET的包装,这两种方式对应的类型__ComObject.
第4种从本质上来讲是第1种方式的变种,只是更为灵活,使用范围更加广范了,因此对应的类型也应该是声明的时候的.NET中的类型
上一文里面留的第二个问题的结果就是原来是什么类型,经过一次Marshal类的方法与IntPtr互转换后的结果还是什么类型,应该是CLR内部记录了指针和.NET类型之前的对应关系,不会每次由IntPtr转到object的时候都用一个不同的包装(感觉有点像WinForm里面从Handle找Control一样).
上一篇我们讲到了C#中创建COM对象的几种方式。不知大家也注意到了,最后一种方式中JetEngineClass类并没有提供方法供我们调用,要使用它的话必须先把这个引用转成接口引用才能直接使用里面的方法,实现早期函数绑定。虽然我们在声明JetEngineClass类的时候并没指定该类实现了IJetEngine接口,但是后面在使用的时候却直接把engine用as操作转成了IJetEngine接口,而且居然转成功了。而且大家也可以用is操作符测试一下,engine is IJetEngine反回的结果也为true。这就是本篇要讲的---C#中COM对象接口的查询。
与COM创建的方法一样,C#中COM接口查询的方法也有好几种:
第1种 Marshal.QueryInterface方法
这个方法本身就是Framework提供的正统的用来查询COM对象的方法,这种方式MSDN上已经有详细的说明了,我也不再多说.唯一注意的是这里只能传COM对象的指针IntPtr,而且这个方法成功返回后,所引用的COM对象的计数会自增1.以后当返回的查询到的接口的指针不再使用了的时候,需要手动调用Marshal.Release,达到平衡COM引用计数的目的.虽说是简单,还是给段代码吧
1 IntPtr pJetClass = GetJetEngine(); // return JetEngineClass Ptr
2
3 IntPtr pJet;
4 Guid g = typeof(IJetEngine).Guid;
5 int hr = Marshal.QueryInterface(pJetClass, ref g, out pJet);
6 if(hr <0)
7 Marshal.ThrowExceptionFromHR(hr);
8
其实在使用IntPtr引用COM对象的时候,就像是在C++里面直接使用COM指针一样,理论上来说这个指针每复制一次,都需要我们手动的调用一次AddRef方法,增加COM对象的引用计数,每当我们把指针设置为无效或不再使用这个指针的时候,同样需要手动的把这个指针用Release方法减少引用计数,当引用计数变为0的时候就释放COM对象.这还是没有摆脱C++里面使用原始的COM指针的时候容易忘记平衡引用计数的问题.这里我故意使用了"原始的COM指针"这外概念,主要是区别于在C++里面我们常使用COM指针的另外一种方式COMPtr<T>泛型类,有了这个泛型类C++里面的COM对象的引用计数就能够正常及时的增加和减少了,使得开发人员不用花心思在COM引用计数的维护上.但是就算是这样,要想查询一个接口还是摆脱不了那个QueryInterface方法.
C#作为一种继承了C++大部分优点的一种语言,当然也提供了类似的方式让我们远离引用计数的陷阱,而且还提供了更加优雅的方式供我们使用.
这就是我们要讲的第2种COM接口查询的方式