Greg Dolley’s Weblog

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

Archive for the ‘C++/CLI’ Category

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

Posted by gregd1024 on February 3, 2008

This is an extension from my previous post on C++/CLI marshalling. In this segment I’m going to talk about sending an object from managed code to native code. While doing this kind of marshalling is required less and less these days as we depend more on external .NET components instead of COM DLL’s or native C API’s, there may still be times where you’ll need to do it.

If you would like to play with the original source code discussed in this post, you can download it below:

Native Library

In order to keep the examples as simple as possible we’re going to create our own native library (.lib) instead of using some third party API (rest assured however, whether you’re calling the Win32 API or a third-party DLL, the process remains the same). Our library will contain one function and one native class. The class looks like this:

class NativeObject
{
   public:
      NativeObject();

   public:
      char m_byte;
      char *m_string;
      int m_int;
      float m_float_array[3];
};

We will be creating the .NET equivalent of this class in different project. You will see how to marshal the .NET equivalent object into the following native library function:

void SomeNativeFunction(NativeObject *obj)
{
   printf(“\r\nInside native library function. Object dump:\r\n\r\n”);
   printf(“obj->m_byte = %i\r\n”, obj->m_byte);
   printf(“obj->m_string = %s\r\n”, obj->m_string);
   printf(“obj->m_int = %i\r\n”, obj->m_int);
   printf(“obj->m_float_array[] = {%f, %f, %f}\r\n”,
                                  obj->m_float_array[0],
                                  obj->m_float_array[1],
                                  obj->m_float_array[2]);
}

I put both the class definition and the prototype of SomeNativeFunction() into a header file called, “NativeSampleAPI.h.” This header will used in the managed project so it can have access to the library’s type(s) and function(s).

Managed Object

Now we will create a C++/CLI project using the following wizard template: “CLR Console Application.” The first step is to create a managed class who’s layout exactly matches the native class’s layout byte-for-byte. For simple types such as int’s and char’s, choosing the correct managed type to match the corresponding native type is easy – in C++/CLI the native type “char” matches managed type “Byte,” the type “int” matches “Int32,” etc. However, for non-intrinsic types, choosing the equivalent can be confusing – how do you deal with an array which has an arbitrary size, or a string that is ANSI instead of unicode?

For these cases we use the “MarshalAs” member attribute to give the compiler more details on how we want the final unmanaged type to end up. See below for the complete conversion of our library’s native type to .NET:

[StructLayout(LayoutKind::Sequential)]
public ref class NativeObjectEquivalent
{
   public:
      NativeObjectEquivalent()
      {
         m_byte = 0x02;
         m_string = “Managed object”;
         m_int = 200;
         m_float_array = gcnew array<float>(3);
         m_float_array[0] = 1.0f;
         m_float_array[1] = 2.1f;
         m_float_array[2] = 3.2f;
      }

   public:
      System::Byte m_byte;

      [MarshalAs(UnmanagedType::LPStr)]
      String ^m_string;
      Int32 m_int;

      [MarshalAs(UnmanagedType::ByValArray,
                 SizeConst=3,
                 ArraySubType=UnmanagedType::R4)]
      array<float> ^m_float_array;
};

The “MarshalAs” attribute applies to the member immediately following it. It accepts either a short or an “UnmanagedType” enum as the first parameter telling the compiler exactly which native type to use. If you need to specify more information about the type, such as fixed string length or array size, you would then fill in a series of named parameters following the first. For example, it’s not enough to tell the compiler that “m_float_array” is an array – it also needs to know the array size and the native type of each element. That’s where “SizeConst” and “ArraySubType” come into play. “SizeConst=3” tells the compiler that “m_float_array” is an array of fixed size and the number of elements it contains is three. “ArraySubType=UnmanagedType::R4” tells the compiler that it’s a “float” array (a “double” array would have been specified with “R8” instead of “R4”).

Now that the two types match in size, let’s see how to pass the managed object to the native function. Take a look at the following code in our managed project:

int main(array<System::String ^> ^args)
{
   Console::WriteLine(“Constructing managed object…”);
   NativeObjectEquivalent ^obj = gcnew NativeObjectEquivalent();

 

   Console::WriteLine(“Writing managed object to unmanaged memory space…”);
   IntPtr ptr(Marshal::AllocHGlobal(Marshal::SizeOf(obj->GetType())));
   Marshal::StructureToPtr(obj, ptr, false);

   Console::WriteLine(“Passing managed object to native function…”);
   SomeNativeFunction((NativeObject *)ptr.ToPointer());

   Console::WriteLine();
   Console::WriteLine(“Press any key to continue…”);
   Console::ReadKey();

   return 0;
}

Let’s go through things line by line (not counting print messages). First we create an instance of NativeObjectEquivalent, our managed version of the native class, via gcnew. Then AllocHGlobal() is called in order to allocate a chunk of memory from the unmanaged heap. It’s important that the memory is not on the managed heap so we’re ensured the pointer will never move (managed pointers can have their address changed during heap compaction). At this point we call StructureToPtr(). This function marshals a managed object into an unmanaged block of memory pointed to by the IntPtr. Finally, we call our library function converting the IntPtr into a native void pointer via the ToPointer() call. We must cast the void pointer into a NativeObject pointer to satisfy the compiler.

And… we’re done! Let’s look at the output:

MarshalToNativeAPI_SampleOutput

Notice how m_string is set to “Managed object” – proving that the object inside our native function did indeed originate from the managed project. 😉

Conclusion

This post, of course, only scratches the surface on the many different types of marshalling that can take place. Hopefully it has given you a good starting point for figuring out more advanced marshalling by yourself. If you have any questions or comments feel free to send me an email via my Contact page.

-Greg Dolley

*Get new posts automatically! Subscribe via RSS here. Want email updates instead? Click here.

Advertisements

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

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 »