C++/CLI and C#: Writing Managed Wrappers for Native Code

3 Minute(s) to read

The following article is going to go more in-depth into what is required to write a managed wrapper for a native library or application. It requires making use of an intermediary language called C++/CLI, which syntactically could be simply described as a hybrid between C++ and C# and requires using the /clr or /clr:pure compiler argument when compiling a native library using MSVC.

Syntax

The following section describes the syntax for some core types in C++/CLI.

The Rules

The following section outlines what the rules are when developing libraries that use a combination of C++/CLI.

  1. Natively declared and defined classes that use "pure" C++ syntax cannot be consumed or referenced directly from C# code.
  2. References to unmanaged C++ pointers within C++/CLI (managed) types must be declared as private an inaccessible to the consumer in C#.
  3. "Pure" C++ types that are defined must only hold references to GC roots (i.e. managed heap resources) as void pointers. C++ types must then down-cast the void pointers to the required type, within a function definition.
  4. Finalizers and destructors play slightly different roles in C++/CLI.

The following is an example C++/CLI class as written in C++ using the /clr compiler option.

#include <msclr.h>

namespace TestNamespace
{
	public ref class TestClass
	{
		public:
			TestClass() : _unmanagedPointer(0)
			{

			}
			!TestClass()
			{
			// This is invoked by the garbage collector
			}
			~TestClass()
			{
			// This is invoked with .Dispose()
			}
		private:
			void* _unmanagedPointer;
	}
}

When compiled, it can be consumed in C# or any other .NET language as the following.

using TestNamespace;

namespace TestProgram
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			TestClass testClass = new TestClass();
		}

	}
}

The Destructor, Finalizer, and Dispoable

Writing managed C++ types (C++/CLI) is made more confusing when examining semantic differences for resource mangement. The "destructor" (denoted with ~ character) in C++/CLI is the IDisposable implementation for Dispose. This means that if you are to invoke Dispose on a C++/CLI, it will invoke what is otherwise considered as the destructor in a native C++.

The Finalizer on the other hand is denoted using the ! character, and specifies what gets invoked by the garbage collector when it's time to reclaim resources that are held by the type. It's important to note that while C++/CLI types can be used from both C# and C++, but will ultimately have its memory allocated and managed by the CLR (Common-Language Runtime).

You can find more documentation about this semantic difference in the MSDN documentation.

This is a lot to keep in mind, so the following sections should put more into perspective. For the rest of this blog post I am going to be referencing an existing public code project of mine, ArcaneManagedFbx.

The purpose of this particular code project is to wrap the functionality of the unmanaged (i.e. written in C++) Autodesk FBX SDK into an easily consumable .NET wrapper. My motivations behind creating this project was so that I could conveniently import FBX assets into MonoGame's content processing pipeline on Windows. The FBX file format is copyrighted by Autodesk, meaning that there is no open-sourced file format specification.

Consuming from C#

The following section outlines what is required for consuming a library that is written in C++/CLI.

Consuming from Native C++

It's still possible to write both static and dynamic libraries that are considered to be "native C++" that consume and use C++/CLI types.

Resources

Find below a few interesting links that might help you.



Comments