分布式组件对象模型DCOM揭秘_第1页
分布式组件对象模型DCOM揭秘_第2页
分布式组件对象模型DCOM揭秘_第3页
分布式组件对象模型DCOM揭秘_第4页
分布式组件对象模型DCOM揭秘_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

PAGEPAGE38介绍

对于许多人来说,学习COM和DCOM是一件吃力的事情。COM的用处很大,不少微软的产品和编程者工具都是基于COM,不过,COM是一门颇难掌握的技术,你可能曾经想去学习它,阅读过一些书,使用过一些向导等,不过还是不太懂。它看来很复杂,而且还带有不少的新名词,例如"marshalling","apartmentthreads","singletonobjects"等,让你摸不着头脑。

这篇指南的目的是帮助你快速理解DCOM的基本要素,并且可以很容易地创建COM客户和服务器。读完这几篇指南后,你将会发现如果有一个好的开始,学习DCOM是一件非常简单的事情。以下是本指南的目录:

COM的基本要素--要学好它,就从这里开始吧

简单的COM客户--介绍简单的COM客户

简单的COM服务器--使用ATL向导来建立一个服务器

下载工程文件

*****下载BeepClient工程文件(9KB)

*****下载BeepServer工程文件(17KB)

COM的基本要素

首先要弄懂COM是怎样工作的。为什么这个工作是首要的呢?因为COM使用它自己专有的词汇。第二个原因是COM包含有不少的新概念。要掌握这些词汇和概念,最简单的其中一个方法是将COM对象和普通的C++对象作比较,并且比较它们的相似和不同之处。你还可以将COM的一些概念映射到标准的C++模型中去,这样就可以用你已经熟悉的东西来理解新概念。我们首先介绍一些COM的基本概念,接着,你就可以很容易地理解后面的例子。

一、类和对象

假设你在C++中创建了一个称为xxx的简单类。它有几个成员函数,称为MethodA,MethodB和MethodC。每个成员函数可接收参数,并返回一个结果。该类的定义如下所示:classxxx{

public:

intMethodA(inta);

intMethodB(floatb);

floatMethodC(floatc);

};在需要使用类的时候,你必须创建该对象的一个实例。实例是真实的对象;类只是定义。每个对象可作为一个变量(本地或者全局)创建,或者可使用new声明动态地创建。new声明可动态创建变量并返回指向它的一个指针。你可通过该指针来调用成员函数,例如:

xxx*px;//指向xxx类的指针

px=newxxx;//创建对象

px->MethodA(1);//调用方法

deletepx;//释放对象

你要明白到,COM使用相同的面向对象模型。COM拥有与C++对象一样的类、成员函数和实例。虽然你从来不会在一个COM对象上调用new方法,不过你必须在内存中创建它。你通过指针来访问COM对象,在你完成处理后,你必须释放它们。

写COM的代码时,我们将不会使用上面的new和delete。虽然我们将使用C++作为开发语言,不过我们将要使用全新的语法。COM是通过调用COMAPI来实现的,这些API提供创建和破坏COM对象的函数。以下就是一个用pseudo-COM代码写的COM程序例子:

ixx*pi//指向toxxxCOM接口的指针

CoCreateInstance(,,,,&pi)//创建接口

pi->MethodA();//调用方法

pi->Release();//释放接口

在这个例子中,我们将称类ixx是一个“接口”。变量pi是指向接口的一个指针。CoCreateInstance方法可创建一个ixx的实例。接口的指针是用来作方法调用的。Release用来删除接口。

为了突出该程序的要点,我故意忽略了CoCreateInstance的一些参数。CoCreateInstance可接收多个参数,每个参数都需要更深入的探讨才可以了解。现在,我们首先回过头来看看COM的一些主要方面。二、COM有什么不同

在某种程度上,COM对象要比它们的同胞C++更复杂,从网络应用方面考虑,大多数的复杂性都是必要的。以下就是在设计COM时的4个基本要素:

。C++对象通常都运行在同一进程空间中。COM对象可跨进程和跨计算机运行

。COM方法可通过网络调用

。在一个进程空间中,C++方法的名字必须是唯一的,而COM对象的名字在整个世界中都是唯一的

。COM服务器可以使用多种不同的语言和在不同的操作系统上编写,而C++对象通常都使用C++编写

以下再谈一下COM和C++的这些不同对于编程者有何意义。

COM可以跨进程运行

在COM中,编程者可在其它的进程中或者网络中的任何机器上创建对象。虽然在许多情况下你都无需这样做,不过,这种可能性意味着你不能通过普通C++的new句法创建一个COM对象,通过本地的程序来调用它的方法也是不足够的。

要创建一个COM对象,某些执行的实体(一个EXE或者服务)将必须执行远程的内存分配和对象创建。这是一个非常复杂的任务。远程的含义是指在另一个进程内或者另一个进程上。这个问题是通过称为COM服务器的概念来解决的。它必须与客户端维持紧密的通信。

COM方法可以通过网络调用

如果你可以访问网络上某台机器,而你想要使用的某个对象的COM服务器已经被安装在该机器上,你就可以在那台机器上创建COM对象。当然,你必须要有相应的权限,并且那台机器上已经进行了正确的设置。

由于你的COM对象并不一定在本机上,因此你需要一个方法来“指向”它,即使它存放在另一台机器的内存中。在技术上,没办法做到这一点。不过它可以通过一个全新级别的对象来模拟。COM使用的其中一个方法是一个称为proxy/stub的概念,我们将会在后面更详细地讨论proxy/stubs。

另一个重要的问题是在COM客户端和它的COM服务器间传送数据。数据在进程、线程之间或者一个网络上传送的时候,它就被称为“Marshalling”。proxy/stub负责为你维护Marshalling。COM还可以使用类库和Automationmarshaller来配置接口的某些数据类型。Automationmarshaller无需特别为每个COM服务器建立。

COM对象在世界上必须是唯一的

整个世界?看来有点夸张,不过考虑到Internet是一个世界范围的网络,即使在单一某台计算机上,COM也必须考虑到这个可能性。唯一是一个问题。在C++的全部类库中,这个问题是通过编译器完成的。编译器可以看到一个程序中每个类的定义,并且匹配它的所有引用,以确保它们严密符合该类。编译器也要确保每个类的名字是唯一的。在COM中也必须有一个好的方法来得到类似严密的匹配。即使在世界范围的网络上,COM也要确保每个对象的名字是唯一的。这个问题是通过一个称为GUID的概念来解决的。

COM是语言无关的

COM服务器可以用不同的语言和在完全不同的操作系统上编写。COM对象可以通过远程访问。远程是指它可以在一个不同的线程、进程或者甚至一个不同的机器上。另一台机器可以运行一个不同的操作系统。这就需要一个好的方法来在网络的机器间传送参数。这个问题是通过创建一个新的方法来指定客户和服务器间的接口来解决。还有一个称为MIDL(MicrosoftInterfaceDefinitionLanguage,微软接口定义语言)的新编译器。该编译器可指定服务器和客户端接口的一般方法。MIDL定义COM对象、接口、方法和参数。

