AddThis Social Bookmark Button

Print

Intro to Managed C++, Part 2: Mixing Managed and Unmanaged Code
Pages: 1, 2

Making Your Classes Managed

As I have stated previously, when native C++ code is recompiled with /clr, the classes don't automatically become managed and are marked __nogc for the reasons I cited. If your class does meet the requirements of the CLR, however, you can make your class managed by marking it with the __gc modifier to indicate that it is a garbage-collected class, or the __value modifier to indicate that it is a CTS value type.



Embedding Unmanaged Types in Managed Classes

What if your class cannot be made managed by adding the __gc or __value modifiers? You may have code that uses templates or multiple inheritance. You may have code that uses inline assembly to reuse the functionality from something you would usually inherit from that class. For obvious reasons outlined earlier, you cannot inherit a managed class from an unmanaged one, and vice versa. So what do you do if you want to reuse the functionality? For problems like this, the general solution is to either "aggregate" or "embed a pointer." Without going into a lot of low-level details, aggregating an unmanaged class within a managed one causes a lot of problems, and the compiler cannot convert an object from the fc heap to a non-GC reference. The way to do this is to embed a pointer to an unmanaged type within the managed class. It looks something like this (a contrived example):


#using <mscorlib.dll>
using namespace System;
#include <string>


__nogc class Container
{
	int value_;

public:
	Container() : value_(0) {}
	void SetValue(int *val) { value_ = *val;}
	const int& GetValue() { return value_; }
	
};

__gc class ManagedContainer
{
	Container* pContainer;
public:
	
	ManagedContainer()
	{
		pContainer = new Container();
	}


	void SetValue(int val)
	{
		int someValue = val;
	
	
		pContainer->SetValue(&someValue);
	}
	~ManagedContainer()
	{
		delete pContainer;
	}
};

void main()
{
	ManagedContainer *mc = new ManagedContainer();
	int someValue = 42;
	mc->SetValue(someValue);

	System::Console::WriteLine("The value is ", 
	  someValue.ToString());
}

In this solution, I create an embedded pointer to the the unmanaged class type Container inside ManagedContainer and control it explicitly. Will this code work? Perhaps sometimes, but there is a big problem with the code as it stands.

Pinning Pointers

The problem is that the CLR, in its GC, moves object references around. This is not a problem until such a reference is passed to an unmanaged function or used in an unmanaged object. The CLR has no way to keep track of the reference once it transitions to unmanaged code. So in order to prevent the value from getting corrupted, we need to "pin" the pointer using the __pin keyword:


#using <mscorlib.dll>
using namespace System;
#include <string>

__nogc class Container
{
	int value_;

public:
	Container() : value_(0) {}
	void SetValue(int *val) { value_ = *val;}
	const int& GetValue() { return value_; }
	
};

__gc class ManagedContainer
{
	Container* pContainer;
public:
	
	ManagedContainer()
	{
		pContainer = new Container();
	}


	void SetValue(int val)
	{
		int someValue = val;
		int __pin* pinnedInt = &someValue;
		pContainer->SetValue(pinnedInt);
	
	}
	~ManagedContainer()
	{
		delete pContainer;
	}
};

void main()
{
	ManagedContainer *mc = new ManagedContainer();
	int someValue = 42;
	mc->SetValue(someValue);

	System::Console::WriteLine("The value is ", 
	  someValue.ToString());
}

This type of approach leads to the ability to wrap your C++ code with managed wrappers, enabling your C++ code to be used from other managed languages like C# and VB.NET. I will explore more of this in detail, in the next article of this series.

Now, what if we want to go the other way? That is, use managed types from unmanaged code?

Using Managed Types from Unmanaged Code

Managed types cannot directly be used from unmanaged types. This is again related to the fact that the CLR must keep track of object references to implement garbage collection. What if we did want to use an object reference in an unmanaged type? The CLR provides a type, System::Runtime::InteropServices::GCHandle, that treats object references as integers from unmanaged code. The technique for using this function is quite simple: call GCHandle::Alloc() to generate a handle and GCHandle::Free() to free it.

However, this pattern can get quite messy, so there is a better way. In the file gcroot.h, the VC++ team has provided a smart pointer, called gcroot<>, to simplify the use of GCHandle in unmanaged types. With this template, we are able to use a System::String in an unmanaged class, like so:


#using <mscorlib.dll>
#include <vcclr.h>
using namespace System;

class CppClass {
public:
   gcroot<String*> str;   // can use str as if it were String*
   CppClass() {}
};

int main() {
   CppClass c;
   c.str = new String("hello");
   Console::WriteLine( c.str ); // no cast required
}


What's Next?

In this article, I have only scratched the surface of what's possible. The next step is to further look at what IJW accomplishes and where it falls short, and how to make functions managed, how to make your data managed, and how to write managed wrappers around unmanaged functions.

Sam Gentile is a well-known .NET consultant and is currently working with a large firm, using Visual C++ .NET 2003 to develop both unmanaged and managed C++ applications.


Return to ONDotnet.com