Distributed COM --跨网络工作
DCOM代表的是“Distributed(分布式)”COM。在前面的部分中,我们已经讲解了运行在同一部计算机的COM客户和服务器。在这一部分,我们将讨论如何将它扩展到DCOM的领域和分布式计算。
大多数的COM编程者仅使用本地的“进程内”服务器,作为DLL运行。DLL载入到客户程序的处理空间,因此很可靠和有效。我们打算使用一个基于EXE的服务器。这意味着服务器和客户端作为分开的程序运行。这个想法很有意义,特别是考虑到两个程序运行在不同的计算机上。当然,它带来了新的难点。
好消息是将COM转换为DCOM是简单的。坏消息是连接客户和服务器时可出现很多的错误。在这里,重点是帮助你避免这些问题。
COM和DCOM的区别
COM和DCOM应用之间的大部分区别都被开发者隐藏起来。客户和服务器端的程序可以一样地编写,而不管程序在哪里运行。这个概念被称为本地/远程透明。
当然,分布和本地COM的内部工作是有着一些区别的。本地的通信可通过许多的方式来完成,包括简单的Windows信息,而连接到一个远程的计算机需要一个全新的对象层和网络传输。除了这些大的区别外,你的程序需要做的改动并不大。
与所有的COM通信一样,只有在客户请求一个服务器的接口时才会开始。在DCOM中,客户端调用CoCreateInstanceEx(),传送服务器计算机的一个描述和请求一个类标识器(CLSID)和接口。该请求由服务控制管理器处理(Service Control Manager,SCM),它是Windows的一部分。SCM负责在服务器计算机上创建和激活COM对象。在DCOM中,SCM将尝试启动远程计算机上的服务器。
*************图一*******************
一旦创建了远程的COM服务器,所有的调用将通过proxy和stub对象配置。proxy和stub使用RPC(Remote Procedure Calls,远程过程调用)进行通信,RPC处理所有网络交互。在服务器端,stub对象负责配置,而客户端则由proxy负责。
跨网络的数据传送由RPC负责。实际上,DCOM使用一个扩展类型的RPC,称为对象RPC(Object RPC)或者ORPC。RPC可以运行在多种不同的协议上,包括有TCP/IP,UDP,NetBEUI,NETBIOS和命名管道。标准的RPC协议是UDP(用户数据报协议)。UDP是一个无连接的协议,看来与DCOM这种面向连接的系统配合并不是一个好主意。不过这并不是一个问题,DCOM自动负责管理连接。
你也知道,分布式的COM是通过不同硬件、操作系统和软件组件这样一个复杂的交互完成的。你应该认识到:
a)COM在后面做了很多工作;
b)有许多地方可能出错
在编写时,如果使用Windows95/98系统,仅可使用TCP/IP协议进行DCOM数据传输。这是一个有点讨厌的限制,即使有其它的网络协议,你仍然需要在所有的Windows系统上安装TCP/IP协议。
服务器的改动不大
作为一个程序(EXE)运行的服务器将能够在网络上工作。实际上,将一个服务器转为DCOM方式工作,要做的改动并不大。不过,你要为该服务器加入一些安全性,这需要作一些努力。为了简单,在这里我忽略了安全性。
如果你以前使用的是进程内的服务器,你将需要作一些改变。进程内的服务器是一个DLL,它不可以跨网络载入。一个DLL载入到客户程序的地址空间中,它不可以通过远程连接工作。有一个称为surrogate的工作区,可以将DLL封装为一个可执行的程序,不过,更好的是将服务器转变为一个EXE,要将一个DLL转换为一个EXE,最简单的方法是重新使用ATL向导创建服务器,并且将代码由DLL传送到EXE中。
在我们的例子中,我提供了RemoteServer.exe的源代码,它实现了一个基于EXE的简单DCOM服务器。如果你查看其中的代码,你将发现它是由向导产生的。不过,我在其中加入了两个方法--一个是得到服务器的名字,另一个是得到服务器的系统时间。
在编译以上的例子后,我们需要将客户的EXE拷贝到客户计算机。要注意的是,由于它是一个用户自己的接口,因此你还需要一个proxy/stub DLL,并且在客户端和服务端计算机中注册该proxy/stub DLL。如果你使用一个带类库的自动服务器,你还需要将类库拷贝到客户计算机,并且登记它。
你还可以使用OLEVIEW和DCOMCNFG来设置一个远程服务器
实际上,你只需改变寄存器的设置就可以令一个服务器程序远程地运行。有两款微软的工具可做到这一点:OLEVIEW和DCOMCNFG。两款工具都可设置寄存器以让DCOM尝试找到远程计算机上的服务器。你可以在OLEVIEW的Activation标签下输入远程的计算机名字,这样它将在该计算机上被启动。
虽然它可以在旧的非DCOM应用上可行,不过这是一个不太便利的解决方法。我打算还是集中在使用编程的方法来设置远程激活,只要你掌握了它,这种方法将是更灵活的。
客户端程序
以下我们将讨论客户端程序的重要部分。
RemoteClient.cpp // RemoteClient.cpp : Defines the entry point for the console application. #include "stdafx.h" // added _WIN32_DCOM // extract definitions from server project // forward reference for status display method int main(int argc, char* argv[]) // Get the server name from user // remote server info // structure for CoCreateInstanceEx // initialize COM // macro to check for success // init security // get the interface pointer if (SUCCEEDED(hr)) // Extract the interface from the MULTI_QI strucure // Call a method on the remote server // Convert name to a printable string // get time from remote computer // display time_t as a string // Release the interface // Close COM // Prompt user to continue return 0; // Display detailed status information // convert to hexidecimal string and display // The hr as a decimal number |
************源代码 ***********
为简单的客户加入DCOM
为了激活我们的DCOM服务器,我们使用了基本的COM客户外壳并且加入了一些额外的方法,以便作DCOM测试。一个明显的改动是我们指定了服务器计算机的名字。以下就是我们必须加入到客户端的东西:
。指定服务器计算机名字的方法。它将载入到COSERVERINFO结构体
。调用CoCreateInstanceEx()代替CoCreateInstance()。这将包括有一些不同的参数和一个称为MULTI_QI的结构体
。在任何的COM编程中,你首先要做的第一件事情是调用CoInitialize。我们将使用默认的线程模式,也就是独立线程
// initialize COM
hr = CoInitialize(0);
通过COSERVERINFO指定服务器
进行远程DCOM连接时,你必须指定服务器计算机的名字。计算机的名字可以是一个标准的UNC计算机名字或者是一个TCP/IP地址。我们将要求用户输入计算机的名字。我们的例子是一个控制台应用,因此我们将使用一个简单的输入流(include )来得到用户的输入并显示信息。
// Get the server name from user char name[32]; cout << "Enter Server Name:" << endl; gets( name ); |
// remote server info COSERVERINFO cs; // Init structures to zero memset(&cs, 0, sizeof(cs)); // Allocate the server name in the COSERVERINFO struct // use _bstr_t copy constructor cs.pwszName = _bstr_t(name); |
// structure for CoCreateInstanceEx MULTI_QI qi[2]; // Array of structures // set to zero memset(qi, 0, sizeof(qi)); // Fill the qi with a valid interface qi[0].pIID = &IID_IGetInfo; qi[1].pIID = &IID_ISomeOtherInterface; // get the interface pointer hr = CoCreateInstanceEx( CLSID_GetInfo, // clsid NULL, // outer unknown CLSCTX_SERVER, // server context &cs, // server info 2, // size of qi qi ); // MULTI_QI array |
typedef struct tagMULTI_QI { // pass this one in const IID *pIID; // get these out (must set NULL before calling) IUnknown *pItf; HRESULT hr; } MULTI_QI; |
// pointer to interface IGetInfo *pI; if (SUCCEEDED(hr)) { // Basic style string BSTR bsName; // Extract the interface from the MULTI_QI structure pI = (IGetInfo*)qi[0].pItf; // Call a method on the remote server hr = pI->GetComputerName( &bsName ); pI->Release(); ... |
// turn off security - overrides defaults hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); |
CO_E_BAD_SERVER_NAME 需要进行远程激活,但是提供的服务器名字是无效的 |
这是其中一个容易看懂的错误信息。要注意的它并不意味着你输入了一个错误的服务器名字。它意味着你输入了一个无效的服务器名字。检查名字是否使用了正确的网络格式--检查有没有无效的字符 不能解析或者不存在的服务器使用一个不同的错误信息: RPC_S_SERVER_UNAVAILABLE. |
CO_E_SERVER_EXEC_FAILURE 服务器执行失败 |
服务器执行失败 查看COM的安全FAQ得到更多的信息 Microsoft Support - Article Q158508 (该站点需要注册) |
E_ACCESSDENIED 一般的访问拒绝错误 |
这是一个来自安全子系统的错误。服务器系统拒绝一个连接。这个问题可以是很难诊断的。这个错误很可能在使用DCOM作远程连接时发生 检查激活问题,特别是在Windows 95/98 检查DCOMCNFG的安全性设置 重新安装服务器和Proxy/Stub DLL. 确保你打开了文件/打印共享 |
E_FAIL 未指定的错误 |
这通常是一个由一个正在返回E_FAIL的方法导致的应用指定错误 |
E_NOINTERFACE 不支持该接口 |
你向一个服务器请求一个不支持的接口。这意味着你的CLSID可能是对的,不过IID不对,在调用QueryInterface (或者通过CoCreateInstance)时,如果它不能识别该接口,将返回这个错误。它可能是一个proxy/stub问题 这可能是一个注册问题。尝试重新登记服务器和proxy/stub |
E_OUTOFMEMORY 没有内存 |
这个信息可能与真正的错误无关。参见安全FAQ得到其它可能性 Microsoft Support - Article Q158508 (该站点需要注册) |
ERROR_INVALID_PARAMETER 参数不正确 |
在你的函数调用中,其中一个参数有问题。这通常发生在诸如CoCreateInstance, CoCreateInstanceEx, CoInitializeSecurity等的函数中 |
ERROR_SUCCESS 操作完全成功 |
与S_OK和NO_ERROR的含义一样 |
REGDB_E_CLASSNOTREG 类没有登记 |
登记或者CLSID问题。检查你的GUID 这个服务器不能在远程的Windows 95/98系统运行 |
RPC_S_SERVER_UNAVAILABLE 找不到RPC服务器 |
在使用远程服务器是,这个问题很常见。这是一个普通的远程连接错误。RPC是用来实现DCOM的协议。这可能是一个系统设置问题或者是一个安全性问题。 你可能正在尝试连接到一个不正确或者断开的计算机。检查服务器的名字 确保计算机上的DCOM和RPC打开了。可使用DCOMCNFG 或者 OLEVIEW (在文件菜单下的“系统设置”中) 重新注册登记服务器和proxy/stub. |