65.9K
CodeProject 正在变化。 阅读更多。
Home

构建本地 COM 服务器和客户端:分步示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (44投票s)

2004年10月29日

8分钟阅读

viewsIcon

175769

downloadIcon

4859

一步一步介绍如何构建您自己的本地 COM 服务器和客户端。

引言

许多 CP(CodeProject)会员都写过关于 COM 的文章,为什么还要再写一篇?

大多数人学习 COM 的方式是先构建一个 InprocServer(即 DLL 文件),然后继续编写本地 COM 服务器和客户端。此时,一个展示本地 COM 服务器和客户端的简单示例将非常有帮助。但是,信不信由你,我花了数小时在网上搜索,试图找到一个本地 COM 服务器和客户端的小例子,以便在此基础上构建我自己的系统,但我从未找到!我找到的大多数示例都是 Inproc 服务器。在某些情况下,我找到了本地服务器,但文档只列出了基本步骤/思路,而没有提供代码。事实上,从了解步骤到构建一个实际的服务器还有很长的路要走:您必须编写 idl 文件、reg 文件,您必须将不同的文件包含在正确的位置(甚至按正确的顺序!您会看到的),……,任何一个错误都会导致非常令人困惑的错误消息或结果。然后我找到了 Andrew Troelsen 的书,并尝试按照他的例子来做。然而,我将 CD 中的服务器代码移到我的本地 PC 后,却发现他的本地服务器甚至无法编译——我最终弄清楚了原因(如果您继续阅读就会看到),并且我相信他在书中应该提到这个问题——这样可以为他的读者节省大量的沮丧和搜索时间。同时,他也没有提供客户端代码,所以这还不是一个完整的故事。

因此,我决定写下所有您需要遵循的必要步骤、您需要编写的所有实际代码、您需要创建的所有文件,以及您在决定构建本地 COM 服务器和客户端时需要记住的所有事情——这个小例子可以作为您更伟大、更宏伟(也更具野心)的项目的一个起点。尽管如此,我还是想清楚地说明,一半的功劳应该归于 Andrew Troelsen:对于服务器端,我使用了他的示例和他的代码(已修正问题),如果您想了解更多关于这部分的内容,可以查看他的书。

因此,如果您有兴趣构建本地 COM 服务器和客户端,这个示例可能会对您有所帮助。另外,我假设您已经具备了 COM 的基本知识:架构、接口、COM 运行时服务函数,以及您可以用来获取 GUID 和检查您的组件等的各种工具,等等。在下一节中,我们将构建一个本地 COM 服务器,之后再构建一个客户端。祝您愉快!

分步:构建本地 COM 服务器

本节将介绍构建本地服务器所需的步骤。这个简单的服务器包含一个名为 MyCarcoclass 以及以下三个接口:ICreateMyCarIEngineIStats。整个服务器本身就很能说明问题,那么就开始吧!

步骤 1:启动 VC6.0 并创建一个新的 WIN32 应用程序工作区,选择“一个简单的应用程序”,并将其命名为 CarLocalServer(或您想要的任何名称)。

步骤 2:创建您的 idl 文件,它应该看起来像下面这样,并将其命名为 CarLocalServerTypeInfo.idl

import "oaidl.idl";

// define IStats interface
[object, uuid(FE78387F-D150-4089-832C-BBF02402C872),
 oleautomation, helpstring("Get the status information about this car")]
interface IStats : IUnknown
{
   HRESULT DisplayStats();
   HRESULT GetPetName([out,retval] BSTR* petName);
};

// define the IEngine interface
[object, uuid(E27972D8-717F-4516-A82D-B688DC70170C),
 oleautomation, helpstring("Rev your car and slow it down")]
interface IEngine : IUnknown
{
   HRESULT SpeedUp();
   HRESULT GetMaxSpeed([out,retval] int* maxSpeed);
   HRESULT GetCurSpeed([out,retval] int* curSpeed);
};

// define the ICreateMyCar interface
[object, uuid(5DD52389-B1A4-4fe7-B131-0F8EF73DD175),
 oleautomation, helpstring("This lets you create a car object")]
interface ICreateMyCar : IUnknown
{
   HRESULT SetPetName([in]BSTR petName);
   HRESULT SetMaxSpeed([in] int maxSp);
};

// library statement
[uuid(957BF83F-EE5A-42eb-8CE5-6267011F0EF9), version(1.0),
 helpstring("Car server with typeLib")]
