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

集成 ACE 和 ATL

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (6投票s)

2011年1月6日

CPOL

6分钟阅读

viewsIcon

29719

downloadIcon

376

展示了组合 ACE 和 ATL 的一种方法

引言

本文展示了一种结合 ACE 和 ATL 的方法。它并非旨在展示某个功能,而是一个小型的“入门”解决方案,展示了实现此目的的可行方法。

我假设本文主要会引起熟悉 ACE 的开发人员的兴趣,因此对如何设置项目的 ATL 部分进行了较为详尽的描述。

ACE 在多线程和同步方面提供了丰富的功能。它拥有最完善的 API 之一,用于开发快速可靠的 C++ 网络解决方案。该框架为编写严肃的实时软件解决方案提供了最先进的功能。

在本文中,我们将构建一个简单的 COM 服务应用程序,演示一种集成 ACE 和 ATL 的可能方法。ACE 将用于实现从服务到 COM 客户端的异步回调。

ACE 是一个 C++ 库,在开发可靠解决方案方面拥有卓越的记录。有关使用 ACE 的公司和项目列表,请参阅谁在使用 ACE

虽然 ACE 主要用于开发可移植且高效的网络解决方案,但将其与 ATL 集成是一个有趣的概念。相同的方法可用于启用与 TAO 的集成,使我们能够轻松开发组合的 Corba 和 COM 服务。

根据 ACE 的发明者 Douglas C. Schmidt 的说法,ACE 的设计宗旨是可移植、灵活、可扩展、可预测、可靠且经济实惠。

由于它是一个开源项目,因此它确实经济实惠,并且拥有一个活跃且响应迅速的开发人员社区。

作为一名开发人员,我也很欣赏“灵活、可扩展、可预测、可靠”这些特性。

如果您对 ACE 一无所知,请查看此教程

必备组件

请访问http://download.dre.vanderbilt.edu下载 ACE,并按照随附的说明构建项目——或者查看http://www.dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ACE-INSTALL.html获取详细说明。

本文假设 ACE 库的 "config.h" 包含

#define ACE_NTRACE 0

#include "ace/config-win32.h"

将 "ACE_NTRACE" 定义为 0 可通过打开 "ACE_TRACE" 宏,在执行期间启用详细的跟踪信息输出。

请记住设置 "ACE_ROOT" 环境变量并将 "%ACE_ROOT%\lib" 添加到系统路径中。

在 Windows 7 下,请以管理员身份运行 Visual Studio,以便在构建期间自动注册 COM 应用程序。

创建项目

我们将从创建一个标准的 ATL 服务应用程序开始。

请记住在 ATL 项目向导的“应用程序设置”页面上选择“服务 (EXE)”单选按钮。

点击完成,Visual Studio 将创建一个标准的 ATL 服务应用程序。

现在我们需要告诉 Visual Studio 在哪里可以找到 ACE 库。

将 "$(ACE_ROOT)\lib" 添加到 "库目录"。

此时,我们已准备好添加 ATL COM 对象实现,因此切换到“类视图”并从项目弹出菜单中选择“添加”->“类”。

这将弹出“添加类”对话框,我们将在其中选择“ATL 简单对象”。

点击“添加”以打开 ATL 简单对象向导。

在“短名称”字段中输入“Demo”,然后转到“选项”页面。

通过选择“自由”线程模型,我们告诉 COM 我们将能够自行处理同步问题。在此示例中,我们不希望支持聚合,但我们希望通过“ISupportErrorInfo”和“连接点”来提供错误处理支持,以使用 COM 事件提供通知。

此时,我们已经有一个基本的“什么都不做”的 COM 服务,现在是时候开始添加基于 ACE 的功能了,但首先我们需要告诉 Visual Studio 在哪里可以找到允许我们使用 ACE 的头文件。打开项目的属性对话框并将 $(ACE_ROOT) 添加到“包含目录”。

转到链接器->系统页面,并将“子系统”设置更改为“控制台 (/SUBSYSTEM:CONSOLE)”。这在调试期间通常很有用,在生产场景中也无害,因为服务无论如何都是不可见的。

此步骤还允许我们使用 ACE_TMAIN(int argc, ACE_TCHAR* argv[]) 作为我们的入口点。

打开 "stdafx.h",并在文件末尾添加以下 includes

#include "ace/Log_Msg.h"
#include "ace/Svc_Handler.h"
#include "ace/Method_Request.h"
#include "ace/Activation_Queue.h"
#include "ace/Future.h"
#include <vector>

打开 ACEATLDemo.cpp,并在 include 部分之后添加以下内容,以告知链接器 ACE 库的存在

#ifndef _DEBUG
#pragma comment(lib,"ace")
#else
#pragma comment(lib,"aced")
#endif

此时,我们的项目看起来像这样

重新构建项目,我们就可以开始实现基于 ACE 功能的 COM 服务了。

为了使事情有趣,我们将把服务的核心实现为一个活动对象,其中功能在单独的线程上异步执行。该类如下所示

