欢迎光临
我们一直在努力

COM组件对象与.NET类对象的相互转换-.NET教程,Asp.Net开发

建站超值云服务器,限时71元/月

运行环境:visual studio.net beta2, vc7, c#
参考资料:msdn
级别:入门级

一、前言

com组件对象与.net类对象是完全不同的,但为了使com客户程序象调用com组件一样调用.net对象,使.net程序
象使用.net对象一样使用com组件,ms使用了wrapper技术。本文详细介绍了两种不同的wrapper技术,并给出了
简单的代码实例。

二、com wrapper简介

传统的com对象与.net框架对象模型有以下几点不同:
(1)、com对象的客户必须自己管理com对象的生存期,而.net对象的生存期由clr(common language runtime)来管
理,即通过gc(garbage collection)机制自动回收。

(2)、com对象的客户通过调用queryinterface查询com对象是否支持某个接口并得到其接口指针,而.net对象的客
户使用reflection(system.reflection.*)来获得对象功能的描述,包括方法属性等。

(3)、com对象的客户通过指针引用com对象,对象在内存中的位置是不变的,而.net对象在内存中的驻留由.net框
架执行环境(execution environment)来管理,对象在内存中的位置是可变的,比如出于优化性能的考虑,同时
会更新所有对对象的引用。这一点也是以clr中不使用指针为前提的。

为了实现传统的com程序与.net程序之间的相互调用,.net提供了包装类rcw(runtime callable wrapper)和
ccw(com callable wrapper)。每当一个.net客户程序调用一个com对象的方法时就会创建一个rcw对象,每当一个
com客户程序调用一个.net对象的方法时就会创建一个ccw对象。

具体示意图如图1所示:

COM组件对象与.NET类对象的相互转换-.NET教程,Asp.Net开发

图1 com wrapper overview

三、.net中调用com组件

1、rcw(runtime callable wrapper)简介
其示意图如图2所示:

COM组件对象与.NET类对象的相互转换-.NET教程,Asp.Net开发

图2 accessing com objects through the runtime callable wrapper

rcw的主要功能:
(1)rcw实际上是runtime生成的一个.net类,它包装了com组件的方法,并内部实现对com组件的调用。

(2)列集(marshal).net客户与com对象之间的调用,列集的对象包括方法的参数返回值等,比如c#中的string与
com中的bstr之间的转换。

(3)clr为每个com对象创建一个rcw,与对象上的引用数无关,就是说每个com对象有且只会有一个rcw对象。

(4)rcw中包含了com对象的接口指针,并管理com对象的引用计数。rcw自身的释放通过gc机制管理。

2、实例演示
(1)使用vc7/atl创建一个最简单的com对象。组件类名叫atlcomserver,实现的接口名叫iatlcomserver,库名叫
atlserver。添加一属性name,并实现get/set函数。其idl如下所示:

import "oaidl.idl";
import "ocidl.idl";

[
        object,
        uuid(77506e08-d9fb-4f45-85e0-376f5187af21),
        dual,
        nonextensible,
        helpstring("iatlcomserver interface"),
        pointer_default(unique)
]

interface iatlcomserver : idispatch{
        [propget, id(1), helpstring("property name")] hresult name([out, retval] bstr* pval);
        [propput, id(1), helpstring("property name")] hresult name([in] bstr newval);
};

[
        uuid(9136eee6-ecee-4237-90b6-c38275ef2d82),
        version(1.0),
        helpstring("atlserver 1.0 type library")
]

library atlserverlib
{
        importlib("stdole2.tlb");

        [
                uuid(0e733e15-2349-4868-8f86-a2b7ff509493),
                helpstring("atlcomserver class")
        ]

        coclass atlcomserver
        {
                [default] interface iatlcomserver;
        };
};

(2)创建一个最简单的c# console程序。执行菜单project/add reference命令,在com属性页中选中刚才创建的
atlserver 1.0 type library并添加,系统会提示是否添加一个wrapper,选择是,然后会自动在c#程序的
bin目录下生成一个文件interop.atlserverlib_1_0.dll,这个就是atlserver的rcw。另外使用命令行命令
tlbimp atlserver.tlb有同样的效果。

(3)在程序中添加调用altserver的代码,如下所示:

using system;
using atlserverlib;     //通过namespace来引用库,在wrapper(即interop.atlserverlib_1_0.dll)中定义

namespace csharpclient
{
        class class1
        {
                static void main(string[] args)
                {
                        atlcomserver server = new atlcomserver();
                        server.name = "chang ming";
                        console.writeline("hello, my names is " + server.name);
                }
        }
}