COM词汇

我们碰到的其中一个问题是要记住两套术语。你可能已经熟悉C++和一些面向对象的术语。以下的表格将COM和传统术语间类似的地方列了出来。

概念传统的(C++/OOP)COM客户端一个从某个服务器请求服务的程序一个调用COM方法的程序服务器一个为其它程序服务的程序一个让某个COM客户得到COM对象的程序接口没有通过COM调用的一组函数的一个指示器类一个数据类型定义了一组一起使用的方法和数据一个对象的定义,用来实现一个或者多个COM接口,“coclass”也是对象一个类的实例化一个coclass的实例化Marshalling没有在客户和服务器端之间移动数据

你会发现接口和Marshalling的概念在C++模型中是没有的。在C++中,与接口较为相近的是一个DLL的外部定义。在使用一个紧密结合(进程间)的COM服务器时,DLL所做的许多事情与COM差不多。Marshalling在C++中也是没有的,如果你要在进程或者计算机之间拷贝数据时,你必须使用一些交互进程通信的方法来写代码,你可以选择sockets、剪贴板和mailslots。

接口

在上面我们已经多次看到“接口”这个词,在我的一本字典中是这样定义一个接口的:

“接口:名词,是两个物体或者界面的共有分界”。

这是一个普通的定义。在COM中“接口”有非常特别的含义。COM接口是一个全新的概念,在C++中是没有的。对于许多人来说,接口的概念在开始时都较难理解。一个接口没有一个有形的存在。它类似一个抽象类,但不完全一样。

最简单地说,接口是函数的集合。在C++,一个类仅允许有一个接口。这个接口的成员函数都是该类所有的公有成员函数。用其它话来说,接口是类的公共可见部分。在C++中一个接口和一个类几乎没有任何的区别,以下就是C++类的一个例子:classyyy{

public:

intDoThis();

private:

voidHelper1();

intcount;

intx,y,z;

};某人使用这个类时,他只可访问到pubilc的成员(这里我们忽略了protected成员和继承)。他不能调用Helper1,也不能使用任何的private变量。对于类的使用者来说,它的定义其实是:

classyyy{

intDoThis();

};

类的public子集是外部的“接口”。接口将类的内部和使用者隔离开来。

C++类似的部分就只有这么多,COM接口并不是一个C++的类,COM接口和类拥有自己特别的一套规则和协定。

COM允许一个coclass(COM类)拥有多个接口,每个接口拥有自己的名字和函数集。这样做便可得到更为复杂和功能更强的对象。这个概念与C++是完全不同的。(可将多个接口想象为两个类定义的结合,当然,这种结合在C++中是不允许的)

接口将客户和服务器隔离开来

COM最重要的一条规定是你只可通过接口来访问一个COM对象。通过接口,客户端的程序与服务器的执行完全隔离开来。这是非常重要的一点。

客户端程序对于实现COM的COM对象或者C++类一无所知。它只能看到接口。接口就象COM对象的一个窗口。接口的设计者只让客户看到设计者希望展示的部分。图一展示了客户是如何通过接口来访问一个COM对象的。

*****图一*****图中一个小圆圈连接一条杆的符号,是表示一个COM接口的通常方法。接口还有许多重要的规定,对于理解COM的详细运作是很重要的,我们将在下面谈到。现在我们只集中谈接口的主要概念。

COM接口的形象化

这里将以另一种方式来形象化一个接口。在这个部分中,我们将不用任何的C++术语来介绍一个COM接口。我们将以一个抽象的形式来了解一个接口。想象一下一个“汽车”对象。对于现实中的所有汽车对象,你知道它有一个“驾驶”的接口,可让你控制汽车向左、向右,或者加速、减速。驾驶接口的成员函数包括有“左”、“右”、“加速”、“减速”、“向前”和“向后”。不少的汽车安装了收音机,因此还有一个“收音机”的接口。收音机的接口可以是“开”、“关”、“大声”、“柔和”、“下一个台”和“前一个台”。DrivingRadio

Left()On()

Right()Off()

Slower()Louder()

Faster()Softer()

Forward()NextStation()

Reverse()PrevStation()有许多不同种类的汽车,它们不一定有收音机。因此它们虽然支持驾驶的接口,但没有实现收音机的接口。对于所有拥有收音机的汽车,收音器的功能都是一样的。一个人可以驾驶一辆没有收音机的汽车,但他不能听到音乐。对于带有收音机的汽车,还拥有收音机的接口。

对于COM类,COM支持这个同样形式的模型。一个COM对象可支持一个接口的集合,每个接口都拥有自己的名字。对于你自己创建的COM对象,你可以只使用单一一个COM接口。不过对于许多现有的COM对象,根据它们支持的特性,可支持多个COM接口。

另一个重要的区别是驾驶接口并不是汽车。驾驶接口并没有告诉你任何关于车的制动装置、车轮或者引擎等的事情。例如你可使用驾驶接口的加速和减速方法,而不需关心减速是如何实现的。汽车使用水力或者空气刹车也是不重要的。

组件的形象化

在你建立一个COM对象时,你会非常关注接口是如何工作的,对于接口的使用者,却不用关心它的实现。就象一辆车的制动一样,用户只关心接口的工作,而无需知道接口后面的细节。

隔离接口和实现对于COM是至关紧要的。通过将它的实现和接口隔离开,我们可以建立组件。组件可被替换和重用。两者均可简化和增加对象的可用性。名字的唯一性

COM接口的名字是唯一的。这就是说,编程者如果访问某个名字的接口,他可以认为在实现接口的所有COM对象中,该接口的成员函数和参数都将是完全一样的。因此,在我们上面的例子中,称为“驾驶”和“收音机”的接口,在任何实现它们的COM对象中,都将拥有完全一样的成员函数。如果你想要改变一个接口的成员函数,你必须用一个新的名字创建一个新接口。

所有接口的源头--IUnknown

通常介绍COM都是从讲述IUnknown接口开始的。IUnknown是所有COM接口的基础。虽然它挺重要,不过就算你不了解IUnknown,你也可以明白接口的概念。IUnknown的实现被更高级别的抽象隐藏起来,我们也使用这些抽象来建立自己的COM对象。不过,太过关注IUnknown将会令你感到迷惑。我们将从一个更高的级别来处理它,从而令你更容易理解它的概念。

IUnknown类似C++中的抽象基类。所有的COM接口必须由IUnknown继承而来。IUnknown处理接口的创建和管理。IUnknown的方法被用来创建、引用计数和释放一个COM对象。所有的COM接口都实现这3个方法,它们被COM内部使用来管理接口。你可能从来不会自己调用这3个方法。

一个典型的COM对象

现在我们要将这些新的概念放在一起,并且介绍一个典型的COM对象和一个要访问该对象的程序。

想象一下,如果你要创建一个最简单的COM对象。这个对象支持一个单一的接口,并且该接口只含有一个单一的函数。这个函数的功能也很简单--只是发出beep声。当一个编程者创建该COM对象,并且调用该对象支持的单一接口中的成员函数时,COM对象存在的机器将会发出beep声。更进一步的是,你要在一台机器上运行这个COM对象,但是从网络上的另一台机器来调用它。