class CDemo;
class CDemoImpl	: public ACE_Task_Base
{
  ACE_Activation_Queue activation_queue_;
  std::vector<CDemo*> clients_;
public:
  CDemoImpl(void);
  ~CDemoImpl(void);
    
  virtual int svc (void);
  int enqueue (ACE_Method_Request *request);
    
  int exitImpl();
  int postMessageImpl(CComBSTR text);
  int registerImpl(CDemo *pDemo);
  int unregisterImpl(CDemo *pDemo);
    
    
  IntFuture callExit();
  void callPostMessage(BSTR bstr);
  IntFuture callRegister(CDemo *pDemo);
  IntFuture callUnregister(CDemo *pDemo);
};

IntFuture 是一个简单的 typedef

typedef ACE_Future<int> IntFuture; 

未来(Future)是一种构造,允许我们等待一个可能在将来出现的值。

ACE 允许我们使用以下声明来实现单例,这种构造保证通过“DemoImpl”只能访问“CDemoImpl”的一个实例。

typedef ACE_Singleton<CDemoImpl, ACE_Null_Mutex> DemoImpl; 

CDemoImpl”类的核心是“svc”函数

int CDemoImpl::svc (void)
{
  ACE_TRACE ("CDemoImpl::svc");
  HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  if (FAILED(hr))
  {
    ACE_ERROR ((LM_ERROR, 
      ACE_TEXT ("CoInitializeEx failed - returned:%d\n"),hr));
  return -1;
  }
    
  while (1)
  {
    auto_ptr<ace_method_request />
      request (this->activation_queue_.dequeue ());
        
    if (request->call () == -1)
    {
      break;
    }
  }
  return 0;
}

请求从激活队列中取出,并使用其“call”函数执行。

一个“ACE_Method_Request”通常看起来像这样

class CExitMethodRequest : public ACE_Method_Request
{
  IntFuture result_;
public:
  CExitMethodRequest(IntFuture& result)
    : result_(result)
  {
    ACE_TRACE ("CExitMethodRequest::CExitMethodRequest");
  }
    
  ~CExitMethodRequest( )
  {
    ACE_TRACE ("CExitMethodRequest::~CExitMethodRequest");
  }    
    
  virtual int call (void)
  {
    ACE_TRACE ("CExitMethodRequest::call");
    int result = DemoImpl::instance()->exitImpl();
    result_.set(result);
    return result;
  }
};

call”函数使用我们的“DemoImpl”单例定义,并设置“IntFuture” “result_”的值,从而通过“IntFuture”将结果提供给调用线程。

svc”函数的对应函数是“enqueue”函数

int CDemoImpl::enqueue (ACE_Method_Request *request)
{
  ACE_TRACE ("CDemoImpl::enqueue");
  return this->activation_queue_.enqueue (request);
}

enqueue”函数的使用方法如下

IntFuture CDemoImpl::callExit()
{
  ACE_TRACE ("CDemoImpl::callExit");
  IntFuture result;

  CExitMethodRequest *request = new CExitMethodRequest(result);
  enqueue	(request);

  return result;
}

打开 ACEATLDemo.cpp 并输入...

typedef ATL::CAtlServiceModuleT< CACEATLDemoModule, IDS_SERVICENAME > Inherited;

...在“CACEATLDemoModule”类定义顶部。然后将以下声明添加到该类中

void RunMessageLoop() throw();

void OnStop() throw();

bool ParseCommandLine(LPCTSTR lpCmdLine,HRESULT* pnRetCode) throw();

并像这样实现它们

void CACEATLDemoModule::RunMessageLoop() throw()
{
  ACE_TRACE( "RunMessageLoop" );
    
  ACE_Reactor::instance()->run_reactor_event_loop();
}

void CACEATLDemoModule::OnStop() throw()
{
  ACE_TRACE( "OnStop" );
  ACE_Reactor::instance()->end_reactor_event_loop();
    
  IntFuture futureResult = DemoImpl::instance()->callExit();
  int result = 0;
  futureResult.get(result);
    
  if(result != -1)
  {
    ACE_ERROR ((LM_ERROR, 
      ACE_TEXT ("callExit failed - returned:%d\n"),result));
  }
  DemoImpl::instance()->wait();
}

bool CACEATLDemoModule::ParseCommandLine(LPCTSTR lpCmdLine,
    HRESULT* pnRetCode) throw()
{
  ACE_TRACE( "ParseCommandLine" );
  bool result = Inherited::ParseCommandLine(lpCmdLine,pnRetCode);
  return result;
}

