DCOM 揭秘之四

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

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

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

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

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

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

  
服务器端

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

  有一点可能令定义这些边界变得更为简单,这就是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对象中,你必须要注意多线程的问题,例如同步。

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


  客户端

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

void main()
{
HRESULT hr; // COM error code
IBeepDllObj *IBeep; // pointer to interface

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

if (SUCCEEDED(hr))
{
hr = IBeep-Beep(800); // call method
hr = IBeep-Release(); // release interface
}
CoUninitialize(); // close COM
}


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

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

  . 客户端不能启动COM

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

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

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

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

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

  . 客户端不能正确地清除

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

 

// This function displays detailed 
// information contained in an HRESULT.
BOOL ShowStatus(HRESULT hr)
{
// construct a _com_error using the HRESULT
_com_error e(hr);

// The hr as a decimal number
cout << "hr as decimal: " << hr << endl;
// show the 1st 16 bits (SCODE)
cout << "SCODE: " << HRESULT_CODE( hr ) << endl;
// Show facility code as a decimal number
cout << "Facility: " << HRESULT_FACILITY( hr ) << endl;
// Show the severity bit
cout << "Severity: " << HRESULT_SEVERITY( hr ) << endl;
// Use the _com_error object to format a message string. 
// This is much easier then using ::FormatMessage
cout << "Message string: " << e.ErrorMessage() << endl;
return TRUE;
}


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

  // display HRESULT on screen

  ShowStatus( hr );

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



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

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

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

你可能感兴趣的:(COM,服务器,interface,远程连接,多线程,dll,server)