为了创建这个简单的COM对象,你必须做以下的事情:

。你必须创建该COM对象,并且给它一个名字。该对象将会在一个相关的COM服务器中实现

。你需要定义该接口并且给它起一个名字

。你需要定义接口中的函数并且给它起一个名字

。你将需要安装COM服务器

在这个例子中,我将该COM对象称为Beeper,接口为IBeep,函数为Beep。你首先要马上面对的一个问题是命名这些对象,事实上,所有的机器都支持多个COM服务器,每个都可包含有一个或者多个COM对象,而每个COM对象都要实现一个或者多个的接口。这些服务器是经由不同的编程者创建的,这样就有可能选择一样的名字。同样,COM对象有一个或者多个的命名接口,它们同样是由多个编程者随意创建的,这样也有同名的问题。因此,我们必须要想个方法来防止名字的冲突。一个称为GUID(GloballyUniqueIDentifier,全球唯一标识器)的方法可解决这个问题。

如何做到唯一--GUID

要确保一个名字是唯一的,仅有两个方法:

1。通过一些准政府组织来登记名字;

2。使用一个特别的算法来产生唯一的数字,这些数字可被认为在世界范围内是唯一的第一个方法与网络上的域名管理一样。它的问题是你必须付$50来登记一个新的名字,而且要令登记生效,你要等几个星期。

第二个方法对于开发者更为方便。如果你可以发明一个算法,每次人们调用它都可以产生一个可被认为是唯一的名字,那么这个问题就解决了。事实上,这个问题已经被开放软件基金会提到(OpenSoftwareFoundation,OSF)。OSF有一个算法,可将一个网络地址、时间(100纳秒递增)和一个计数器结合,得到一个128位的唯一数字。

2的128次方是一个非常大的数字。通过它,你可以识别由宇宙开始到现在的每个100纳秒--而且还会剩下39位。OSF将它称为UUID,意思是UniversallyUniqueIdentifier,在COM的命名标准上,微软使用同样的算法。在COM中微软将它重命名为GloballyUniqueIdentifier。

GUID的记录通常采用16进制。不过这没有关系,一个典型的GUID类似为:

"50709330-F93A-11D0-BCE4-204C4F4F5020"typedefstruct_GUID

{

unsignedlongData1;

unsignedshortData2;

unsignedshortData3;

unsignedcharData4[8];

}GUID;GUID的普通读音是“gwid”,与“squid”的发音类似。一些人也读为“goo-wid”。

GUID通过一个称为GUIDGEN的程序产生。在GUIDGEN中,你只要按下一个按钮就可以产生一个新的GUID。你可以认为你产生的每个GUID都是唯一的,不管你产生了多少个,或者世界上有多少人产生它。这个认定可以成立是基于以下的原因:Internet上的所有机器都有一个唯一的地址。因此,你的机器最好是处在网络上。不过,即使你没有网络地址,GUIDGEN也将会产生一个,但是这样就会令唯一性的机率降低。

COM对象和COM接口都有一个GUID来标识自己。因此我们为该对象选用的名字“Beeper”是没有关系的。对象是通过它的GUID来命名的。我们将该对象的GUID称为它的classID。然后我们就可以使用一个#defind或者一个常数来令Beeper的名字和该GUID相关,这样我们就无需在代码中都使用这个128位的值。同样接口也将拥有一个GUID。要注意的是许多由不同的编程者来创建的不同COM对象将支持同样的IBeep接口,而它们都将全部使用同样的GUID来命名它。如果没有同样的GUID,COM就认为这是一个不同的接口。GUID就是它的名字。

一个COM服务器

COM服务器就是实现COM接口和类的程序。COM服务器有三个基本的配置。

。进程中或者DLL服务器

。Stand-aloneEXE服务器

。基于WindowsNT的服务

COM对象都是一样的,与服务器的类型无关。COM接口和coclasses将不会关心当前使用的服务器类型。对于客户端程序来说,服务器的类型几乎是完全透明的。不过对于写真正的服务器端来说,每种配置都将会有明显的不同:

进程中的服务器是作为动态连接库(DLL)实现的。这意味着该服务器在运行时被动态地放进你的进程中

COM服务器将成为你应用中的一部分,而COM操作在应用的线程中进行。事实上,许多的COM对象都是以这种方式实现的,因为性能很好--一个COM函数调用的系统开销很小,但你可以得到COM所有的设计和重用的好处

COM自动处理载入和卸下该DLL

一个进程外的服务器令客户和服务端的区分更明显。该类服务器作为一个独立的可执行(EXE)程序运行,因此处在一个私有的进程空间中。EXE服务器的启动和停止在Windows中服务管理器中进行(SCM)。COM接口的调用通过内部的进程通信技术来处理。服务器可以运行在本地的机器,或者在一个远程的计算机上。如果服务器在一个远程的计算机上,我们称它为“DistributedCOM,分布式的COM”,或者DCOM。

WindowsNT提出了一个服务的概念。一个服务是指该程序由WindowsNT自动管理,与桌面的用户无关。这意味着服务可以在启动时自动开始,并且即使是没有人登录到WindowsNT中,也可以自动运行。服务提供了一个极好的方法来运行COM服务器应用。

还有第四种的服务器,称为“surrogate”,这是一个可允许进程中的服务器在远程运行的程序。对于要建立一个可通过网络访问的基于DLL的COM服务器,surrogate是很有用的。

客户端和服务器端的交互

在COM中,客户端程序驱动所有的事情。服务器是被动的,只响应客户的请求。这意味着对于客户的个别方法调用,COM服务器以一个同步的方式运作

。客户端的程序启动服务器

。客户端请求COM对象和接口

。客户端发起所有的方法调用到服务器

。客户端释放服务器的接口,允许服务器关闭

这个区别是重要的。有各种不同的方法可模拟服务器到客户的调用,不过它们都难以实现,并且都相当复杂(这个被称为回叫)。通常没有客户的请求,服务器不做任何的事情。

以下就是COM客户和服务器之间的一个典型的交互

客户请求

请求访问一个特别的COM接口,特别的COM类和接口(通过GUID)

服务器响应

。启动服务器(如果需要)。如果是一个进程内的服务器,DLL将被载入。可执行的服务器将由SCM运行

。创建请求的COM对象

。创建到COM对象的一个接口

。增加激活接口的引用计数

。返回该接口给客户

客户请求

调用接口的一个方法

服务器响应

执行一个COM对象的方法

客户请求

释放接口

服务器响应

减少接口引用的数目

如果引用计数为0,将会删除该COM对象

如果没有活动的连接,关闭服务器。某些服务器不会关闭自身

如果你要了解COM,你必须使用一个以客户为中心的方法

三、总结

我们尝试从几个不同的方面来了解COM。C++是COM的原始语言,不过重要的是我们要了解它们的不同。COM有许多类似C++的地方,不过它也有很大的不同。在客户和服务器间通信方面,COM提供了一个全新的方式。