通过实现 RunMessageLoop,我们有效地替换了 ATL 的默认实现,并使用 ACE 反应器作为标准消息循环的替代。为了正确处理服务控制管理器的停止事件,我们还需要实现 OnStop 方法。由于“DemoImpl”在单独的线程上运行“svc”函数,我们使用“callExit”告诉“svc”是时候退出请求处理循环了,并调用 wait 确保线程已完成执行。ParseCommandLine 使用我们添加到“CACEATLDemoModule”类定义顶部的“Inherited 调用默认实现。它在这里是为了展示如何“挂接”ATL 的命令行处理。为了支持模拟服务控制管理器的停止事件,我们为 Ctrl+C 和 Ctrl+Break 等控制台控制事件实现了一个应用程序处理例程。

BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType)
{
  ACE_TRACE( "ConsoleCtrlHandler" );
  _AtlModule.OnStop();
  return TRUE;
}

现在我们把“_tWinMain”函数改为

int ACE_TMAIN (int argc, ACE_TCHAR * argv[] )
{    
  ACE_TRACE("main");
  int result = 0;
  try
  {    
    STARTUPINFO startupInfo = {sizeof(STARTUPINFO),0,};
    GetStartupInfo(&startupInfo);
    if(IsDebuggerPresent())
    {
      SetConsoleCtrlHandler(ConsoleCtrlHandler,TRUE);
      HRESULT hr = _AtlModule.InitializeCom();
            
      result = _AtlModule.Run(startupInfo.wShowWindow);
            
      _AtlModule.UninitializeCom();
      _AtlModule.Term();
      SetConsoleCtrlHandler(ConsoleCtrlHandler,FALSE);
    }
    else
    {
      result = _AtlModule.WinMain(startupInfo.wShowWindow);
    }
  }
  catch(...)
  {
    ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"),
      ACE_TEXT ("Unknown exception in main")));
  }
	
  return result;
}

此时,我们已经创建了一个可运行的应用程序,它在执行过程中为我们提供了一些有用的信息。当应用程序在调试器下执行时,它将始终作为控制台应用程序运行,而忽略注册表中的“LocalService”设置。打开 ACEATLDemo.idl 并添加

[id(1)] HRESULT PostMessage(BSTR messageText);

到“IDemo”接口的定义中,然后打开 Demo.h 并添加

STDMETHOD(PostMessage)(BSTR messageText);

作为一个 public 方法。打开 Demo.cpp 并实现该方法

STDMETHODIMP CDemo::PostMessage(BSTR messageText)
{
  ACE_TRACE("CDemo::PostMessage");

  DemoImpl::instance()->callPostMessage(messageText);

  return S_OK;
}

callPostMessage 函数在激活队列中排队一个 CPostMessageMethodRequest 请求。

void CDemoImpl::callPostMessage(BSTR bstr)
{
  ACE_TRACE ("CDemoImpl::callPostMessage");
  CPostMessageMethodRequest *request = new CPostMessageMethodRequest(bstr);
  enqueue	(request);
}

当请求出队时,其 call() 函数将调用

int CDemoImpl::postMessageImpl(CComBSTR text)
{
  ACE_TRACE ("CDemoImpl::postMessageImpl");
    
  for(vector<cdemo* />::iterator it = clients_.begin(); 
      it < clients_.end(); 
      it++)
  {
    CDemo *pDemo = (*it);
    pDemo->Fire_OnPostMessage(text.m_str);
  }
  return 0;
}

实现测试客户端

为了测试我们的服务器,我们需要开发一个小型测试应用程序。使用 .NET 和 C# 很容易做到这一点。我们像这样实现客户端

public partial class MainForm : Form
{
  ACEATLDemoLib.Demo demo;
    
  public MainForm()
  {
    InitializeComponent();
  }
    
  protected override void OnShown(EventArgs e)
  {
    base.OnShown(e);
        
    demo = new ACEATLDemoLib.Demo();
	    
    demo.OnPostMessage += new ACEATLDemoLib._IDemoEvents_OnPostMessageEventHandler
			(demo_OnPostMessage);        
  }    
    
  delegate void demo_OnPostMessageDelegate(string messageText);
  void demo_OnPostMessage(string messageText)
  {
    if (InvokeRequired)
    {
      BeginInvoke(new demo_OnPostMessageDelegate(demo_OnPostMessage), messageText);
    }
    else
    {
      messagesTextBox.AppendText(messageText + Environment.NewLine);
    }
  }
    
  private void sendMessageButtonButton_Click(object sender, EventArgs e)
  {
    demo.PostMessage(messageTextBox.Text);
  }
}

在 Visual Studio 2010 中,表单看起来像这样

现在我们可以启动服务器和几个客户端应用程序实例。由于我们启用了 ACE_TRACE 宏,服务器将提供其行为的有趣视图。将此项目视为一个起点,结合 ACE 或 TAO 与 ATL 可以让我们基于提供的功能创建软件。浏览文档,您将找到实时软件开发中一些极具挑战性方面的优质实现。

延伸阅读

Stephen D. Huston、James CE Johnson 和 Umar Syyid 合著的《ACE 程序员指南:网络和系统编程的实用设计模式》一书提供了 ACE 开发的入门介绍。

历史

  • 2011年1月2日 - 初版
© . All rights reserved.