从上面可以看到,atlserverlib.atlcomserver就代表了com组件atlcomserver。在传统的com客户中通过接口
iatlcomserver来调用,而在.net中只是把它当作了一个普通的.net类。因为实际上调用的是wrapper中的类,
而不是真正的com对象。

下载rcw的示例代码(23kb)

四、com程序中调用.net对象

1、ccw(com callable wrapper)简介
其示意图如图3所示:

COM组件对象与.NET类对象的相互转换-.NET教程,Asp.Net开发

图3 accessing .net objects through com callable wrapper

ccw的主要功能:
(1)ccw实际上是runtime生成的一个com组件,它在注册表注册,有clsid和iid,实现了接口,内部包含了对
.net对象的调用。

(2)列集(marshal).net对象与com客户之间的调用。

(3)每个.net对象只有一个ccw,多个com客户调用同一个ccw。

(4)com客户以指针的方式调用ccw,所以ccw分配在non-collected堆上,不受runtime管理。而.net对象则分配
在garbage-collected堆上,受runtime管理,享受clr的种种好处。

(5)ccw实际上是com组件,所以它遵循引用计数规则。当它的引用计数为0时,会释放它对它管理的.net对象的
引用,并释放自己的内存空间。当.net对象上引用计数为0时,则会被gc回收。

.net中受控类型(manages types)如class、interface、struct和enum都可以无缝的与com类型相结合,但是要
遵循以下规则:
(1)受控类型必须是public型。只有public型的类型才会被输出到类型库中。

(2)只有public型的methods、properties、fields和events才会被输出到类型库中,才会被com客户看见。

(3)受控类型必须有一个公用的缺省构造函数。这是因为com组件要求必须有缺省构造函数。

(4)强烈推荐.net类中显式地实现接口。如果一个.net类没有显式地实现一个接口,com interop会自动为其生
成一个接口,该接口包含了这个.net类及其父类的所有公有成员。这个被自动生成的接口被称为"class interface"。
但是ms强烈推荐使用显式的接口定义,原因在下面阐述。

2、实例演示一(不显示定义接口)
(1)创建一个最简单的c# console工程,其程序如下所示:

using system;
using system.runtime.interopservices;

namespace csharpserver
{
//缺省的是classinterfacetype.autodispatch,该方式下只生成dispatch接口
        //只能被使用script、vb等late binding方式的com客户使用
        [classinterfaceattribute(classinterfacetype.autodual)]  
        public class sharpobject
        {
                private string m_strname;

                public sharpobject(){}

                public string name //property: name, get/set
                {
                        get {       return m_strname;       }
                        set {       m_strname = value;      }
                }
        }
}

(2)在工程的属性中设置register for com interop为true。这样编译后就会生成csharpserver.tlb文件,并且
自动将其注册。命令行命令regasm有同样的效果。注册表内容如下:

[hkey_classes_root\clsid\{88994e22-e99f-320b-908c-96e32b7bfe56}]
       @="csharpserver.sharpobject"
[\inprocserver32]
       @="c:\\winnt\\system32\\mscoree.dll"
       "threadingmodel"="both"
       "class"="csharpserver.sharpobject"
       "assembly"="csharpserver, version=1.0.583.39183, culture=neutral, publickeytoken=null"
       "runtimeversion"="v1.0.2914"
       "codebase"="file:///e:/cm/net/c%23/exer/csharpserver/bin/debug/csharpserver.dll"
[\progid]
       @="csharpserver.sharpobject"

csharpserver.tlb文件中包含了组件的类型库信息,包括clsid、iid、接口定义等。而组件的真正实现,对.net
对象的调用则是由通用语言运行时库mscoree.dll完成的。可以说mscoree.dll和csharpserver.tlb加起来就是runtime为csharpserver这个.net类生成的ccw。

(3)写一个简单的vbscript程序test.vbs,如下所示:

dim obj
set obj = createobject("csharpserver.sharpobject")
obj.name = "chang ming"
msgbox "my name is " & obj.name

双击该文件,成功运行。

(4)创建一个最简单的mfc对话框工程,加入以下代码:

//这里应该用raw_interfaces_only,因为sharpobject缺省的从objec
//如果不加这个选项的话,也要为object的公用函数和属性生成包装函数,
//而object::gettype返回type型,而没有为类type生成包装接口,所以编译时会出错
#import "..\csharpserver\bin\debug\csharpserver.tlb" raw_interfaces_only no_namespace named_guids

