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:
-
Download source projects [~60k]
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:
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.
Bostich said
Hi Greg,
i just want to say “Thank you”.
This code snippet saved my day 🙂
gregd1024 said
Bostich,
Nice! 😉
-Greg
piyumali said
Thank You lot. Actually this is what I was searching for about 2 days. You have done a great job. Keep going.
gregd1024 said
piyumali – Thank you, glad it helped! 😉
-Greg
Al_S said
I am looking for the opposite. How to pass a native object to managed code. If you have a native function that returns a struct, how is this method different? Is the method used Marshal::PtrToStructure?