理解最简单的COM客户

来源:互联网 发布:下一个风口行业 知乎 编辑:程序博客网 时间:2024/05/16 09:03
理解最简单的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"

// GUIDS defined in the server
const IID IID_IBeepObj =
{0x89547ECD,0x36F1,0x11D2,
{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};
const CLSID CLSID_BeepObj =
{0x89547ECE,0x36F1,0x11D2,
{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

int main(int argc, char* argv[])
{
HRESULT hr; // COM error code
IBeepObj *IBeep; // pointer to interface

hr = CoInitialize(0); // initialize COM
if (SUCCEEDED(hr)) // macro to check for success
{
hr = CoCreateInstance(
CLSID_BeepObj, // COM class id
NULL, // outer unknown
CLSCTX_INPROC_SERVER, // server INFO
IID_IBeepObj, // interface id
(void**)&IBeep ); // pointer to interface

if (SUCCEEDED(hr))
{
// call method
hr = IBeep->Beep(800);

// release interface
hr = IBeep->Release();
}
}
// close COM
CoUninitialize();
return 0;
}


  在编译服务器时,头部的“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。它们都是Globally Unique ID's (GUID's)。GUID's用作唯一辨认所有的COM类和接口。

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

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

hr = CoCreateInstance(
CLSID_BeepObj, // COM class id
NULL, // outer unknown
CLSCTX_INPROC_SERVER, // server INFO
IID_IBeepObj, // interface id
(void**)&IBeep ); // pointer to inter

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

  第二个参数是一个指针,它指向“outer unknown”。我们不会使用这个参数,因此传送一个NULL。在涉及到“aggregation”(集合)概念时,outer unknown是很重要的。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()。其中的一些初次看来没有太多的作用。不过从更高的级别来看,懂得这些也是重要的。我们将在以后的例子中进一步谈及。

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

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

原创粉丝点击