I was working on a C# application to load and validate embedded devices with multiple firmware configurations. The code to actually load the firmware onto the devices is native C++ code and is object oriented; which means classes are involved. Platform Invoke, also known as P/Invoke, is a .NET feature to call functions in native DLLs; however, interacting with native code objects from managed code is outside the realm of Platform Invoke. So I began to investigate how to interact with native objects from managed code. I began reading about C++/CLI (which replaced Managed C++). I picked up two great books on the subject: C++/CLI In Action by Nishant Sivakumar and Expert C++/CLI: .NET for Visual C++ Programmers by Marcus Heege and began to develop a sample application to test my theories on how to accomplish my end goal: using a managed code user interface that interacts with exported naive C++ objects contained in DLLs.
I started by creating a normal C++ class (CNativeClass) that was structured the same as the class that I would ultimately use in my final project. This was so that I could not only limit the amount of code I would need to develop for the sample, but also to minimize the complexity of my sample to test my theories. The CNativeClass exposes methods to add two values and return the result and to multiply two values and return the result. You can’t get any simpler than that. I structured the callback implementation exactly as it was implemented in the real class. Since I cannot change the C++ code in the native DLL, I must ensure that my managed code will interface to the callback so that the managed code can be called from the native code during the operation. You can think of this as while the firmware is being downloaded to the device (native code) it would be nice to update a progress bar in the user interface (managed code) to indicate the amount of time left to complete the operation.
#pragma once
#include "tchar.h"
#include "StringX.h"
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the NATIVECLASS_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// NATIVECLASS_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef NATIVECLASS_EXPORTS
#define NATIVECLASS_API __declspec(dllexport)
#else
#define NATIVECLASS_API __declspec(dllimport)
#endif
// This class is exported from the NativeClass.dll
class NATIVECLASS_API CNativeClass
{
public:
enum Operation
{
none,
addition,
multiplication
};
enum ResultCode
{
success,
aborted,
fail_unknown,
fail_os
};
// Result object
class NATIVECLASS_API Result
{
public:
Result(const Result &result);
Result(ResultCode code = success, const TCHAR *detail = _T(""));
// Assignment operators
Result& operator = (ResultCode code);
Result& operator = (const Result &result);
// Accessor functions
ResultCode GetCode() const { return code; }
CStringX GetDetail() const { return detail; }
private:
// The status data
ResultCode code;
CStringX detail;
// Obtain textual version of an operating system error
static CStringX GetOSError();
};
struct NATIVECLASS_API Status
{
Status();
Result result;
};
// Callback object
class Callback
{
public:
Callback() {}
virtual ~Callback() {}
// Callback function
virtual void Progress(CNativeClass* pNativeClass, const Status& status) {}
};
private:
// Implementation
Callback* m_pCallback; // Pointer to a Callback object.
Operation m_operation; // Operation processed.
int m_nValue1; // Value 1 of the operation.
int m_nValue2; // Value 2 of the operation.
public:
// Normally I would recommend that all variables be private or protected
// and that member access functions be created; however, in an effort to
// show how to access public member variables this variable is public.
int m_nResult;
public:
CNativeClass();
// Add two values and return the result.
int AddValues(int nValue1, int nValue2);
// Multiple two values and return the result.
int MultiplyValues(int nValue1, int nValue2);
// Callback control
void SetCallback(Callback* pCallback = 0) { m_pCallback = pCallback; }
// Member variable access methods.
Operation GetOperation() { return m_operation; }
int GetValue1() { return m_nValue1; }
int GetValue2() { return m_nValue2; }
};
Now that the DLL was complete I wanted to start at a point which I knew would work to verify my native DLL. So I created a regular C++/MFC dialog based application (MFCAccess) to interface to my native DLL. As you would expect, this went effortlessly.
So on to the challenge. The key about C++/CLI is that C++/CLI provides the most powerful and convenient interop functionality of any CLI language. To bridge the gap between managed code and native code I developed a wrapper using C++/CLI that contains both managed and native code intermixed within the class (NativeClassWrapper). The wrapper is defined as its own C++ CLR Class Library project within the solution. As you can see the NativeClassWrapper defines a pointer to a CNativeClass object (pNativeClass) and contains managed code elements as well.
// NativeClassWrapper.h
#pragma once
#include <vcclr.h>
#include <msclr\auto_gcroot.h>
#include "..\NativeClass\NativeClass.h"
using namespace System;
using namespace System::Runtime::InteropServices;
// CLR Class Library project.
namespace NativeClassWrapper_CLR
{
// Forward class declaration.
class CNativeClassCallback;
public ref class NativeClassWrapper
{
public:
enum class Operation
{
none,
addition,
multiplication
};
enum class ResultCode
{
success,
aborted,
fail_unknown,
fail_os
};
ref class Result
{
public:
// Constructors
Result();
Result(const Result% result);
Result(ResultCode code, const TCHAR* detail);
// Assignment operators
Result^ operator = (ResultCode code);
Result^ operator = (const Result^ result);
// Accessor functions
ResultCode GetCode() { return NativeClassWrapper::Result::code; }
String^ GetDetail() { return NativeClassWrapper::Result::detail; }
private:
// The status data
ResultCode code;
String^ detail;
};
ref struct Status
{
// Constructor
Status();
Status(Result^ result);
// Data
Result^ result;
};
// Callback object
interface class ICallback
{
// Callback function
void Progress(Status^% status);
};
public:
NativeClassWrapper();
~NativeClassWrapper();
protected:
// Finalizer
!NativeClassWrapper();
public:
int AddValues(int nValue1, int nValue2)
{ return pNativeClass->AddValues(nValue1, nValue2); }
int MuliplyValues(int nValue1, int nValue2)
{ return pNativeClass->MultiplyValues(nValue1, nValue2); }
// Callback control
void SetCallback();
void SetCallback(ICallback^ callback);
Operation GetOperation();
int GetValue1() { return pNativeClass->GetValue1(); }
int GetValue2() { return pNativeClass->GetValue2(); }
int GetResult() { return pNativeClass->m_nResult; }
private:
// Points to an instance of the unmanaged CNativeClass class.
CNativeClass* pNativeClass;
// Points to an instance of the unmanged CNativeClassCallback class.
CNativeClassCallback* pNativeClassCallback;
};
public class CNativeClassCallback : public CNativeClass::Callback
{
private:
// Handle to a manged interface.
msclr::auto_gcroot<NativeClassWrapper::ICallback^> managedCallbackInterface;
public:
CNativeClassCallback(NativeClassWrapper::ICallback^ managedCallbackInterface);
// NativeClass callback functions
virtual void Progress(CNativeClass* pNativeClass,
const CNativeClass::Status& nativeStatus);
};
}
Of course the development of the native code wrapper needed to be developed and debugged using a managed code interface – out goes the comfortable MFC dialog based application. I chose to start with developing a C++ CLR Windows Forms Application (CLRAccess). This is C++/CLI code, just like the wrapper code, but the key difference is that the Windows Forms application is strictly a managed application that interfaces with the wrapper which interfaces with the native code, thereby bridging the gap from managed code to native code. This allowed me to test the wrapper before jumping to the final destination, a managed C# application that uses the C++/CLI wrapper to interface with the native code.
I am not going to explain the C++ CLR Windows Forms Application (CLRAccess) since once you see how all this works by reviewing the C# application you should be able to understand the CLR version easily.
Below is the C# application (CSharpAccess) form. Like I said, I wanted to keep it simple. The ‘Add’ button will take the two supplied values and output the result in the edit box, provide the textual formula and provide the status; which in the case of the ‘Add’ function will always be ‘Success: Values were calculated.’ The ‘Multiply’ button will take the two supplied values and output the result in the edit box, provide the textual formula and provide the status; which in the case of the ‘Multiply’ function will always be ‘OS Failure: There was an operating system error.’; however, this message can be ignored. The values are calculated correctly, I just wanted to verify that the NativeClassWrapper is functioning properly when there is an error condition.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Add reference
// Solution Explorer -> References
using NativeClassWrapper_CLR;
// C# Windows Application project.
// Change 'Output path' from bin\Debug\ to ..\Debug
// This is so that the manifest for the NativeClassWrapper DLL can be found
// so that the MSVCR80.DLL and MSVCM80.DLL files can be located.
// http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=98744
// http://www.ddj.com/windows/184406482
// Delegates
// http://msdn.microsoft.com/en-us/magazine/cc301810.aspx
namespace CSharpAccess
{
public partial class CSharpAccessForm : Form, NativeClassWrapper.ICallback
{
private NativeClassWrapper nativeClassWrapper;
public CSharpAccessForm()
{
InitializeComponent();
}
// http://msdn.microsoft.com/en-us/library/8903062a.aspx
// http://msdn.microsoft.com/en-us/library/6w96b5h7(VS.80).aspx
public void Progress(ref NativeClassWrapper.Status status)
{
string operationString = "";
switch (nativeClassWrapper.GetOperation())
{
case NativeClassWrapper.Operation.addition:
operationString = string.Format("{0} + {1} = {2}",
nativeClassWrapper.GetValue1(),
nativeClassWrapper.GetValue2(),
nativeClassWrapper.GetResult());
break;
case NativeClassWrapper.Operation.multiplication:
operationString = string.Format("{0} * {1} = {2}",
nativeClassWrapper.GetValue1(),
nativeClassWrapper.GetValue2(),
nativeClassWrapper.GetResult());
break;
}
operationLabel.Text = operationString;
switch (status.result.GetCode())
{
case NativeClassWrapper.ResultCode.success:
callbackLabel.Text = "Success";
break;
case NativeClassWrapper.ResultCode.aborted:
callbackLabel.Text = "Aborted";
break;
case NativeClassWrapper.ResultCode.fail_unknown:
callbackLabel.Text = "Unknown Failure";
break;
case NativeClassWrapper.ResultCode.fail_os:
callbackLabel.Text = "OS Failure";
break;
}
messageLabel.Text = status.result.GetDetail();
}
private void CSharpAccessForm_Load(object sender, EventArgs e)
{
operationLabel.Text = "";
callbackLabel.Text = "";
messageLabel.Text = "";
// Create handle to native class wrapper.
nativeClassWrapper = new NativeClassWrapper();
nativeClassWrapper.SetCallback(this);
}
private void addButton_Click(object sender, EventArgs e)
{
int nValue1 = Convert.ToInt32(addValue1TextBox.Text);
int nValue2 = Convert.ToInt32(addValue2TextBox.Text);
int result = nativeClassWrapper.AddValues(nValue1, nValue2);
addResultTextBox.Text = result.ToString();
}
private void multiplyButton_Click(object sender, EventArgs e)
{
int nValue1 = Convert.ToInt32(multiplyValue1TextBox.Text);
int nValue2 = Convert.ToInt32(multiplyValue2TextBox.Text);
int result = nativeClassWrapper.MuliplyValues(nValue1, nValue2);
multiplyResultTextBox.Text = result.ToString();
}
private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
}
}
Now let’s take a look at the C# code and see how it interfaces to the wrapper class and the native code. When the form is loaded (CSharpAccessForm_Load) a NativeClassWrapper object is created. The NativeClassWrapper constructor is called which creates a CNativeClass object at which time the CNativeClass constructor is called. The NativeClassWrapper is responsible for maintaining the pointer to the native object.
private void CSharpAccessForm_Load(object sender, EventArgs e)
{
operationLabel.Text = "";
callbackLabel.Text = "";
messageLabel.Text = "";
// Create handle to native class wrapper.
nativeClassWrapper = new NativeClassWrapper();
nativeClassWrapper.SetCallback(this);
}
Next the callback is set by calling the SetCallback method of the NativeClassWrapper class. The NativeClassWrapper::SetCallback method takes as a parameter a handle to an ICallback interface. Since the CSharpAccessForm is defined to support the ICallback interface of the NativeClassWrapper we can use the ‘this’ keyword in the call to reference the current object. In the NativeClassWrapper::SetCallback method a CNativeClassCallback object is created which holds a handle to the CSharpAccessForm object (managedCallbackInterface). A callback is then set in the CNativeClass object to call back to the CNativeClassCallback object which is derived from CNativeClass::Callback. As you can see, the NativeClassWrapper is the middle man in the callback operation.
void NativeClassWrapper::SetCallback(ICallback^ callbackInterface)
{
pNativeClassCallback = new CNativeClassCallback(callbackInterface);
pNativeClass->SetCallback(pNativeClassCallback);
}
When the ‘Add’ button is clicked the AddValues method of the NativeClassWrapper class is called, which in turn calls the AddValues method of the CNativeClass class; where the computation is made, a status is set, and the callback is triggered.
int CNativeClass::AddValues(int nValue1, int nValue2)
{
// Set the operation.
m_operation = addition;
// Remember the passed values.
m_nValue1 = nValue1;
m_nValue2 = nValue2;
// Calculate the result.
m_nResult = nValue1 + nValue2;
// Set the status
CNativeClass::Status status;
status.result = Result(success, _T("Values were calculated."));
// Trigger a callback
if (m_pCallback)
m_pCallback->Progress(this, status);
return m_nResult;
}
The member variable m_pCallback points to a Callback object. Therefore, the call to Progress calls back to the CNativeClassCallback::Progress method which then calls the Progress method in CSharpAccessForm. This little member function here demonstrates the power of C++/CLI. The first parameter is a pointer to the native CNativeClass object and the second parameter is a reference to a CNativeClass::Status object. The method then uses the reference to the CNativeClass::Status object to create a managed version (NativeClassWrapper::Status) to pass into the managed code callback. Notice that a pointer to the native CNativeClass object is passed into the callback but never used. The parameter must exist due to the callback definition in the original non-modifiable native code, but since the pointer to the CNativeClass object is being managed by the wrapper class, the parameter can be ignored. Furthermore, the pointer is irrelevant when calling back into the managed code object.
// Callback function
void CNativeClassCallback::Progress(CNativeClass* pNativeClass,
const CNativeClass::Status& nativeStatus)
{
managedCallbackInterface->Progress(
gcnew NativeClassWrapper::Status(gcnew NativeClassWrapper::Result(
(NativeClassWrapper::ResultCode) nativeStatus.result.GetCode(),
nativeStatus.result.GetDetail())));
}
}
In the managed code (CSharpAccessForm) I wanted to demonstrate the ability to retrieve data from the native object; hence the calls to GetOperaton, GetValue1, GetValue2, and GetResult.
public void Progress(ref NativeClassWrapper.Status status)
{
string operationString = "";
switch (nativeClassWrapper.GetOperation())
{
case NativeClassWrapper.Operation.addition:
operationString = string.Format("{0} + {1} = {2}",
nativeClassWrapper.GetValue1(),
nativeClassWrapper.GetValue2(),
nativeClassWrapper.GetResult());
break;
case NativeClassWrapper.Operation.multiplication:
operationString = string.Format("{0} * {1} = {2}",
nativeClassWrapper.GetValue1(),
nativeClassWrapper.GetValue2(),
nativeClassWrapper.GetResult());
break;
}
operationLabel.Text = operationString;
switch (status.result.GetCode())
{
case NativeClassWrapper.ResultCode.success:
callbackLabel.Text = "Success";
break;
case NativeClassWrapper.ResultCode.aborted:
callbackLabel.Text = "Aborted";
break;
case NativeClassWrapper.ResultCode.fail_unknown:
callbackLabel.Text = "Unknown Failure";
break;
case NativeClassWrapper.ResultCode.fail_os:
callbackLabel.Text = "OS Failure";
break;
}
messageLabel.Text = status.result.GetDetail();
}
So there you have it. A managed application using C++/CLI to interface to a native object developed in C++. This application provided the proof-of-concept that I needed to design and develop the real managed application that would interact with an existing native DLL. Hopefully, my example will help others understand how they can interact with existing native DLLs that exports C++ classes.
In addition to the two books I previously mentioned, I also consulted the following articles on the Internet.
Quick C++/CLI – Learn C++/CLI in less than 10 minutes
.NET An Introduction to Delegates – Jeffrey Richter
The complete source (Visual Studio 2005 solution) can be downloaded using the link below.
http://cid-e7e4366eac188131.office.live.com/self.aspx/Live Writer folder/CSharpCLINativeTest.zip