接口是COM最为重要的概念之一。所有的COM交互都经由接口进行。由于在C++中,并没有一个直接与接口对应的事物,因此有点难以掌握。我们还介绍了GUID的概念。GUID在COM中是普遍存在的,并且提供了一个极好的方式来在一个大型网络中标识一个实体。

COM服务器是传送COM组件的媒介。所有的事情都集中在传送COM组件到一个客户应用上。在以下的章节中,我们将创建一个简单的客户和服务器应用来解释这些概念。理解最简单的COM客户

要理解COM的最直接方法是通过一个客户应用来考察它。COM编程的目的是为了让客户应用可以得到有用的对象。一旦你理解了客户,要理解服务端就变得非常的简单。相反,同时直接考察服务端和客户端是容易令人迷惑的;如果你首先学习其细节的话,就更加复杂了。因此,我们首先由最简单的定义开始:COM客户是一个使用COM来调用一个COM服务器上的方法的程序。这种客户/服务关系的一个最简单直接的例子是一个用户界面应用(客户)调用另一个应用(服务端)的方法。如果该用户界面应用使用COM来调用这些方法,那么根据定义,这个用户界面应用就是一个COM客户。

我们不断强调以上的内容是有理由的,因为COM服务器和客户的分别可以是更为复杂的。许多时候,应用客户也将是一个COM服务端,而应用的服务器也可是一个COM客户。一个应用同时是COM客户和服务器是很常见的。在这一章中,我们将让这个区别最简单化,涉及的只是一个纯COM客户。

客户端连接的4个步骤

客户使用COM与一个服务器通信时,通常要经过4个基本的步骤。当然,现实中的客户端做的事情更多,不过即使它非常复杂,其核心也是这4个步骤。在这部分中我们将以最低级的方式介绍COM--使用简单的C++调用。

以下是我们将要进行的4个步骤:

1、初始化COM子系统,并且在完成时关闭它;

2、经一个服务器的特有接口查询COM

3、执行接口上的方法

4、释放该接口

为了简单,我们将使用一个极为简单的COM服务器。我们已经假定服务器已经写了出来,并且有使用说明。

该服务器拥有一个称为IBeep的接口。该接口只有一个方法,称为Beep。Beep接收一个参数:持续时间。以下我们将写一个最简单的COM客户来连接该服务器,并且调用Beep的方法。

以下就是实现这4个步骤的C++代码。这是一个真正可以工作的COM客户应用。#include"..\BeepServer\BeepServer.h"

//GUIDSdefinedintheserver

constIIDIID_IBeepObj=

