分布式组件对象模型DCOM揭秘

以下是本指南的目录:

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

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

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

  下载工程文件

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

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

   COM的基本要素

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

   一、类和对象

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

class xxx {
public:
int MethodA(int a);
int MethodB(float b);
float MethodC(float c);
};


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

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

  px = new xxx; // 创建对象

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

  delete px; // 释放对象

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

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

  ixx *pi // 指向to xxx COM接口的指针

  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还可以使用类库和Automation marshaller来配置接口的某些数据类型。Automation marshaller无需特别为每个COM服务器建立。

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

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

  COM是语言无关的

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

class yyy {
public:
int DoThis();
private:
void Helper1();
int count;
int x,y,z;
};


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

  class yyy {
   int DoThis();
  };

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

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

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

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

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

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



*****图一*****


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

  COM接口的形象化

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

Driving Radio
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(Globally Unique IDentifier,全球唯一标识器)的方法可解决这个问题。

  如何做到唯一--GUID

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

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

  2。使用一个特别的算法来产生唯一的数字,这些数字可被认为在世界范围内是唯一的

  第一个方法与网络上的域名管理一样。它的问题是你必须付$50来登记一个新的名字,而且要令登记生效,你要等几个星期。

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

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

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

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

  由于在C++中并没有标准的128位数据类型。,因此我们使用一个结构体来表示。虽然GUID的结构体包含有4个不同的字段,不过你可能从来不会操作它的成员。该结构体通常都作为一个整体使用。

typedef struct _GUID
{
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;

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

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

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

  一个COM服务器

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

  。进程中或者DLL服务器

  。Stand-alone EXE服务器

  。基于Windows NT的服务

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

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

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

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

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

  Windows NT提出了一个服务的概念。一个服务是指该程序由Windows NT自动管理,与桌面的用户无关。这意味着服务可以在启动时自动开始,并且即使是没有人登录到Windows NT中,也可以自动运行。服务提供了一个极好的方法来运行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组件到一个客户应用上。在以下的章节中,我们将创建一个简单的客户和服务器应用来解释这些概念。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值