library CarLocalServerLib
{
   importlib("stdole32.tlb");
   [uuid(1D66CBA8-CCE2-4439-8596-82B47AA44E43)]
   coclass MyCar
   {
      [default] interface ICreateMyCar;
      interface IStats;
      interface IEngine;
   };
};

将此文件插入您的项目中。另外,请注意,所有 GUID 都应该使用 guidgen.exe 生成,因此您的 ID 将不会像上面文件中的那样。我上面列出了该文件的每一行,以便您对这个简单的服务器有一个清晰的了解。对于服务器中的其余文件,您可以下载并仔细阅读。

步骤 3:将以下文件插入您的工作区:CarLocalServer.cppMyCar.cppMyCarClassFactory.cppLocks.cppMyCar.hMyCarClassFactory.hLocks.h。现在,请注意这些您真正需要关注的事情(这两件事在 Troelsen 的书中没有提到,但它们是我无法编译的原因)

  1. 请注意包含的 .h 文件,并记住,每当您需要包含 stdafx.h 文件时,都要在包含任何其他文件之前先包含它。如果您不这样做,您的编译器将给出一些非常令人困惑的错误消息(至少我的编译器是这样)。
  2. 打开 stdafx.h,检查该文件是否包含以下行
    #define WIN32_LEAN_AND_MEAN
    // Exclude rarely-used stuff from Windows headers

    如果包含,请确保将其删除(注释掉)。这也是我无法成功编译服务器的另一个原因(我花了一段时间才弄清楚)。

我真的认为 Troelsen 应该提到这两件事。嗯,如果您恰好读了他的书,我希望您也能注意到这篇文章,它可能会为您节省一些时间。

步骤 4:创建 .reg 文件。您可以为该文件取任何您想要的名称,但其内容必须与您从本文下载的完全相同,除了

  1. 您应该使用您刚生成的 ID,并且,
  2. 您应该用您自己的路径替换我在该文件中使用的路径,
  3. 您可以使用不同的名称(保持 coclass 名称 MyCar 不变)。

创建此文件后,双击它,您的 Windows 系统应该会告诉您您的组件已成功注册。

步骤 5:构建整个项目。您应该没有任何编译错误,并请记住您至少应该运行一次。

现在您已经拥有了一个本地 COM 服务器。我再说一遍,这部分代码来自 Troelsen 的书,我只做了一些小的更正,如步骤 3 中所示(以消除编译错误)。此外,我在服务器中还添加了一些错误保护;如果出现问题,您可以得到一些有意义的错误消息。

3. 分步:构建本地 COM 客户端

现在,让我们按照以下步骤构建一个本地 COM 客户端。我将在此处列出源代码并解释重要部分,因为 Troelsen 在他的书中没有提供客户端代码。

步骤 1:再次启动您的 VC6.0 并创建一个新的 WIN32 控制台应用程序工作区。选择“一个空项目”,并将其命名为 CarLocalClient(或您想要的任何名称)。

步骤 2:创建一个新的 .cpp 文件作为您的客户端代码,随意命名,并将其添加到项目中。您的客户端代码应如下所示

#include "../CarLocalServer/CarLocalServerTypeInfo.h"    // use your own path here
#include "../CarLocalServer/CarLocalServerTypeInfo_i.c"  // use your own path here
#include "iostream.h"

// for showing possible mistakes
void ShowErrorMessage(LPCTSTR,HRESULT);

