Search This Blog

Sunday, July 14, 2013

Interoperatablity between Managed and Unmanaged Code


1. Abstract:

            Code that executes under the control of the runtime is called managed code. Conversely, code that runs outside the runtime is called unmanaged code. COM components, ActiveX interfaces, and Win32 API functions are examples of unmanaged code.

2. The background of the post:

            I got an assignment to interface with a .net c# dll from my c++ unmanaged static library. Here I have to handle data from both managed and unmanaged processes and which inspired me to come up with a blog which unveils under the hood of Managed C++ programming or C++\CLI.

 3. Managed Vs. Unmanaged

            In this blog I will cover following topics in detail:

            1. What is C++\CLI ? 
            2. Managed C++ Programming
            3. PInvoke
            4. Marshaling 
            5. Interoperability between COM Streams and Managed Streams 

3.1 What is C++\CLI?

            CLI stands for Common Language Infrastructure. C++/CLI is the means to program .NET in C++, similarly like C# or VB.NET are used.

3.1.1 Using .Net types in Managed C++ 

 

     3.1.1.1 Using Pointers and references

            As you know, in C++, we have the "*" to denote a pointer, and in C++/CLI, we have the ^ to denote a handle (reference/pointer). Now, a "*" designates a native pointer which resides on the CRT heap, whereas the handles designate "safe pointers" and reside on the managed heap. The handles can be thought of as references, and unlike native pointers, they won't cause memory leaks if they are not properly deleted, since the GC will take care of that.

            System::Object ^ x = gcnew System::Object();

In addition to the punctuator "^" symbol, we have the percent "%" which stands for a tracking reference;
                     N* pn = new N; // allocate on native heap
                    N& rn = *pn; // bind ordinary reference to native object
                   R^ hr = gcnew R; // allocate on CLI heap
                 R% rr = *hr; // bind tracking reference to gc-lvalue

In general, the punctuator % is to ^ as the punctuator & is to *.

3.1.1.2 Arrays 

             Array in managed heap is as below:settings

             cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3}; 

3.1.1.3. Managed Classes

             To indicate that a class is managed you have to prefix the declaration with the "__gc" modifier. This modifier can be used with classes, struts and variables that are explicitly marked to be managed by the garbage collector.

#using <mscorlib.dll> // to use Console::WriteLine
#include <stdio.h> // to printf()
using namespace System; 
// Mark unmanaged code
#pragma unmanaged
void print(char *msg)
{
    printf("%s\n", msg);
} 
// Switch back to managed code
#pragma managed
int main()
{
// Write to the console through managed call
Console::WriteLine(S"Hello world from managed method");
// use stdio to write to console
print("hello world from unmanaged method");
return 0;
}


3.1.1.4 Pinning Pointers

           Managed pointers are managed and tracked by the garbage collector. When you want to pass a gc pointer to a nongc method, you need a way to tell the garbage collector not to move or discard that object. In all cases you cannot pass a gc pointer to a non gc method directly.When you pin a managed object you may pass the pinned pointer to the unmanaged method.Pinned objects will increment the object reference count and will inform the garbage collector so that it doesn't move the object in memory.

#using <mscorlib.dll>
#include <stdio.h>

using namespace System;
// just a simple unmanged method
#pragma unmanaged
void print_msg(char *s)
{ printf("printf() -> %s\n", s); }
#pragma managed
int main()
{
// Construct a byte array
Byte arr[] = new Byte[20];
// fill the array with: Hello
arr[0] = 'h'; arr[1] = 'e'; arr[2] = 'l'; arr[3] = 'l'; arr[4] = 'o'; arr[5] = 0;
// Pin the pointer to the first element
unsigned char __pin *str = &arr[0]; // Call the managed method print_msg((char *)str);
return 0;
} 

3.2 Marshaling

           Interop marshaling governs how data is passed in method arguments and return values between managed and unmanaged memory during calls. Interop marshaling is a run-time activity performed by the common language runtime's marshaling service.

Marshal Class Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code.

3.3 Platform Invoke (PInvoke)

           PInvoke is a short name for Platform Invoke; it enables managed code to call C-style functions in native DLLs.

This is an important and unique feature of Managed Extensions for C++ is that you can use unmanaged APIs directly. If you do not require customized data marshaling, you do not need to use PInvoke.

Platform invoke relies on metadata to locate exported functions and marshal their arguments at run time. The following illustration shows this process.

A platform invoke call to an unmanaged DLL function




When platform invoke calls an unmanaged function, it performs the following sequence of actions:
Locates the DLL containing the function.
Loads the DLL into memory.
Locates the address of the function in memory and pushes its arguments onto the stack, marshaling data as required.

Locating and loading the DLL, and locating the address of the function in memory occur only on the first call to the function.

Transfers control to the unmanaged function.

Platform invoke throws exceptions generated by the unmanaged function to the managed caller.

To call functions exported from an unmanaged library, a .NET Framework application requires a function prototype in managed code that represents the unmanaged function. To create a prototype that enables platform invoke to marshal data correctly, you must do the following:
  • · Apply the DLLImportAttribute attribute to the static function or method in managed code.
  • · Substitute managed data types for unmanaged data types.
http://msdn.microsoft.com/en-us/library/ac7ay120.aspx - Lists the mapping between managed and unmanaged types.
                  a). MarshalAsAttribute to specify the marshaling for parameters passing during PInvoke specifically. 
                 b). Many unmanaged functions expect you to pass structure as a parameter to the function, in such cases we need to define structures in managed code and provide necessary marshaling(if needed). During PInvoke those structures will be automatically converted to unmanaged.

[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.
[C++] Use the DllImportAttribute to identify the DLL and function. Mark the wrapper method or function with extern "C".

C# using System.Runtime.InteropServices;

public class Win32 {
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr MessageBox(int hWnd, String text,
String caption, uint type);
}

public class HelloWorld {
public static void Main() {
Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
}
}

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}

class Win32API {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);
}

3.4. Example on handling Managed IO Stream in Unmanaged Code.

Below shows the code snippet in the managed c++ wrapper class to receive and convert the System.IO.Stream

1. //Copying Managed stream data of length 'cb' to managed byte array                            
      array<byte>^ managedBuffer;
      managedBuffer = gcnew array<byte>((int)cb);
      bytesRead = ioStream->Read(managedBuffer,0,(int)cb);
               
2. //Copies data from a managed array to an unmanaged memory pointer
     if( bytesRead > 0 )
     {  
         byte* pv = new byte[bytesRead];
                   Marshal::Copy(managedBuffer,0,(IntPtr)pv,(int)(bytesRead)); }
                  delete[] managedBuffer;
             }
3. Now you can use the above unmanaged byte array in your unmanaged code for further processing
           a) You can write to a file and can get the FileStream 
           b) You can get the COM IStream from the byte array using ( SHCreateMemStream or CreateStreamOnGlobal).

4. Conclusion

      1. If you want to access managed library in unmanaged process , then
          a). Create a managed c++ library to access the managed data
                      i. In project settings allow to use common language runtime
                      ii. We need to put: "#using <mscorlib.dll>" in order to start using the .NET Framework.
          b). Marshal to unmanaged memory space.

     2. If you want to access unmanaged apis in managed space, then make use of PInvoke to marshal and call unmanaged APIs directly.

5. References