Greg Dolley’s Weblog

A Blog about Graphics Programming, Game Programming, Tips and Tricks

Archive for January 30th, 2008

Part 1: How to Make Native Calls from Managed Code in C++/CLI

Posted by gregd1024 on January 30, 2008

Sometimes when working in a .NET environment we inevitably need to call a native function. With most .NET languages the programmer doesn’t need to have knowledge of the marshalling going on behind the scenes. However, programming in C++/CLI happens to be the exception.

While C++/CLI certainly does a better job at abstraction than the old managed C++ language in .NET 1.0, it doesn’t compare to the ease of C# or VB.NET.

This tutorial will be split into two posts: basic marshalling and advanced marshalling. This one will cover the basics – how simple data types are marshalled such as strings and character arrays.

The Marshal Class

Take a look at the following namespace:

  • System::Runtime::InteropServices

This namespace contains a class called “Marshal.” It is very versatile and will handle all the marshalling techniques presented in this tutorial. For most cases of managed to native calls, you’ll never have to look anywhere else.

Functions Which Take String Arguments

First, let’s look at a function which takes a series of native strings. We’re going to look at “printf()”. Since native strings are really just character buffer pointers, we need to convert a .NET “String” object into a character buffer pointed to by an IntPtr struct. Once we have an IntPtr, grabbing the native pointer comes down to calling one of its member functions. To do the first step we use the following function in Marshal:

  • System::IntPtr Marshal::StringToHGlobalAnsi(System::String ^s)

This function will allocate a character buffer and return an address via the IntPtr struct. Converting this into a native pointer is a simple matter of calling its “ToPointer()” member function. The “ToPointer()” function looks like this:

  • void *IntPtr::ToPointer(void)

Now we put both functions together like in the following code snippet:

// Get .NET version string, convert to native char *, and print to screen with printf()

 

Version ^v = Environment::Version;

String ^ver_string = String::Format(“{0}.{1}.{2}”, v->Major, v->Minor, v->Build);

 

char *native_string = (char *)Marshal::StringToHGlobalAnsi(ver_string).ToPointer();

printf(“.NET version: %s\r\n”, native_string);

Marshal::FreeHGlobal(IntPtr((void *)native_string));

We must call “FreeHGlobal()” because the previous call to “StringToHGlobalAnsi()” allocated a buffer of characters on the unmanaged heap (just like calling “malloc()” in C or C++). Otherwise we would end up with a memory leak.

Functions Which Return Strings

OK, now we’re going to look at native functions which return strings. This time, we need to do the opposite of what we did in the last section – take a char pointer buffer (or unicode buffer) and convert it into a .NET String object. We do this via the “PtrToStringAnsi()” function inside the Marshal class. It looks like this:

  • System::String ^Marshal::PtrToStringAnsi(System::IntPtr ptr)

You must pass it a managed pointer wrapper instead of a native pointer, but the IntPtr object has a constructor that will do this conversion automatically. You’ll see how to use it in the following example.

We’re going to read some characters from a file via the native “fread()” function. This function fills in a buffer pointed to by the first parameter and returns the number of bytes read. If the function had instead returned a pointer directly via its return value, the conversion technique would be no different.

char file_buffer[256];

FILE *fp = fopen(“c:\\Temp\\test.txt”, “rb”);

 

memset(file_buffer, 0, sizeof(char)*256);

fread(file_buffer, sizeof(char), 10, fp);

String ^file_buffer_string = Marshal::PtrToStringAnsi(IntPtr((void *)file_buffer));

 

fclose(fp);

Objects and Structures

Strings and character buffers may not be the only thing you need to marshal between a managed context and a native call. You may come across a function that takes some predefined object – such as a native struct or class. These cases will be covered in part two of this tutorial which I will be posting in the next couple days.

As always you can get new posts automatically using this RSS feed. Or if you prefer email updates, click here.

-Greg Dolley

Posted in C++/CLI, Tips and Tricks | 2 Comments »