Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

Using ACE with C++ CLI

4.94/5 (8 votes)
6 Jan 2011CPOL2 min read 44.3K   356  
Demonstrates how easy it is to combine ACE and .NET using C++ CLI mixed mode

Introduction

This article demonstrates something I should have tested years ago. I've been wondering about the power and limitations of the Microsoft C++ CLI for years, nearly always choosing C# for my .NET development needs.

What if it is possible to combine a powerful C++ Framework like ACE with .NET Microsoft C++ CLI and mixed mode programming? Turns out it works quite well.

The simple purpose of this program will be to execute code in a native thread, and return a result to the calling managed code.

Prerequisites

Compile ACE as described by the included documentation. I'll assume you know how to set up the include and library paths for the project, and how to set up a mixed mode C++ CLI project.

Follow this link to download ACE.

Coding

What really surprised me was how easy it was – it probably shouldn’t, but it did.

First, we create a Visual C++ Windows Forms Application.

We are going to pull in a few header files from ACE, so open stdafx.h and add the following:

C++
#ifndef STRICT
#define STRICT
#endif

#pragma managed(push,off)
#include <sdkddkver.h />

#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>
#include <string>

#pragma managed(pop)

#pragma managed(push,off) and #pragma managed(pop) turns off and on managed code compilation respectively, enabling unmanaged code compilation.

In ACEDotNetDemo.cpp which contains our C++ CLI main method, we add the following just before our main method:

C++
#pragma managed(push,off)
#ifndef _DEBUG
#pragma comment(lib,"ace")
#else
#pragma comment(lib,"aced")
#endif
#pragma managed(pop)

This lets the linker know that we want to link against "ace.lib" or "aced.lib" in release or debug builds respectively.

The main method is fairly standard, we only call ACE::init() and ACE::fini() to initialize and finalize the framework.

C++
[STAThreadAttribute]
int main(array<:string> ^args)
{
 int result = ACE::init();
 if(result >= 0)
 {
  // Enabling Windows XP visual effects before any controls are created
  Application::EnableVisualStyles();
  Application::SetCompatibleTextRenderingDefault(false); 

  // Create the main window and run it
  Application::Run(gcnew MainForm());

  ACE::fini();

  }
  return result;
}

In ACEDotNetDemoTask.hpp, we declare a very simple class ACEDotNetDemoTask derived from ACE_Task_Base. This is our thread implementation, and the svc method is executed in another thread.

C++
#pragma once

#pragma managed(push,off)

typedef ACE_Future<int> IntFuture;

class ACEDotNetDemoTask : 
 public ACE_Task_Base
{
 ACE_Activation_Queue 
   activation_queue_;

public:
  ACEDotNetDemoTask(void);
  ~ACEDotNetDemoTask(void);

  virtual int svc (void);
  int enqueue (ACE_Method_Request *request);

  IntFuture call_exit();
};

typedef ACE_Singleton<ACEDotNetDemoTask, ACE_Null_Mutex> 
    ACEDOTNETDEMOTASK;

class ExitMethodRequest : 
 public ACE_Method_Request
 {
  IntFuture result_;
public:
 ExitMethodRequest(IntFuture& result)
  : result_(result)
 {
 ACE_TRACE ("ExitMethodRequest::ExitMethodRequest");
 }

 ~ExitMethodRequest( )
 {
  ACE_TRACE ("ExitMethodRequest::~ExitMethodRequest");
 }

 // Sets the value of the IntFuture to -1, and
 // returns -1 causing the svc method to exit.
 virtual int call (void)
 {
  ACE_TRACE ("ExitMethodRequest::call");
  int result = -1;
  result_.set(result);
  return result;
 }
};

#pragma managed(pop)

We use ACE_Singleton to declare a singleton, ACEDOTNETDEMOTASK, for our ACEDotNetDemoTask.

ACEDotNetDemoTask.cpp contains the implementation of ACEDotNetDemoTask.

The svc method dequeues method requests from the activation queue, and calls the call method of the dequeued request until the call method returns -1, quite similar to a standard windows message loop.

C++
int ACEDotNetDemoTask::svc (void)
{
 ACE_TRACE ("ACEDotNetDemoTask::svc");

 while (1)
 {
  auto_ptr<ace_method_request /> 
    request (this->activation_queue_.dequeue ());

  if (request->call () == -1)
  {
    break;
  }
 }
 return 0;
}

The enqueue method enqueues request into the activation queue, for servicing by the svc method:

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

call_exit enqueues an ExitMethodRequest to the activation queue, and returns an IntFuture that allows the caller to get the result from the unmanaged thread.

C++
IntFuture ACEDotNetDemoTask::call_exit()
{
 ACE_TRACE ("ACEDotNetDemoTask::call_exit");
 IntFuture result;

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

 return result;
}

#pragma managed(pop)

That takes care of our unmanaged thread implementation based on ACE_Task_Base.

Interacting with the unmanaged thread from managed C++ CLI code is as you can see really, really simple.

C++
protected:
 virtual void OnShown(EventArgs^ e) override 
 {
  System::Windows::Forms::Form::OnShown(e);
  // Start unmanaged thread
  ACEDOTNETDEMOTASK::instance()->activate();
 }

 virtual void OnFormClosing(FormClosingEventArgs^ e) override 
 {
  // Call unmanaged thread, and tell it to exit
  IntFuture futureResult = ACEDOTNETDEMOTASK::instance()->call_exit();

  // and get the result
  int result = 0;
  futureResult.get(result);
  
  // Wait for the unmanaged thread to exit
  ACEDOTNETDEMOTASK::instance()->wait();

  System::Windows::Forms::MessageBox::Show(
      String::Format(L"Exit Result:{0}",result),
              L"Call Exit");

  System::Windows::Forms::Form::OnFormClosing(e);
 }

Concluding Remarks

No doubt, many of you already know that integrating managed and unmanaged code using mixed mode C++ CLI works amazingly well. But I guess there are many like me who thought this a tantalizing, even probable, idea – but just never got around to verifying it.

What I’ve implemented is a simple active object in unmanaged code; and interacted with it successfully from managed code.

So in the hope that some of you may find it useful, I wrote this little article demonstrating that it actually works very well.

History

  • 6th of January, 2011 - Initial posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)