{
        coinitialize(null);

        //方法一
        //因为使用了raw_interfaces_only,所以没有生成属性name的包装函数getname,putname
        _sharpobjectptr psharpobject(__uuidof(sharpobject));
        psharpobject->put_name(_bstr_t("chang ming"));
        bstr strname;
        psharpobject->get_name(&strname);
        afxmessagebox("my name is " + _bstr_t(strname));

        //方法二
/*      _sharpobject *psharpobject = null;
        hresult hr = cocreateinstance(clsid_sharpobject,
               null,
         clsctx_inproc_server,
         iid__sharpobject,
         (void**)&psharpobject);

        if (succeeded(hr))
        {
                psharpobject->put_name(_bstr_t("chang ming"));
                bstr strname;
                psharpobject->get_name(&strname);
                afxmessagebox("my name is " + _bstr_t(strname));
                psharpobject->release();
        }
        else
        {
                afxmessagebox("error");
        }
*/

        couninitialize();
}

自动生成的class interface中,接口名是_+类名,即_sharpobject。除此之外,使用方式与调用一般的com对
象完全一样。

(5)使用class interface的缺点在于.net类的变化会影响到com客户。具体而言,对于使用script、vb等late binding
方式的语言如test.vbs,net类的变化对其没有影响。而对于early binding的客户,因为dispid与其在.net类中
的位置相关,所以.net类的变化很有可能会改变成员的dispid,从而会影响到客户程序,客户程序需要重新编译。
对于通过指针直接调用的c++客户程序,每次.net的重新编译都会导致其重新编译,因为class interface的iid
每次都是随机生成的!所以ms强烈要求不要使用这种方式,class interface不能算是一个真正的接口,它总是
不断的改变,这违背了接口的精神,违背了com的精神。

3、实例演示二(显示定义接口)
(1)创建一个最简单的c# console工程,其程序如下所示:

using system;
using system.runtime.interopservices;

namespace csharpserver2
{
        //如果不指定guid,每次都会随机生成iid
        [guid("539448de-9f3b-4781-a1f6-f3c852091fc9")]
        public interface isharpobject2
        {
                string name     //property: name, get/set
                {
                        get;
                        set;
                }

                void test();
        }

        //如果不指定guid,每次都会随机生成clsid
        [guid("f5a31aab-faa9-47cc-9a73-e35606114ce8")]
        public class sharpobject2 : isharpobject2
        {
                private string m_strname;

                public sharpobject2(){}

                public string name      //property: name, get/set
                {
                        get     {       return m_strname;       }
                        set     {       m_strname = value;      }
                }

                public void test(){}
        }
}

(2)在工程的属性中设置register for com interop为true。这样编译后就会生成csharpserver2.tlb文件,并
且自动将其注册。注册表内容如下:

[hkey_classes_root\clsid\{f5a31aab-faa9-47cc-9a73-e35606114ce8}]
       @="csharpserver2.sharpobject2"
[\inprocserver32]
       @="c:\\winnt\\system32\\mscoree.dll"
       "threadingmodel"="both"
       "class"="csharpserver2.sharpobject2"
       "assembly"="csharpserver2, version=1.0.583.38696, culture=neutral, publickeytoken=null"
       "runtimeversion"="v1.0.2914"
       "codebase"="file:///e:/cm/net/c%23/exer/csharpserver2/bin/debug/csharpserver2.dll"
[\progid]
       @="csharpserver2.sharpobject2"

(3)创建一个最简单的mfc对话框工程,加入以下代码:

//这里不用raw_interfaces_only,因为sharpobject2只从接口isharpobject2继承
//而isharpobject2没有父类,所以不会有sharpobject那样的编译错误
#import "..\csharpserver2\bin\debug\csharpserver2.tlb" no_namespace named_guids

{
        coinitialize(null);

        //方法一
        isharpobject2ptr psharpobject2(__uuidof(sharpobject2));
        psharpobject2->putname("chang ming");
        afxmessagebox("my name is " + psharpobject2->getname());

        //方法二
/*      isharpobject2 *psharpobject2 = null;
        hresult hr = cocreateinstance(clsid_sharpobject2,
null,
clsctx_inproc_server,
iid_isharpobject2,
(void**)&psharpobject2);

        if      (succeeded(hr))
        {
                psharpobject2->putname("chang ming");
                afxmessagebox("my name is " + psharpobject2->getname());
                psharpobject2->release();
        }
        else
        {
                afxmessagebox("error");
        }
*/

        couninitialize();
}

只有接口isharpobject2保持不变,就不会影响到com客户程序。
   

下载ccw的示例代码(50kb)

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » COM组件对象与.NET类对象的相互转换-.NET教程,Asp.Net开发
分享到: 更多 (0)