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

对象|转换

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

图1 COM wrapper overview

三、.NET中调用COM组件

1、RCW(Runtime Callable Wrapper)简介
其示意图如图2所示:

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

开发">

图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)

时间: 2024-10-26 08:08:38

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

C++类对象的拷贝构造函数分析

对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #include <iostream>using namespace std;class CA{ public: CA(int b) { a=b; } void Show () { cout<<a<<endl; } private: int a;};int main(){

(一二四)给类对象赋值、以及类对象的返回值

于直接给对象赋值: 之前学过,如何给对象在初始化时进行赋值. 对于C++11来说,初始化方式有三种: ① man c = man{ "cc",1 }; ② man d = { "dd",1 }; ③ man f{ "ff",1 };   假如有一类M,他有两个私有成员a和b(int类型). 于是新建一对象M q; 对象q使用默认构造函数(假如都赋值为0,这个不重要): 现在,我们想给对象q的第一个私有成员赋值,该怎么办? 这章刚学过运算符重载,难

声明函数时候,形参为什么有时候用指针对象,有时候使用普通类对象?

问题描述 声明函数时候,形参为什么有时候用指针对象,有时候使用普通类对象?为什么不能像java都使用普通类对象?难道类对象作用不能实现到指针对象功能?比如代码如下: class A{}class B( public: B(A* aaa);//这里为什么不用A aaa去掉指针 //普通类传参有什么区别 B(A aaa);} 解决方案 引用class B( public: B(A* aaa);//这里为什么不用A aaa去掉指针 //普通类传参有什么区别 B(A aaa); } 1.传指针(和引用)

浅谈C++中派生类对象的内存布局_C 语言

主要从三个方面来讲: 1 单一继承 2 多重继承 3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性. 派生类可以看作是完整的基类的Object再加上派生类自己的Object.如果基类中没有虚成员函数,那么派生类与具有相同功能的非派生类将不带来任何性能上的差异.另外,一定要保证基类的完整性.实际内存布局由编译器自己决定,VS里,把虚指针放在最前边,接着是基类的Object,最后是派生类自己的object.举个栗子: class A { int b; char c; }

Ruby 组织对象用的类的用法介绍

类与实例 2016年9月5日 下午7:35 *** 一个类里面会定义一些方法,类存在的理由就是要被实例化,也就是去创建一个类的实例得到一个对象.一个实例化的动作,像这样: obj = Object.new Object 是 Ruby 内置的一个类,在类上使用点形式,就是 Object 与 new 之间的那个点.你就是发送了一个信息给类.类会对这个信息做出响应,就像对象可以响应信息一样.类也是对象.new 方法是一个构造器,也就是类里面的可以加工与返回新实例的方法. 使用 class 关键词可以去

[Java] 方法锁、对象锁和类锁的意义和区别

版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 首先的明白Java中锁的机制 synchronized  在修饰代码块的时候需要一个reference对象作为锁的对象.  在修饰方法的时候默认是当前对象作为锁的对象.  在修饰类时候默认是当前类的Class对象作为锁的对象.   线程同步的方法:sychronized.lock.reentrantLock分析 方法锁(synchronized修饰方法时) 通过在方法声明中加入 synchronized关键字来声明 synch

ios-无法初始化类对象-XCode

问题描述 无法初始化类对象-XCode 执行文件如下: @implementation Utils +(id)alloc { return [self instance]; } +(Utils *)instance { static Utils *utils = nil; if (!utils) { utils = [self init]; } return utils; } -(Utils *)init { self = [super init]; if (self) { mConst = [

如何让类对象只在栈(堆)上分配空间?

转自:http://blog.csdn.net/hxz_qlh/article/details/13135433 一般情况下,编写一个类,是可以在栈或者堆分配空间.但有些时候,你想编写一个只能在栈或者只能在堆上面分配空间的类.这能不能实现呢?仔细想想,其实也是可以滴. 在C++中,类的对象建立分为两种,一种是静态建立,如A a:另一种是动态建立,如A* ptr=new A:这两种方式是有区别的. 1.静态建立类对象:是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后

对象的初始化-用函数的返回值初始化一个类对象,这其中用了几次复制构造函数

问题描述 用函数的返回值初始化一个类对象,这其中用了几次复制构造函数 这是我自己写的一段代码#includeusing namespace std;class Example{int num;public:Example(int i){num=i;cout<<""This is construction with parameter.n"";}Example(){num=0;cout<<""This is construc