Recently I worked on a project to develop a specialized application library for a client. The client was looking for a native library that ran on the network stack and that can service native C++ GUI applications. They also planned to develop a WPF/.NET version of the same GUI application. To accomplish this, they needed a thin managed C++ wrapper on the native C++ library. I thought I would share my experiments with creating a managed wrapper type for a native c++ class.
Let us create a native class called ‘CNative’ which has one get accessor namely ‘get_OneProperty’ which returns a int type. This class is exported from the native library as below.
// file: Native.h
class __declspec(dllexport) CNative
{
public:
int get_OneProperty() ;
};
This class will be imported in a mixed-mode managed C++ library (compiled with clr). One of the most common ways of wrapping the native class is as below:
// file : Managed.net.h, linked with Native.lib
#include “Native.h”
public ref class Managed
{
CNative* pNative ;
public:
property int OneProperty
{
int get();
}
~Managed(); // dispose/destructor
!Managed(); // finalizer
internal:
Managed(CNative*);
static Managed^ Create();
};
// file Managed.net.cpp
Native^ Native::Create()
{
// Get the native pointer using some method.
CNative* pNative = GetNativeObject();
Managed^ managed = gcnew Managed(pNative);
return managed;
}
Managed::Managed(CNative* native) : pNative(native)
{
}
Managed::~Managed()
{
this->!Managed();
}
Managed::!Managed()
{
// We are not deleting the native instance here since the lifetime of the native object is decided by the native library.
}
int Managed::OneProperty::get()
{
return pNative->get_OneProperty();
}
The above code sample assumes that, the managed instance is received by the GUI application in a CLR event(callback). The event is raised when a native callback function notifies of a change in CNative class instance. The CLR event then in turn notifies the managed GUI application of the change through the managed wrapper instance.
The native callback receives a pointer to, say, three instances of CNative class. When this callback is received, the managed layer must obtain a managed wrapper for the native instance and should pass to the .net GUI applications as discussed in previous paragraph.
// callback function inside managed library
void OnNativeMessage(CNative* pNative)
{
Managed^ managed = gcnew Managed(pNative);
…. some managed c++ approach to invoke a managed delegate to notify gui application.
}
Hey, what is happening here. We are creating a new instance of Managed class at every notification. Even when there are only three instances of CNative class, we still are not reissuing those three managed wrapper instances, instead we are creating fresh ones. This cannot be ideal for a performance critical application.
Naturally first approach we would use to counter this problem would be to keep a dictionary datastructure that will map a managed instance to the native pointer. Which will be fine, except that, we now have to worry about additional memory requirements (usually dictionaries have memory-overheads) and management and cleaning up of this dictionary as soon as managed instance is garbage collected.
After some thoughts, I have decided to approach this problem differently.
First, in the native library, I created a root class as below (in fact there was already a root class with some common features, which I extended it as below):
// file: Object.h
class __declspec(dllexport) CObject
{
void* ext
protected:
CObject() : ext(0)
{
}
public:
void* get_Ext()
{
return ext;
}
void set_Ext(void* pExt)
{
ext = pExt;
}
};
Now the Native class derives from this class.
// file: Native.h
#include “Object.h”
class __declspec(dllexport) CNative : public CObject
{
public:
….
….
In the managed library, the “Managed” ref class will now have a templated parent class. Note that, this is a C++ templating on a managed type (not the generic version). Templated parent class has a field which is the pointer to the native class. Additionally a static method is defined to create or obtain the instance of the managed class. The finalizer in this class will do the freeing if necessary. These actions are done on the behalf of the managed class (that derives from the managed templated base) which essentially is the actual representative of the native class in the managed heap.
// file: wrapperbase.net.h
#include <msclr\lock.h>
using namespace System;
using namespace System::Runtime::InteropServices;
template<typename TNative, typename TManaged>
public ref class WrapperBase
{
static Object^ syncRoot = gcnew Object();
protected:
TNative* pNative ;
public:
WrapperBase(TNative* nativePtr) : pNative(nativePtr)
{
}
static TManaged^ ToManaged(TNative* nativePtr)
{
TManaged^ managed = nullptr;
if (!nativePtr) return managed;
// check the external property on the native object.
void* pExt = nativePtr->get_Ext();
if (NULL != pExt)
{
GCHandle^ handle = GCHandle::FromIntPtr((IntPtr)pExt);
managed = static_cast<TManaged^>(handle.Target);
if (managed != nullptr)
return managed;
if (handle) handle->Free();
}
msclr::lock l(TManaged::syncRoot);
managed = gcnew TManaged(nativePtr);
void* pExt = GCHandle::ToIntPtr(GCHandle::Alloc(this, GCHandleType::Weak));
nativePtr->set_Ext(pExt);
return managed;
}
!WrapperBase() // finalizer
{
if (nativePtr)
{
msclr::lock l(TManaged::syncRoot);
GCHandle^ gcHandle = nullptr;
void* pExt = pNative->get_Ext();
if (pExt)
{
gcHandle = GCHandle::FromIntPtr((IntPtr)pExt);
if (gcHandle != nullptr)
gcHandle->Free();
pNative->set_Ext(0);
}
// We are not deleting the native instance here since the lifetime of the native object is decided by the native library.
}
}
};
It is time to make necessary changes in the managed wrapper class to admit the templated managed wrapper base.
#include “Native.h”
#include “wrapperbase.net.h
public ref class Managed : public WrapperBase<CNative, Managed>
{
public:
Managed(CNative*);
property int OneProperty
{
int get();
}
};
// file Managed.net.cpp
Managed::Managed(CNative* native) : WrapperBase(native)
{
}
int Managed::OneProperty::get()
{
return pNative->get_OneProperty();
}
Now we will revisit the callback function we discussed earlier.
// callback function inside managed library
void OnNativeMessage(CNative* pNative)
{
Managed^ managed = Managed::ToManaged(pNative);
…. some managed c++ approach to invoke a managed delegate to notify gui application.
}
As you can see, the “ToManaged” static function will either create a new instance of the managed wrapper or return an existing wrapper of the native object so long as the managed wrapper is not garbage collected. While doing so, a pointer to the GCHandle (tracking handler) is assigned to the native class instance, while the managed instance receives the pointer to the native counter part. The usage of GCHandleType::Weak ensures that the managed wrapper can be garbage collected when not required. This cross reference between managed and native instances ensures that we can easily obtain the native instance from the managed and vice versa.
I hope you enjoyed this article.
