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:
#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:
#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.
[STAThreadAttribute]
int main(array<:string> ^args)
{
int result = ACE::init();
if(result >= 0)
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
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.
#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");
}
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.
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:
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.
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.
protected:
virtual void OnShown(EventArgs^ e) override
{
System::Windows::Forms::Form::OnShown(e);
ACEDOTNETDEMOTASK::instance()->activate();
}
virtual void OnFormClosing(FormClosingEventArgs^ e) override
{
IntFuture futureResult = ACEDOTNETDEMOTASK::instance()->call_exit();
int result = 0;
futureResult.get(result);
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