I just found this long blog post I wrote at my work place last year - thought its worthwhile sharing it
Date: 1/21/2010


Introduction

This article aspires to explain one of the ways a managed type (/clr compiled) can be used/invoked from a native type (/clr compiled) and more importantly from unmanaged C/C++ code.

Background 

When I started dabbling in C++/CLI, I often got confused by these words like managed code, unmanaged code, managed type, native types etc, so for someone who happens to be in the same boat, lets try to get that out of the way. Simply put, managed code would be any code that is compiled with the /clr (and its variants like /clr:pure) flag, and Unmanaged code would be the one that is not compiled with /clr (or any of its variants). The concept of managed and native types applies to managed code only. There are no such concepts in the unmanaged world. Everything is "native" in unmanaged code... but the types are not referred to "native types" as such. Again, simply put, a Managed type is a type that is defined with the "ref" keyword (ManagedType in Listing 1 below, for example). A Native type is the one that is declared without the "ref" keyword (NativeType in Listing 2, for example).
Managed types are allocated on managed stack or managed heap. Native types are allocated on native stack or native heap. The managed and native stack are much similar as far as the way memory gets cleaned up. Once the object goes out of scope, the memory is reclaimed. Memory in managed heap is managed by the garbage collector. Memory in native heap is managed by the programmer.

Accessing Managed type from Native type 

First, lets briefly see how to access a managed type from a native type, but the main purpose of this post is to see how one can access a managed type from purely unmanaged code (compiled without /clr). The method is sort of explained here (http://msdn.microsoft.com/en-us/magazine/cc300632.aspx) but I found it somewhat difficult to follow so here is my attempt to simplify it a little. 
As an example, lets invoke the DoSomething method of ManagedType from NativeType, and then invoke the same method from a global UnmanagedFunction (see Listing 6) 

Listing 1 - ManagedType.h

public ref class ManagedType
{
void DoSomething();
};  

To use the ManagedType from NativeType, one could simply try to include the reference type as a member of native type as shown in Listing 1.1 below. It will cause a compiler error. Native types are allocated on native stack/heap but managed types are not. Including a managed type as a member would mean that it will also get allocated on the native stack/heap, which would violate the C++/CLI design.

Listing 1.1 - NativeType.h

class NativeType
{
public:
ManagedType^ managedType; // error...not allowed

Operator overloads make its use much the same as the underlying managed type it wraps. See the implementation of NativeType in Listing 3, for example.

Listing 2 - NativeType.h

#include <vcclr.h>

class NativeType
{
public:
NativeType();
~NativeType();

void proxy();
gcroot<ManagedType^> virtualManagedType;
}  
Listing 3 - NativeType.cpp (compile with /clr)

#include "NativeType.h"

NativeType::NativeType()
{
//one way of assigning managed reference to gcroot<T> handle
virtualManagedType = gcnew ManagedType();
}

void NativeType::proxy()
{
if (virtualManagedType)
{
virtualManagedType->DoSomething();
}

As you can see virtualManagedType can be treated just the same as a reference to ManagedType. 

Accessing Managed type from Unmanaged code  

Now, lets see how to access the ManagedType from unmanaged code. It has no idea of what ManagedType is, but how about a NativeType? After all, isn't native type a simple C/C++ type albeit compiled with /clr ? The answer is not really... 

Listing 4 - UnmanagedCode.cpp (compile without /clr)

#include "NativeType.h" // error...
... 

Doing so produces an error. The reason is the gcroot<T> member of NativeType. Remember that it wraps GCHandle which is a managed type, and unmanaged code has no idea of what that means. 
Here, we can use the _MANAGED precompiler directive and intptr_t to trick the unmanaged code  into believing that the referenced type is an integer type (unmanaged code) called intptr_t. _MANAGED is set when the compilation mode is /clr. We wrap the gcroot<T> handle in _MANAGED to make it invisible to unmanaged code, and expose an intptr_t instead.

Listing 5 - NativeType.h

#include <vcclr.h>

class NativeType
{
public:
NativeType();
~NativeType();

void proxy();

#ifdef _MANAGED
gcroot<ManagedType^> virtualManagedType;
#else
intptr_t virtualManagedType;
#endif


We can replace the gcroot<T> handle with intptr_t because the memory semantics for both are exactly the same. The intptr_t (http://msdn.microsoft.com/en-us/library/323b6b3k.aspx) is simply an integer whose size is either 32-bit or 64-bit depending on the platform. The gcroot<T> has a void pointer (void* _handle) as its only data member, which is also 32-bit or 64-bit integer depending on the platform. 
Now, we can simply include NativeType.h anywhere and use NativeType 

Listing 6 - UnmanagedCode.cpp (compile without /clr)

#include "NativeType.h"

void UnmanagedFunction()
{
NativeType nativeType;
nativeType.proxy(); // calls ManagedType::DoSomething()
}

Its important to keep it in mind that what we did with the _MANAGED and intprt_t is just a trick to get the unmanaged code to "know" about the a member of NativeType called virtualManagedType that occupies a certain size memory, so that it can compile and link correctly. In both cases, the linker links to the same implementation of NativeType which is in fact managed code (remember it is /clr compiled)