int main()
{
   // initialize the COM runtime
   cout << "Initialize the COM runtime...";
   CoInitialize(NULL);
   cout << "success." << endl;

   // declare variables
   HRESULT hr;
   IClassFactory* pICF = NULL;
   ICreateMyCar*  pICreateMyCar = NULL;
   IEngine*       pIEngine = NULL;
   IStats*        pIStats = NULL;

   cout << endl << "Get the class factory interface for the Car class...";
   hr = CoGetClassObject(CLSID_MyCar,CLSCTX_LOCAL_SERVER, 
                         NULL,IID_IClassFactory,(void**)&pICF);
   if ( FAILED(hr) )
   {
      ShowErrorMessage("CoGetClassObject()",hr);
      exit(1);
   }
   else cout << "success." << endl;

   cout << "Create the Car object and get back the ICreateMyCar interface...";
   hr = pICF->CreateInstance(NULL,IID_ICreateMyCar,(void**)&pICreateMyCar);
   if ( FAILED(hr) )
   {
      ShowErrorMessage("CoCreateInstance()",hr);
      exit(1);
   }
   else cout << "success." << endl;

   // set parameters on the car
   cout << endl << "Set different parameters on the car...";
   pICreateMyCar->SetMaxSpeed(30);
   BSTR carName = SysAllocString(OLESTR("COMCar?!"));
   pICreateMyCar->SetPetName(carName);
   SysFreeString(carName);
   cout << "success." << endl;

   cout << endl << "Query the IStats interface...";
   pICreateMyCar->QueryInterface(IID_IStats,(void**)&pIStats);
   cout << "success." << endl;

   cout << endl << "Use the IStats interface to 
                    display the status of the car:" << endl;
   pIStats->DisplayStats();

   cout << endl << "Query the IEngine interface...";
   pICreateMyCar->QueryInterface(IID_IEngine,(void**)&pIEngine);
   cout << "success." << endl;

   cout << endl << "Start to use the engine..." << endl;
   int curSp = 0;
   int maxSp = 0;
   pIEngine->GetMaxSpeed(&maxSp);
   do
   {
      pIEngine->SpeedUp();
      pIEngine->GetCurSpeed(&curSp);
      cout << "current speed is: " << curSp << endl;
   } while (curSp <= maxSp);

   if ( pICF )         pICF->Release();
   if ( pICreateMyCar) pICreateMyCar->Release();
   if ( pIStats )      pIStats->Release();
   if ( pIEngine )     pIEngine->Release();

   cout << endl << "Close the COM runtime...";
   CoUninitialize();
   cout << "success." << endl;
   return 0;
}

void ShowErrorMessage(LPCTSTR header, HRESULT hr)
{
   void* pMsg;

   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                 FORMAT_MESSAGE_FROM_SYSTEM,NULL,hr,
                 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), 
                 (LPTSTR)&pMsg,0,NULL);

   cout << header << ": Error(" << hex << hr << "): " 
        << (LPTSTR)pMsg << endl;

   LocalFree(pMsg);
}

请注意 include 语句:您实际上不需要包含 windows.h 之类的文件,您只需要包含 CarLocalServerTypeInfo.hCarLocalServerTypeInfo_i.c 文件,而这两个文件已经包含了所有必需的头文件。请注意,这两个文件是在 VC6.0 编译 CarLocalServerTypeInfo.idl 文件后由您的系统生成的,并且它们位于您在第 2 节中创建的工作区中。因此,您需要将它们复制到您的客户端工作区目录,或者您可以使用相对路径,就像我在这里做的那样。

要访问您的 COM 组件(您的 coclass 和接口),您首先调用 COM 服务函数 CoGetClassObject(),并从该调用中获取指向您 IClassFactory 接口的指针。一旦您拥有了这个接口,其余的似乎就很明显了:然后您就可以调用该接口上的 CreateInstance() 来开始您的旅程。您可能已经注意到 CoGetClassObject() 调用中的 CLSTX_LOCAL_SERVER 参数,这就是我们表明我们正在构建本地服务器的地方!

另一种选择是调用 CoCreateInstance() 而不是 CoGetClassObject()。该函数实际上调用 CoGetClassObject(),然后像我们在客户端代码中所做的那样再次调用 CreateInstance()。也许您应该这样做,就像我们在客户端代码中所做的那样:除了性能考虑之外,这样做似乎更安全:通过自己进行两次调用,您可以在每次调用后进行错误保护,因此如果出现问题,您就能相对清楚地知道问题出在哪里。另一方面,如果您使用 CoCreateInstance(),如果此调用失败,您将失去对问题所在位置的控制。请注意,在此客户端代码中,我们只显示错误,没有添加任何异常处理代码——我只是假设您将是添加适当异常处理的人。

步骤 3:构建项目并尝试一下。您应该能够“与”您的 COM 服务器“交谈”,而无需将其作为进程内 DLL 文件。

以上就是构建本地 COM 服务器和客户端的基本步骤。如果您真的尝试一下,您会发现这确实涉及大量工作,而且,同样,任何一个错误都会给您带来非常令人困惑的错误消息。也许,在尝试了这个示例之后,您会开始更欣赏 ATL。

就这些!

本文介绍了一个构建您自己的本地 COM 服务器和客户端的简单示例。其真正的好处(至少我希望如此)是您可以将其用作构建更实用、更大的项目的起点。您可能希望在实际工作中 YaATL,但理解底层原理总是好的。感谢您的阅读,我非常期待您的评论和建议。

© . All rights reserved.