Mixing Managed and Native Types in C++ / CLI

Brief Introduction

C++/CLI (Common Language Infrastructure) was designed to bring C++ to .NET as a means of developing managed code applications. Specifically it helps simplify writing managed code when using C++.

Suppose you are writing a WinForms application in Microsoft Visual Studio with a view to using C++ to write managed code. You may prefer to reuse existing libraries and headers written in C and C++ but are a little unsure as to how this is achieved exactly.

So how can you write managed code and at the sdame time make use of classes from a C++ library? In the spirit of existing postings on this blog, we will we’ll take a minimalist look at mixing managed and native types.

Getting started
The first real challenge you are likely to encounter is how to make use of your traditional C++ types from within managed types. In other words, how to allocate and store them within the realm of the managed heap.

Managed types are created on the managed heap, where the rules that native types take for granted are no longer available — that is, stable addresses in a process’s address space.

To illustrate what I mean, have a look at the following mixed type example. In Visual Studio, create a simple Windows Forms application:

Within the Form1.h file, insert the ‘NativeType’ bits into the code as shown in order to demonstrate how merely inserting native types does not work:

#pragma once

namespace PinPtr {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;

	class NativeType
	{
		// etc...
	};

	/// <summary>
	/// Summary for Form1
	/// </summary>
	public ref class Form1 : public System::Windows::Forms::Form
	{
		NativeType m_native;

	public:
		Form1(void)
		{
			InitializeComponent();
			//
			//TODO: Add the constructor code here
			//
		}

	protected:
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		~Form1()
		{
			if (components)
			{
				delete components;
			}
		}

	private:
		/// <summary>
		/// Required designer variable.
		/// </summary>
		System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		void InitializeComponent(void)
		{
			this->components = gcnew System::ComponentModel::Container();
			this->Size = System::Drawing::Size(300,300);
			this->Text = L"Form1";
			this->Padding = System::Windows::Forms::Padding(0);
			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
		}
#pragma endregion
	};
}

See how this results in an error message similar to the following:

error C4368: cannot define 'm_native' as a member of managed 'PinPtr::Form1': mixed types are not supported

One way to deal with this kind of problem is to instead use pointers, creating and destroying them by writing your own initialization / clean routines. So that those code bits then look something like this:

NativeType* m_native;

void Initialize()
{
    m_native = new NativeType;
}

void Cleanup()
{
    delete m_native;
}

What if you then wish to pass this NativeType pointer as an argument to some other function? For example:

void Initialize()
{
    m_native = new NativeType;
    SomeFunction( m_native );
}

Where SomeFunction is a function that requires a NativePointer pointer as the argument passed to it:

void SomeFunction( NativeType )
{
     // etc...
}

Unforunately things are not that simple. Try compiling this and you see the following error message:

error C2664: 'PinPtr::SomeFunction' : cannot convert parameter 1 from 'PinPtr::NativeType *' to 'PinPtr::NativeType'

This error happens because .NET uses a heap. Heap items can be moved as a result of garbage collection. In order use a pointer to a native type, you will need to “pin” the pointer for the duration of the call function. This is easy enough when you know how. It can be achieved by doing it like this instead:

cli::pin_ptr<NativeType > p = m_native;
SomeFunction( static_cast<NativeType *>( p ) );

Once pin_ptr p goes out of scope the pointer will be free to change again

Full code listing for this WinForm example as shown:

#pragma once

namespace PinPtr {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;

	class NativeType
	{
		// etc...
	};

	void SomeFunction( NativeType* nType )
	{
		// etc...
	}

	/// <summary>
	/// Summary for Form1
	/// </summary>
	public ref class Form1 : public System::Windows::Forms::Form
	{
		NativeType* m_native;

		void Initialize()
		{
			m_native = new NativeType;

			//SomeFunction( m_native );
			cli::pin_ptr<NativeType > p = m_native;
			SomeFunction( static_cast<NativeType *>( p ) );
		}

		void Cleanup()
		{
			delete m_native;
		}

	public:
		Form1(void)
		{
			InitializeComponent();
			//
			//TODO: Add the constructor code here
			//
			Initialize();			
		}

	protected:
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		~Form1()
		{
			if (components)
			{
				delete components;
			}
			Cleanup();
		}

	private:
		/// <summary>
		/// Required designer variable.
		/// </summary>
		System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		void InitializeComponent(void)
		{
			this->components = gcnew System::ComponentModel::Container();
			this->Size = System::Drawing::Size(300,300);
			this->Text = L"Form1";
			this->Padding = System::Windows::Forms::Padding(0);
			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
		}
#pragma endregion
	};
}

Leave a Reply