{0x89547ECD,0x36F1,0x11D2,

{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

constCLSIDCLSID_BeepObj=

{0x89547ECE,0x36F1,0x11D2,

{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

intmain(intargc,char*argv[])

{

HRESULThr;//COMerrorcode

IBeepObj*IBeep;//pointertointerface

hr=CoInitialize(0);//initializeCOM

if(SUCCEEDED(hr))//macrotocheckforsuccess

{

hr=CoCreateInstance(

CLSID_BeepObj,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

IID_IBeepObj,//interfaceid

(void**)&IBeep);//pointertointerface

if(SUCCEEDED(hr))

{

//callmethod

hr=IBeep->Beep(800);

//releaseinterface

hr=IBeep->Release();

}

}

//closeCOM

CoUninitialize();

return0;

}在编译服务器时,头部的“BeepServer.h”会被创建。BeepServer是一个进程内的COM服务器,我们将在下一节再详细讨论。在编译该服务器时,开发工具包还会自动产生几个头文件。这个特别的头文件定义了接口IBeepObj。编译服务器还会在该程序的顶部产生GUID。我们将它从服务器工程的顶部拷贝了过来。

以下我们将详细讨论这4个步骤。初始化COM子系统:

这是一个简单的步骤。我们需要使用的COM方法是CoInitialize():

CoInitialize(0);

该函数接收一个参数,而该参数通常是一个0,这是它的起源OLE的一个惯例。CoInitialize函数初始化COM库。在你做其它的处理之前,你需要调用这个函数。在更为专业的应用中,我们将会使用扩展的版本--CoInitializeEx。

在完成COM的所有处理后,你要调用CoUnInitialize()。这个函数将会卸载COM库。我通常在自己的MFC应用中的InitInstance()和ExitInstance()函数中包含这些调用。

大部分的COM函数返回一个称为HRESULT的错误代码。这个错误的代码包含了几个字段,给出了错误严格、简要定义和错误的类型。我们使用SUCCEDDED宏,因为COM可以返回几个不同的成功代码。只是检验普通的成功代码(S_OK)将是不够周密的。我们将在后面更为详细地讨论HRESULT。

通过一个特别的接口查询COM

COM客户端感兴趣的是它可以调用的函数,在COM中,你可以通过接口来访问一套有用的函数。接口最简单的形式就是函数的一个集合。当我们得到COM服务器的一个接口时,我们就得到了一个指向一套函数的指针。

通过调用CoCreateInstance()函数,你就可以得到一个接口的指针。这是一个非常强大的函数,它可与COM子系统进行交互,并做以下的事情:

查找服务器

开始、载入或者连接到服务器

在服务器端创建一个COM对象

返回指向COM对象接口的一个指针

对于查找和访问接口,有两种数据类型是很重要的,它们是:CLSID和IID。它们都是GloballyUniqueID's(GUID's)。GUID's用作唯一辨认所有的COM类和接口。

为了得到某个特别的类和接口,你需要它的GUID。要得到GUID,有许多方法。通常我们可以由服务器的头文件得到CLSID和IID。在我们的例子中,我们在源代码的开始部分使用#defind语句定义了GUID。通过接口的一般名字来查找GUID也很方便的。

让我们得到接口指针的函数是CoCreateInstance。hr=CoCreateInstance(

CLSID_BeepObj,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

IID_IBeepObj,//interfaceid

(void**)&IBeep);//pointertointer第一个参数是一个GUID,它可唯一指定客户端需要使用的COM类。GUID或者CLSID是COM类的标识符。世界上的每个COM类都有自己唯一的CLSID。COM将使用该ID来查找可产生请求COM对象的服务器。一旦连接到服务器,将会创建该对象。

第二个参数是一个指针,它指向“outerunknown”。我们不会使用这个参数,因此传送一个NULL。在涉及到“aggregation”(集合)概念时,outerunknown是很重要的。aggregation可让一个接口直接调用另一个COM接口而无需通知客户端。aggregation和containment是接口用来调用其它接口的两个方法。

第三个参数定义COM类的Context或者CLSCTX。该参数控制服务器的范围。我们可以通过它来控制服务器是进程内的服务器,还是一个EXE或者是在远程的计算机上。CLSCTX是一个位掩码,因此你可以混合几个值。这里我们使用的是CLSCTX_INPROC_SERVER--该服务器将运行在本地的计算机,并且作为一个DLL连接到客户。由于进程内的服务器是最容易实现的,因此我们在这个例子中选用它来讲解。

通常客户端都不用关心服务器是如何实现的。这时它将使用CLSCTX_SERVER的值,该服务器可以是一个本地的或者是进程内的。

接着是接口的标识符或者IID。这是另一个GUID--用来标识我们请求的接口。我们请求的IID必须是存在的,即被由CLSID指定的COM类支持。再次,IID的值通常由一个头文件提供,或者使用接口名查找出来。

最后的参数是指向一个接口的指针。CoCreateInstance()将创建所请求的类对象和接口,并且返回一个指向接口的指针。这个参数也是CoCreateInstance调用的目的。然后我们就可以使用该接口指针来调用服务器上方法。

执行接口上的一个方法

CoCreateInstance()使用COM来创建一个指向IBeep接口的指针。我们可以假设接口是指向一个普通C++类的指针,不过事实上并不是。实际上,该接口指针指向一个称为VTABLE的结构,它是一个函数地址表。我们可以使用->操作符来访问接口指针。

由于我们的例子使用一个进程内的服务器,它将作为一个DLL载入到我们的程序中。忽略接口对象的细节,得到该接口的目的是用来调用服务器上的一个方法。

hr=IBeep->Beep(800);

Beep()在服务器上执行--它令计算机发出Beep声。有许多简单的方法可让一部计算机发出beep声。如果我们拥有一个远程的服务器,它运行在另一台计算机上,该机器将发出beep声。

接口的方法通常都带有参数。这些参数必须是属于COM支持的类型之一。有不少的规定来控制接口支持的参数。我们将在MIDL的部分更详细地讨论这个问题,MIDL是COM的接口定义工具。

释放接口

C++的一个规则是所有分配的事物都应该反分配。由于我们并不是使用new来创建接口,因此我们不能使用delete来删除它。所有的COM接口都拥有一个称为Release()的方法来断开对象,并且删除它。释放一个接口是很重要的,因为它可允许服务器来清除它。如果你使用CoCreateInstance来创建一个接口,你将需要调用Release()。

总结

在这一节中我们讲解了一个简单的COM客户。COM是一个客户驱动的系统。所有都是为了令客户更容易得到组件对象。相信该客户程序的简单性会给你留下一个深刻的印象。这里定义的4个步骤可让你使用大量的组件和大范围的应用。

其中的一些步骤是基本的,例如CoInitialize()和CoUninitialize()。其中的一些初次看来没有太多的作用。不过从更高的级别来看,懂得这些也是重要的。我们将在以后的例子中进一步谈及。

VisualC++Version5和6通过使用“智能指针”和#import令客户端的程序更加简化。在这个例子中我们使用的是一个低级的C++格式,以便更好地解释这个概念。我们将在后面的部分讨论智能指针和import。

在下一部分中,我们将建立一个简单的进程内服务器去管理IBeep接口。我们将在后面的章节继续深入讨论接口和激活的细节。理解简单的DCOM服务器

以上我们主要讲解了如何通过一个客户应用使用COM。对于客户来说,COM的编程技巧是相当简单的。客户端的应用向COM子系统请求一个特定的组件,服务器端将其传送过来。

实际上,对于后台的组件管理工作,还需要写很多的代码。真正的对象实现需要使用复杂的系统组件和标准的应用模块。就算是使用MFC,也是很复杂的。大多数的专业编程者都不会花时间来研究这个过程。自从COM的标准发布以来,很快就令我们明白到让开发者来自己写这些代码是不现实的。

当你查看实现COM的真正代码时,你会发现其中大部分都是重复的。对于这类复杂的问题,传统C++的解决之道是创建一个COM类库。实际上,MFCOLE类提供了大部分的COM特性。

不过对于COM组件来说,MFC和OLE并不是一个好的选择,有几个理由。随着ActiveX和微软Internet策略的推出,COM对象应该要非常的紧凑和快速。ActiveX需要COM对象可以经过网络相当快地被复制。如果你使用MFC较多,就会发现它实在太大了(特别是在静态链接时)。通过网络来传送巨大的MFC对象是不现实的。

或许通过MFC/OLE方法来实现COM组件的最大问题是复杂性。OLE编程是复杂的,并且大部分的编程者都不会在上面走得很远。有大量关于OLE的书,这都说明它是非常难以掌握的。

由于OLE的开发有不少的难度,因此微软创建了一个称为ATL(ActiveTemplateLibrary)新工具。对于COM编程来说,ATL是当前最实用的工具。实际上,如果你对其背后的东西没有兴趣,使用ATL向导来编写COM服务器是相当简单的。

这里介绍的例子都是通过ATL和ATL应用向导来创建的。这一节我们将讲解如何建立一个基于ATL的服务器,并对向导产生的代码给出了一个摘要。关于代码

有一点你要花时间去习惯,编写ATL服务器和传统的编程是不一样的。COM服务器其实是几个独立组件的协作构成的,包括有:

。你的应用

。COM子系统

。ATL模板类

。“IDL”代码和MIDL产生的“C”头文件和程序

。系统寄存器(注册表)

要将一个基于ATL的COM应用作为一个整体看是挺困难的。即使你知道它正在做什么,还有很大一块应用你是看不到的。真正服务器中的大部分逻辑都深入隐藏在ATL的头文件中。你将不会找到一个单一的用来管理和控制服务器的main()函数。你只找到一个用来调用基本ATL对象的瘦外壳。

在以下的部分中,我们将把所有这些令服务器运作的部分放在一起。首先我们会使用ATLCOM应用向导来创建服务器。第二步我们将加入一个COM对象和一个方法。我们将写一个进程内的服务器,因为它是最容易实现的COM服务器之一。一个进程内的服务器也不用建立一个proxy和stub对象。

建立一个基于DLL(进程内)的COM服务器

一个进程内的服务器就是一个会在运行时载入到你程序中的COM类。换句话说,就是一个动态链接库(DLL)中的COM对象。用传统的观点来看,一个DLL并不是一个真正的服务器,因为它会直接载入到客户的地址空间中。如果你熟悉DLL,你已经知道了许多关于COM对象如何载入和映射到调用程序的知识。

通常在调用LoadLibrary()时,DLL就会被载入。在COM中,你无需显式调用LoadLibrary()。在客户端的程序调用CoCreateInstance()时,所有的处理都会自动启动。CoCreateInstance需要的其中一个参数是你要使用的COM类的GUID。当服务器在编译时创建时,它就会登记了所有它支持的COM对象。当客户端需要该对象时,COM找到服务器DLL,并且自动装载它。一旦载入,DLL就拥有了创建COM对象的类库。

CoCreateInstance()返回一个指向COM对象的指针,它是用来调用方法的(再这里的例子中,这个方法就是被称为Beep()的方法)。COM的一个便利之处是DLL可以在不需要的时候被自动卸载。在对象被释放和CoUninitialize()被调用后,FreeLibrary()将会被调用来卸载服务器DLL。

如果你对以上的都不熟悉也不要紧。要使用COM,你不需要知道关于DLL的任何知识。你所要做的是调用CoCreateInstance()。COM的其中一个好处是它隐藏了这些细节,因此你无需担心此类问题。

进程内的COM服务器有优点也有缺点。如果动态链接是你的系统设计中的重要一环,那么你将发现COM可提供一个极好的方式来管理DLL。一些有经验的编程者会将所有他们的DLL都写成为进程内的COM服务器。COM处理所有涉及载入、卸载的杂事,而输出DLL函数和COM函数调用只有很少的系统开销。

我们选择一个进程内服务器的主要理由就更简单了:它可令例子更加简单。我们不必关心如何启动远程的服务器(EXE或者服务),因为我们的服务器将会在需要的时候自动载入。我们也无需建立一个proxy/stubDLL来做marshalling的工作。

缺点是,由于进程内的服务器与我们的客户绑定很紧密,因此COM许多重要的“分布”特性没有展现出来。一个DLL服务器和它的客户共享内存,而一个分布的服务器将令客户端更加隔离开来。在一个分布的客户和服务器间传送数据的处理被称为marshaling。marshaling在COM上的利用是受到限制的,而在进程内的服务器中,我们无需关心这些。使用ATL向导创建服务器

为了让你理解COM的基本规则,我们将创建一个非常简单的COM服务器。该服务器只有一个方法--Beep()。这个方法只是发出Beep的声音--一个不是很有用的方法。我们将要做的是设置该服务器所有部分。一旦该体系设置完毕,要加入其它有用的方法就变得非常简单了。

使用ATLAppWizard是一个可快速产生一个COM服务器的简单方法。该向导可让我们选择所有基本的选项,并且将产生我们需要的大部分代码。以下就是一个产生服务器的详细步骤。在这个程序中,我们将称该服务器为BeepServer。所有的COM服务器都至少要有一个接口,而我们的接口将会被称为IBeepObj。你可以为你的COM接口取任意的名字,不过如果你想遵循标准的命名传统,你最好使用“I”的前缀来命名它。

注意:很多人在这时可能还会分不清COM“对象”、“类”和“接口”定义之间的区别。特别是对于C++的编程者,这些术语开始都不太令人舒服。不过,当你了解这些例子后,你的混淆就会减少。在大部分的微软文档中,COM类都用“coclass”来表示,以将COM类和普通的C++类区分开来。

以下就是使用VisualC++version6来创建一个新的COM服务器的步骤(它与版本5中的几乎一样):

1。首先,创建一个新的“ATLCOMAppWizard”项目。由主菜单中选择File/New。

2。在“New”的对话框中选择“Projects”标签页。在项目类型中选择“ATLCOMAppWizard”。选择以下的选项并且按下OK。

。项目的名字:BeepServer

。创建新的Workspace

。Location:你的工作目录

图一

3。在第一个的AppWizard对话框中我们将创建一个基于DLL(进程内)的服务器。输入以下的设置:

。动态链接库

。不允许合并proxy/stub代码

。不支持MFC

图二

4。按下完成

AppWizard创建一个基于DLL的COM服务器,并且带有所有必要的文件。虽然该服务器将可编译和运行,但它只是一个空壳。为了令它做我们想做的事情,我们将需要一个COM接口和支持该接口的类。我们也必须写接口中的方法。

加入一个COM对象和一个方法

现在我们继续COM对象的定义,包括接口和方法。类的名字是BeepObj,它拥有一个称为IBeepObj的接口:

1。查看“ClassView”的标签页。在开始的时候它的列表中仅有唯一一个项目。右击“BeepServerClasses”项

2。选择“NewATLObject...”。这个步骤也可通过主菜单来完成。在弹出的菜单项中选择“NewATLObject”。

图三

3.在对象向导的对话框中选择“Objects”。选择“SimpleObject”并且按Next。

图四

4。选择Names的标签页。输入对象的名字:BeepObj。其余所有的选择都会自动填入标准的名字

图五

5。按下“Attributes”标签页并且选择ApartmentThreading,CustomInterface,NoAggregation。很明显,在aggregation在这个服务器中并没有用到。

图六

6。按下OK,这将创建Com对象

为服务器加入一个方法

我们现在已经创建了一个空的COM对象。不过,它还是一个无用的对象,因为它并不做任何的事情。我们将创建一个称为Beep()的简单方法,它可令系统发出一次beep声。我们的COM方法将调用Win32API函数:Beep()。

1。打开“ClassView”标签。选择IBeepObj的接口。该接口有由一个类似匙的小图标代表

图七

2。右击IBeepObj的接口。由菜单中选择“AddMethod”。

3。在“AddMethodtoInterface”对话框中,输入以下的信息并且按下OK。加入“Beep”的方法并且给它一个单一的[in]参数作为持续时间。这将是发beep音的长度,以毫秒计。

图八

4.“AddMethod”已经创建了我们定义方法的MIDL定义。该定义以IDL编写,并且定义该方法到MIDL编译器。如果你想看IDL的代码,双击“CalssView”标签页中的“IBeepObj”接口。这将打开和显示BeepServer.IDL文件的内容。我们没有必要改变这个文件,我们的接口定义如下所示:interfaceIBeepObj:IUnknown

{

[helpstring("methodBeep")]

HRESULTBeep([in]LONGduration);

};IDL的句法与C++类似。这一行和C++的函数原型相当。我们将在以后谈论IDL的句法。

5。现在我们将要写该方法的C++代码。AppWizard已经为我们的C++函数写入一个空壳,并且将它加入到头文件的类定义中(BeepServer.H)。

打开BeepObj.CPP的源文件。找到//TODO:行并且加入到APIBeep函数的调用。修改Beep()方法为如下:STDMETHODIMPCBeepObj::Beep(LONGduration)

{

//TODO:Addyourimplementationcodehere

::Beep(550,duration);

returnS_OK;

}6。保存文件,并且编译该项目

我们已经拥有一个完整的COM服务器了。当项目结束编译时,你应该会看到如下的信息:Configuration:BeepServer-Win32Debug

CreatingTypeLibrary...

Microsoft(R)MIDLCompilerVersion5.01.0158

Copyright(c)MicrosoftCorp1991-1997.Allrightsreserved.

ProcessingD:\UnderCOM\BeepServer\BeepServer.idl

BeepServer.idl

ProcessingC:\ProgramFiles\MicrosoftVisualStudio\VC98\INCLUDE\oaidl.idl

oaidl.idl

.

.

Compilingresources...

Compiling...

StdAfx.cpp

Compiling...

BeepServer.cpp

BeepObj.cpp

GeneratingCode...

Linking...

CreatinglibraryDebug/BeepServer.libandobjectDebug/BeepServer.exp

Performingregistration

BeepServer.dll-0error(s),0warning(s)这意味着开发工具已经完成了以下的步骤:

。执行MIDL编译器来产生代码和类库

。编译源文件

。链接项目来创建BeepServer.DLL

。注册COM组件

。使用RegSvr32注册DLL,以便在需要的时候自动下载

看看我们产生的项目吧。在我们按下按钮的时候,AppWizard已经产生了文件。如果你观看“FileView”标签,可看到已经产生了以下的文件源文件描述BeepServer.dswProjectworkspaceBeepServer.dsp项目文件BeepServer.plg项目的日志文件,包含了项目建立时的详细错误信息BeepServer.cppDLL主程序,DLL输出的实现BeepServer.hMIDL产生文件包含了接口的定义BeepServer.def声明标准的DLL模块参数:DllCanUnloadNow,DllGetClassObject,DllUnregisterServerBeepServer.idlBeepServer.dll的IDL源。IDL文件定义了所有的COM组件BeepServer.rc资源文件。这里主要的资源是IDR_BEEPDLLOBJ,它定义了注册表的脚本,用来将COM的信息载入到寄存器中。

Resource.h微软DeveloperStudio产生的包含文件StdAfx.cpp预编译头的源Stdafx.h标准的头BeepServer.tlb由MIDL产生的类库。该文件是COM接口和对象的二进制描述。作为连接一个客户的一个可选方法,TypeLib是非常有用的BeepObj.cppCBeepObj的实现。该文件包含了所有真正的C++代码,用来实现COMBeepObj对象中的所有方法。BeepObj.hBeepObjCOM对象的定义BeepObj.rgs注册表的脚本,用来在注册表中登记COM组件。在服务器工程被建立时,注册是自动进行的BeepServer_i.c包含了IID和CLSID的真正定义,这个文件通常被包含在cpp代码中。

还有另外几个proxy/stub文件,由MIDL产生。只是几分钟,我们就创建了一个完整的COM服务器应用。在没有向导的日子里,写一个服务器将需要数个小时。向导的缺点是我们有了一大块没有完全弄懂的代码。在下面的部分我们将更为详细地查看产生的模块,然后是整个的应用。

总结

整个服务器的代码几乎都是完全由ATL向导产生的。我们使用的是一个基于DLL的服务器。不过这个过程对于所有的服务器类型几乎都是一样的。使用这个架构,我们可以很快的开发出一个服务器应用,因为你不需知道许多的细节就可以开始工作。

在以下的章节中,我们将查看进程内服务器和ATL的代码。我们已经讨论了DCOM的基本要点,了解了如何创建一个简单的DCOM服务器和一个相关的客户端。你也可以看到这个基本的过程是非常简单的ATL向导处理了服务器端的大部分细节,要激活服务器,你只需要在客户端写10行左右的代码就可以了。

接下来我们将讨论两个相关的主题。首先是创建你自己的COM客户和服务器,结合第一部分我们所学到的,让你了解要在自己的代码中集成一个DCOM服务器,确实需要做哪些事情。然后我们将快速地看一下由ATL向导产生的代码。

本文的最后将会讲解要创建一个分布式的COM服务器,你需要经过的步骤。所谓分布式的COM服务器,是指该服务器可以处在网络的别处,并且可通过网络非常简单和透明地激活。

创建自己的COM客户和服务器

在第一部分的DCOM介绍中,你可以看到要创建COM客户和服务器是非常简单的。只要在客户和服务器端写入几行代码就可以产生一个完整的COM应用。你现在明白到为什么许多的开发者在创建一个DLL时会使用COM了--因为仅需要大概5分钟,就可以设置好一个进程内的COMDLL,并且令它工作。

本部分的目的是讨论如何创建自己的COM服务器,并且在你创建的真正应用中使用它们。你也会记得,第一部分介绍的客户端代码是非常少的。我们将介绍要创建服务器需要进行的基本步骤,然后看看要正确地激活服务器,你需要在客户端写入哪些代码。

服务器端

ATL向导令COM服务器的创建变得非常的简单。创建一个COMcoclass的第一步是要分离出一个或者多个的功能函数,你要从一个应用的代码主体中分离出这些功能函数。至于分离出来的目的,可以是多样的,你可能是想让该函数可以跨越多个应用重新使用,也可能是让一个队伍的编程者更容易地分离出各个独立的工作组,或者是让代码的开发和维护变得更加的简单。不论是出于什么原因,定义功能是第一步。

有一点可能令定义这些边界变得更为简单,这就是COM服务器的运作和一个普通的C++类是几乎一样的。象一个类,你实例化一个COM类,然后可以开始调用它的方法。COM的实例化和方法调用的句法和C++是有点不同的,不过它们的想法是一样的。如果一个服务器仅有一个接口,它事实上的用法就相当于一个类。(不过在访问对象时,你仍然需要遵守COM的规定)

一旦你已经定义了功能和访问它的方法,就可以建立自己的服务器。在第一部分中,我们已经知道,要创建一个服务器,有4个基本的步骤:

1。使用ATL向导来创建你的COM服务器的外壳。你选择该服务器是一个DLL、一个EXE或者是一个服务。

2。在服务器的外壳中创建一个新的COM对象。你将要选择线程的模式,这将会创建可装入方法的接口。

3。在你的对象中加入方法,并且声明它们的参数

4。为你的方法写代码

上面的这些步骤已经在第一部分中的“理解一个简单的COM服务器”中详细介绍过了。

经过第一部分的介绍后,一个常见的问题是关于线程模式,也就是COM对象的独立线程(apartment-threade)和自由线程(free-threaded)之间的区别?要理解它们之间的区别的最简单方法是将独立线程看成为单线程,而将自由线程想象为多线程。

在独立线程中,多个服务器客户的方法调用在服务器端的COM对象中被串行化,也就是说,每个独立的方法调用完成后,才会开始下一个的方法调用。因此独立线程的COM对象天生就是线程安全的,而自由线程的COM对象可同时在COM对象上有多个的方法调用执行。每个客户的方法调用都在一个不同的线程中运行。因此,在一个自由线程的COM对象中,你必须要注意多线程的问题,例如同步。

开始的时候你将更趋向于使用独立的线程,因为它更加简单,不过以后最好转向到自由线程,因为它有着更多的优点。

客户端

第一部分介绍的客户端程序非常清楚和紧凑。不过,它包含很少的错误检测代码,因此要在一个真正的程序中应用是不足够的。让我们再次看一下这些代码,它非常简单,因此可让你清楚地看到要创建一个客户端的必要步骤。voidmain()

{

HRESULThr;//COMerrorcode

IBeepDllObj*IBeep;//pointertointerface

hr=CoInitialize(0);//initializeCOM

if(SUCCEEDED(hr))//macrotocheckforsuccess

{

hr=CoCreateInstance(

clsid,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

iid,//interfaceid

(void**)&IBeep);//pointertointerface

if(SUCCEEDED(hr))

{

hr=IBeep-Beep(800);//callmethod

hr=IBeep-Release();//releaseinterface

}

CoUninitialize();//closeCOM

}CoInitialize和CoCreateInstance的调用初始化COM,并且得到指向一个接口的指针。然后你就可以调用接口的方法。在完成方法调用后,你就可释放接口并且调用CoUninitialize。整个步骤就完成了。

不过,在一个COM客户尝试启动一个COM服务器时,可出现各种不同的错误。一些常见的问题包括有:

.客户端不能启动COM

.客户端不能查找到请求的服务器

.客户端能查找到请求的服务器,但是不能正确地启动

.客户端不能找到请求的接口

.客户端不能找到请求的方法

.客户端可以找到请求的方法,但在调用时失败

.客户端不能正确地清除

为了跟踪这些潜在的问题,你必须在每一步作检查,具体是查看HRESULT的值。以上的代码有作检查,不过在出错的时候并没提示。以下的函数补救了这个不足://Thisfunctiondisplaysdetailed

//informationcontainedinanHRESULT.

BOOLShowStatus(HRESULThr)

{

//constructa_com_errorusingtheHRESULT

_com_errore(hr);

//Thehrasadecimalnumber

cout<<"hrasdecimal:"<<hr<<endl;

//showthe1st16bits(SCODE)

cout<<"SCODE:"<<HRESULT_CODE(hr)<<endl;

//Showfacilitycodeasadecimalnumber

cout<<"Facility:"<<HRESULT_FACILITY(hr)<<endl;

//Showtheseveritybit

cout<<"Severity:"<<HRESULT_SEVERITY(hr)<<endl;

//Usethe_com_errorobjecttoformatamessagestring.

//Thisismucheasierthenusing::FormatMessage

cout<<"Messagestring:"<<e.ErrorMessage()<<endl;

returnTRUE;

}该函数拆除了HRESULT的值,并且打印出它的所有成员,包括有极为有用的英语错误信息值ErrorMessage。你可以在任何的时候调用它:

//displayHRESULTonscreen

ShowStatus(hr);

要完整了解一个简单的COM程序可产生的不同错误模式,以下的客户端解释代码使用了一个MFC对话框应用,可让你控制一些可能的错误信息,并且看到该信息如何影响HRESULT的值。客户端的运行如图1所示。

图一

左边的单选按钮可让你选择各种的错误,包括有缺少CoInitialize函数,一个错误的classID,以及一个错误的接口ID。如果你按下运行的按钮,在右边你将可看到客户端的不同函数返回的不同错误对HRESULT的值的影响。

在你使用该例子中的客户端代码时,你将会发现该版本要比我们原来使用的标准客户端代码强壮。它还允许通过DCOM作远程连接。例如,它使用CoInitializeSecurity函数来设置默认的安全性,它还使用CoCreateInstanceEx函数以便另一台机器上的远程服务器可以被调用。研究一下这些代码,在文档中查找这两个函数,你将会惊奇地发现要理解它是非常简单的,你终于对COM有了一些了解。理解ATL产生的代码

我们服务器端DLL的源代码是由ATL产生的。对于许多人来说,可以完全不用了解ATL创建的代码。不过,对于一些喜欢寻根究底的人来说,这是不可以接受的。这里就介绍一下由ATL产生的代码。

服务端的DLL代码由三种不同类型的文件组成

首先,是传统的C++源文件和头文件。在开始时,所有这些代码是由ATL向导产生的

Beep方法是通过使用“AddMethod”对话框加入的,它修改了MIDL接口的定义。MIDL的源代码是一个IDL文件--在这个例子中它是BeepServer.IDL。MIDL编译器将使用该文件来创建几个输出文件。这些文件负责了大部分实现服务器的工作。当我们为COM对象加入方法时,我们也将在IDL加入一些东西。

第三组的源文件是自动产生的MIDL输出文件,是由MIDL编译器产生的。这些文件是源代码文件,不过由于它们是由MIDL编译器通过IDL源代码自动产生的,因此不能被向导或者开发者直接修改。你可以将它们称为“第二产生文件”--向导创建了一个IDL文件,而MIDL编译器由该IDL文件创建了源代码文件。由MIDL创建的文件包括有:

BeepServer.RGS-服务器的注册脚本

BeepServer.h-该文件包括了COM组件的定义

BeepServer_i.c-COM组件的GUID结构

Proxy/Stubfiles-包括了"C"源代码、DLL定义,以及Proxy和Stub的makefile(.mk)

ATL向导还创建了一个应用的“资源”。如果你查看项目的资源,你将会发现它处在“REGISTRY”下。该资源包含了BeepServer.RGS中定义的注册脚本。资源的名字是IDR_BEEPOBJ。

我们将在以下的部分看一下这些不同的组件

主C++模块

我们在运行ATLCOMAppwizard时,我们选择创建一个基于DLL的服务器,并且选择不使用MFC。向导的第一个选择屏幕决定了服务器的整体配置。

AppWizard创建了一个标准的DLL模块。该类的标准DLL并没有一个WinMain应用循环,不过它有一个DllMain函数用作在载入时初始化该DLL:CComModule_Module;

BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)

END_OBJECT_MAP()

////////////////////////////////////////

//DLLEntryPoint

extern"C"

BOOLWINAPIDllMain(HINSTANCEhInstance,

DWORDdwReason,LPVOID/*lpReserved*/)

{

if(dwReason==DLL_PROCESS_ATTACH)

{

_Module.Init(ObjectMap,hInstance);

DisableThreadLibraryCalls(hInstance);

}

elseif(dwReason==DLL_PROCESS_DETACH)

_Module.Term();

returnTRUE;//ok

}DllMain函数的真正工作是检查有没有一个客户连上该DLL,然后会做一些初始化的工作。一眼看去,并没有一个明显的指示这是一个COM应用。

我们新服务器的COM部分被封装到ATL类CComModule中。CComModule是ATL服务器的基类。它包含了所有用作登记和运行服务器、开始和维护COM对象的COM逻辑。CComModule被定义在头文件“altbase.h”中。该代码用以下的行声明一个全局的CComMoudule对象:

CComModule_Module;

这个单一的对象包含了许多用作我们应用的COM服务器功能。它在程序执行开始时的创建和初始化设置了一连串的事件动作。

ATL需要你的服务器命名它的全局CComModule对象“_Module”。使用你自己的类来覆盖CComModule是可以的,不过你不能改变它的名字。

如果我们选择创建一个可执行的服务器,或者一个带MFC的DLL,代码将会是完全不同。这时还会有一个基于CComModule的全局对象,不过程序的入口将会是WinMain()。选择一个基于MFC的DLL将会创建一个基于CWinApp的主对象。

对象映射

CComModule通过在前面部分看到的对象映射连接到我们的COM对象(CBeepObj)。一个对象映射定义了该服务器控制的所有COM对象的一个数组。对象映射在代码中使用OBJECT_MAP宏定义。以下就是我们DLL的对象映射:BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)

END_OBJECT_MAP()OBJECT_ENTRY宏通过一个C++类与对象的CLSID关联。一个服务器中包含有多于一个的COM对象是很常见的。这时,对于每个对象,都将会有一个OBJECT_ENTRY。

输出文件

我们这个进程内的DLL和大部分的DLL一样,都拥有一个输出文件。输出文件将被客户端用来连接到我们DLL中的外部函数。这些定义都放在BeepServer.def文件中:;BeepServer.def:Declaresthemoduleparameters.

LIBRARY"BeepServer.DLL"

EXPORTS

DllCanUnloadNow@1PRIVATE

DllGetClassObject@

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论