Introduction

Save the work done in the past is a guideline in many enterprises; and they are right! The investment to save generally represents, for a programmer, several thousand men-days. Why throw a code that has proved its worth?

One option open to the programmer is to gradually switch to the new technology. For .NET, the solution is to mix managed code and native code. The approach can be done either in a top-down issue (from UI to low-level layers) or bottom-up (from low-level to UI).

The objective of this document is to present, through two simple examples, how to use the two technologies together, as well as the first “traps” to avoid, or restrictions to be taken into consideration.

Two approaches will be presented:

How to call managed code from native code

How to call native code from managed code

This article is not intended to cover all mixed environment aspects, traps, and tips. It is dedicated to mixed CLR beginners for a “first touch”. For a complete view of development issues, I can’t do anything but advise you to read books as the one from Stephen Phraser: “Pro Visual C++/CLI and the .NET 2.0 Platform” (Apress editor), and specially, part 3: “Unsafe/Unmanaged C++/CLI”.

Calling managed code from native code

This sample shows how a native code (C++) can use a managed class library written in C#, by using an intermediate “mixed code” DLL that exports an API using managed code.

This could seem to be a bit heavy, but this is the only way in some situations:

If the native client is compiled with Visual Studio 2005/2008, some new compiler options allow changing how a native module can use managed code, and the intermediate C++/CLI DLL is useless. For example, Visual Studio 2008 have the “/clr” option.

If a native client is compiled with a “legacy compiler” (i.e., Visual C++ 6), previous specific compiler options are not available; the application designer will have to design an intermediate module as shown above.

We made here the effort to present anything to the native caller of the CLR environment. For example, in order to avoid seeing what is exported into the vcclr.h file. That’s why we are using a void pointer as the wrapped CLR object. Then, the caller thinks that it’s a classical C++ class.

Open the door of a strange world…

As I already said, things begin with including the vcclr.h file. But, as we will internally use CLR code and need to marshal complex types (like strings, arrays, etc.), here are the .NET “includes”:

This native class is allowed to store reference pointers on managed classes, but that’s not our goal as we don’t want to show managed code to the user code.

Moreover, as we use a void pointer to mask the object, a new problem appears: we are not allowed to convert a managed type into an unmanaged pointer. That’s why we use the gcroot<> template helper class.

Notice also how we write “pointers” to managed objects with the ^ character; this means we are using “reference pointers” to a managed class. Remember that class objects in .NET are considered as references when used as function parameters.

Notice also the keyword for .NET allocations: gcnew. This means we are allocating on the garbage collector protected environment, not on the process heap.

Be aware of that at any time, the process heap is completely different from the garbage collector protected environment. We will see that marshaling tasks will have to be done, with huge consequences on the code and performance.

Like all heap allocated objects, we will have to free the allocated memory when it is no more needed; this is done in the class destructor:

We cannot return a System::String object directly into a native string. It must be decomposed into several steps:

Get the System::String object.

Get a global handle on it with the help of Marshal::StringToHGlobalAuto(). Note that we are here using the “auto” version that gets the Unicode returned string, and convert it as necessary into an ANSI string.

Finally, get the pointer on the underlying object of the handle.

We have here three steps instead of one!

Reading reference books on C++/CLI, you will meet other specific keywords as pin_ptr<> and internal_ptr<> that allow you to get the underlying pointer of the object in a short time. See documentations for more details.

The big mix

This standalone sample shows how to build a native console application with MFC and CLR! Except the particularity of how to initialize MFC from a console application, this sample uses concepts that have been seen before. This sample is presented only “for the fun”.

Conclusion (native code calling managed code)

Using managed code in native code is one of the most complex things to do. The sample shown here is very simple. As simple as it is, you have seen some complex considerations. Hope that you will meet many others in your experience on mixed code.

Calling native code from managed code

This sample shows how a CLR code (C#) can use a native class library written in C++, by using an intermediate “mixed code” DLL that exports an API using unmanaged code.

If the .NET client is written in C++/CLI, it can be transformed to call pure native C++ code; but as writing mixed C++/CLI is quite hard, this could be an expensive experience. Minimizing the intermediate mixed DLL is the fastest way to incorporate native code.

The native C++ DLL

The DLL simply exports:

A C++ class

A C-style function

A C-style variable

This paragraph presents object declarations. As they are simplest as possible, comments are unnecessary.

The module is compiled as a regular DLL without any particular option for future use by a .NET module.

The exported C function

The exported C variable

The.NET client

There is nothing to say about this module. Everything is classical.

The mixed native/managed C++ DLL

Here begins the hard work…

Note 1:

C++ .NET classes (managed) cannot inherit from native C++ classes. Writing a C++ managed class compels us to internally embed an instance of any native C++ object. Moreover, in order to be used by other managed code, a C++ managed class cannot use unmanaged types as parameters or attributes.

Note 2:

Declaring a member CPerson _person2; would generate a C4368 compiler error (cannot define ‘member’ as a member of managed ‘type’: mixed types are not supported).

That’s why a pointer (seen as ‘unsafe‘ in C#) is used internally.

What says the documentation:

You cannot embed a native data member in a CLR type. You can, however, declare a pointer to a native type and control its lifetime in the constructor and destructor and the finalizer of your managed class (see Destructors and Finalizers in Visual C++ for more information).

Notice the use of the pin_ptr keyword in order to protect the string against CLR operations.

A pinning pointer is an interior pointer that prevents the object pointed into from moving on to the garbage-collected heap (the value of a pinning pointer is not changed by the common language runtime). This is necessary when passing the address of a managed class to an unmanaged function because the address will not change unexpectedly during the resolution of the unmanaged function call.

The object is no longer pinned when its pinning pointer goes out of scope, or is set to nullptr.

C-style APIs

C-style APIs can be used in two ways:

Using a wrapper method/attribute

Using the [DllImport] attribute as method decoration

Note that the second way can only be used on functions. It cannot be used with a variable export. In order to call variable exports, the developer must use the first way.

Conclusion (managed code calling native code)

If we can see that importing native code into managed code is simpler than the opposite, consider that writing the “intermediate assembly” is not so easy.

You will have to make sure that the investment is really less than that for a complete code migration. Consider redesigning a complete application taking into account an ISO-functional rewriting to managed code (C# is so similar to C++) could be less expensive than a migration. Moreover, the final application architecture is often cleaner.