将 dotNET组件暴露给COM (上)

类别:.NET开发 点击:0 评论:0 推荐:

dotNET组件暴露给COM 


小气的神

2002-4-23

 

Article Type: In-Depth 

难度等级:6/9       

版本:2.32

 

       COMdotNET之间的话题似乎总也讲不完,只要COM还存在而你又使用着dotNET,那么它们之间总有些有趣的事让你遇到。这丝毫不像小说中男女主人公相遇那么经典浪漫,反而像一个Debug和查错的过程,你必须很清楚问题是出在COM这边还是dotNET这边,事实上,有时你会忽略一个比较重要的因素:人的因素-我们每个人都会犯错:)

 

       dotNET体系中的交互性调用可以分为两层面:一个层面是关于原始的平台调用;一个是关于COM对象的调用 (似乎dotNET之前Microsoft划分DLL是按COM来的,除了COM之外的就是基于win32sC/C++DLL了,dotNET之后Microsoft划分DLL是按dotNET来了,除了managed DLL剩下就是非managed DLL),对于第一个层面的调用,Microsoft建议使用P/Invoke,事实上它能工作的很好,但这也是单方向的,即dotNETCLR)调用平台的DLLwin32s);反之相反的方向上,在原始的DLL中调用dotNET组件的问题,Microsoft还没有太迫切的需求,Widnows平台之外的开发商似乎对这个问题更感兴趣一些(他们没有COM)。对于P/invoke,在经历了VJ++之后Microsoft利用他在编译器上的丰厚经验把这个问题解决得很好,开发人员利用属性告诉编译器如何如何转换这些参数,因为调用最后是发生在堆栈上的(无论如何堆栈是共享的),所以只要保证调用时堆栈上的类型翻译和转换是正确的也就无所谓managed还是非managed的。Don Box的文章曾公布过一份Isomorphic and Nonisomorphic Types的名单上面列举了dotNET数据类型和平台DLL中的类型比较。几乎Win32s DLL中所有的类型和dotNET现有的类型都是Isomorphic的,这也就意味着这些类型调用如果发生在调用和被调用者的共享堆栈上,是不用进行转换的。剩下的是一些和字符(WCHARLPST)、interfaceBSTRVARIANTSAFEARRAY相关的少数类型(看得出都和COM有关,也终于有些明白StructC#以及整个CLR中的原因了:Struct也是Isomorphic的,Microsoft的那些设计者们真的是深谋远虑haha

 

       剩下的就是与COM的交互性了,实际上问题已经演变到一个dotNET组件的引用如何转变成COM的引用或是相反的问题。这涉及到对象的引用计数和生命周期,远远不是堆栈能够解决的了。Microsoft给出了一个明确的方案:以MSCOREE.dll 为界,当一个CLR对象的引用越过MSCOREE的边界(那么之外一定是COM的天空)所以一个保证这个CLR对象可以被COM调用的代理自动建立(COM-callable wrapper);同样一个COM对象的应用进入MSCOREE的边界(那么里面一定是dotNET的天空),所以也有一个保证这个COM对象可以被dotNET调用的代理自动建立(Runtime-callable wrapper)。这种代理/存根的游戏COM时代就有而且丰富多彩,现在也一样,只不过大家以MSCOREE为界,双方对于对象的引用计数、生命周期、事件、序列化、同步等等技术各有各的实现,事实上COM对象没有被dotNET接管和控制,dotNET对象也没有被COM接管和控制,大家通过代理交换数据以及暴露接口和功能。往回看看这似乎又回到了第一个层面,现在的调用成为P/Invoke的一个超集了(地点从共享堆栈转移到了代理和存根之间)

 

       上面是所谓的观念了,但事实上实际应用和操作中我们根本看到不到什么边界、什么代理/存根。而对于dotNET调用COM组件的情况更容易造成假相:VS.NET IDE几乎封装了一切。但反方向的过程让你会对老式的开发环境产生恐惧(好的办法是Microsoft针对目前的VS98再发布一个SP7 haha)。下面我们会到一些比较典型的问题比如如何把一个dotNET的组件暴露给VB6的环境使用。相对于第一种P/Invoke我认为没有太多的内容可以讲述,这个方面唯一的技巧是你的观察力和实践能力,收集专家或高手们P/Invoke声明是最有效的,实践是检验这些函数声明是否正确的标准,最主要是这些Win’sAPI太多太多了;其间我们会看到一些有关的技巧和信息,这些都是关于COMdotNET交互的,我认为这些是我们应用dotNET过程中更多会遇到的问题,特别是迁移应用和切换开发环境时一定会遇到的。

 

       第一个问题,就是如何向COM展示dotNET的组件,这多是我们希望以前的VBASP或其他的语言能够直接使用dotNET组件和其带来的好处。这里必须抱怨一些dotNET Beta2的作品或文章,如果今天你还是按照上面所写的去做,往往会听到这样的疑问:为什么我写的dotNET组件在VB的对象浏览器中看不到一个方法?

往往他们看到的不是下面这样一副图,而是左边有类名,但右边没有任何成员函数可以调用。

 

 

附带文件中的COMVisible项目中,你会看到整个的代码过程,这里我们只是讲述一些容易忽视的地方,下面是一个可能的Checklist

1.      是否引用了System.Runtime.InteropServicesSystem.Reflection

2.      组件是否强名,如果没有最好先用sn k 将组件强名。

3.      希望暴露的ClassInterface是否都已经使用了ProgId属性。

4.      希望暴露的ClassInterface是否都已经使用了GuidAttribute属性和产生了固定的GUID

5.      是否使用了ClassInterfaceAttribute属性和设置了正确的值。

6.      是否在VS.NET进行正确的设置或是用手工的方式进行注册了

7.      是否已经产生了后绑定需要的Tlb文件,如果已有,查看一下它的内容是否符合你的需要。

 

对于14点我的建议是最好一定都有,尽管有些它们不是必须的;对于5点是最重要也是最容易忽视的。

 

ClassInterfaceAttribute有三种类型参数:

AutoDispatch

指示Class仅仅支持后绑定的COM消费者,当COM消费者请求时会自动产生一个dispinterface ,但这个接口的类型信息将不在Tlbexp产生的类型库中进行描述。这是ClassInterfaceAttribute 缺省的选项。

AutoDual

指示将自动的产生一个双接口类并且暴露给COM,类型库信息中将包括类接口的信息,但这个选项不是一个建议的选项。Microsoft警告你要小心版本兼容问题。

None

指示不为类产生任何类接口。这是一个建议的选项。

 

如果这样的描述不是很清楚,看到下面我分别用这三个参数产生的Tlb文件描述也许你就清楚了。

AutoDual参数产生的类型库描述:

coclass dotNETClass {

        [default] interface _dotNETClass;

        interface _Object;

    };

 

    [

      odl,

      uuid(1EA7C0BF-0562-3D1E-A6C3-89CF63F040F6),

      hidden,

      dual,

      nonextensible,

      oleautomation,

      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, COMVisible.dotNETClass)   

 

    ]

    interface _dotNETClass : IDispatch {

        [id(00000000), propget,

          custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]

        HRESULT ToString([out, retval] BSTR* pRetVal);

        [id(0x60020001)]

        HRESULT Equals(

                        [in] VARIANT obj,

                        [out, retval] VARIANT_BOOL* pRetVal);

        [id(0x60020002)]

        HRESULT GetHashCode([out, retval] long* pRetVal);

        [id(0x60020003)]

        HRESULT GetType([out, retval] _Type** pRetVal);

        [id(0x60020004)]

        HRESULT Add(

                        [in] long x,

                        [in] long y,

                        [out, retval] long* pRetVal);

        [id(0x60020005)]

        HRESULT AddEx(

                        [in] long x,

                        [in] long y,

                        [in] long z,

                        [out, retval] long* pRetVal);

        [id(0x60020006)]

        HRESULT Hello([out, retval] BSTR* pRetVal);

    };

 


特别:

本文原创,CSDN署名首发,所有文字和图片版权所有。未经授权请勿传播、转载或改编。

如果有问题或建议,请发电子邮件给[email protected]

 

本文地址:http://com.8s8s.com/it/it45974.htm