分享
 
 
 

COM组件对象与.NET类对象的相互转换

王朝c#·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

运行环境: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所示:

图1COM wrapper overview

三、.NET中调用COM组件

1、RCW(Runtime Callable Wrapper)简介

其示意图如图2所示:

图2Accessing 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所示:

图3Accessing .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)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有