none
Getting C++ arrays in C# RRS feed

  • Question

  • I am trying a small interop application, in which I am invoking some C++ methods from C#. I have a very basic example woking for invoking a method and returning an integer, which works just fine.

    InteropApp.cpp

    #include "stdafx.h"
    #include "DLLFunctionExposeTest.h"
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        return TRUE;
    }
    DLLFUNCTIONEXPOSETEST_API int AddDigits(int a, int b)
    {
        return a + b;
    }
    

    C# InteropAppTest

    static class TestImport
        {
            [DllImport("DLLFunctionExposeTest.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "fnSumofTwoDigits")]
            public static extern intAddDigits(int a, int b);
        }
    public partial class MainWindow : Window
        {
            public MainWindow()
            {
                try
                {
                InitializeComponent();
                int g = TestImport.AddDigits(2, 1);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }

    In the above C++ application, I want one method to return an array of strings, and another method to return an array of integers. But, as I have read to do the same, I am confused if this would involve marshalling? What would the method prototypes look like both for C++ and C#? What else would be changed to invoke the c++ array-returning function in C# app?

    It would be great to get a simple example for the above thing, since I wasn't able to find any straightforward eg to start with.

    Saturday, February 18, 2012 10:29 AM

Answers

  • Hello optimus_prime,

    1. Returning an Array of Integers from Unmanaged Code (e.g. C++) to Managed Code (e.g. C#).

    1.1 I shall cover this topic first.

    1.2 I shall cover string arrays in another post.

    1.3 Basically, there are 2 ways to return an array of integers from unmanaged code to managed code.

    1.4 The first is by manual memory allocation/deallocation, managed array creation and data setting.

    1.5 The second is by the use of SAFEARRAYs. The use of SAFEARRAYs provides the advantage of automatic memory allocation/deallocation and managed array creation and data setting.

    2. Returning an Array of Integers via Manual Memory Allocation/Deallocation.

    2.1 The following is a sample C++ API that performs this :

    // ReturnIntegerArray() creates in memory an array of
    // 10 integers and returns to the caller a pointer to
    // this array.
    extern "C" DLLFUNCTIONEXPOSETEST_API void ReturnIntegerArray
    (
      /*[out]*/ int** ppIntegerArrayReceiver, 
      /*[out]*/ int* iSizeReceiver
    )
    {	
    	*iSizeReceiver = 10;
    	*ppIntegerArrayReceiver = (int*)::CoTaskMemAlloc(sizeof(int) * 10);
    	
    	for (int i = 0; i < 10; i++)
    	{
    		(*ppIntegerArrayReceiver)[i] = i;
    	}
    }
    

    Note the following pertinent points about the function above :

    • The CoTaskMemAlloc() API is used to allocate the memory for the array.
    • The other API that can be used is GlobalAlloc().
    • Do not use the "new" keyword or the malloc() C library function.
    • The managed counterpart method for CoTaskMemAlloc() (i.e. the method for memory freeing) is Marshal.FreeCoTaskMem().
    • The managed counterpart method for GlobalAlloc() is Marshal.FreeHGlobal().
    • Note that there are no counterpart methods for "new" and malloc(). "new" is C++ compiler-dependent and malloc() is C-lib dependent.
    • Note also that an "out" parameter for indicating the size of the returned array (i.e. iSizeReceiver) is necessary because the caller will not know the length of the returned array.

    2.2 A sample C# client app is listed below :

    [DllImport("DLLFunctionExposeTest.dll", CallingConvention=CallingConvention.Cdecl)]
    public static extern void ReturnIntegerArray([Out] out IntPtr IntegerArrayReceiver, [Out] out int iSizeReceiver);
    
    static void DoTestIntegerArray()
    {
        IntPtr  IntegerArrayReceiver = IntPtr.Zero;
        int     iSizeReceiver = 0;
    
        ReturnIntegerArray(out IntegerArrayReceiver, out iSizeReceiver);
    
        int[] pIntArray = new int[iSizeReceiver];
    
        Marshal.Copy(IntegerArrayReceiver, pIntArray, 0, iSizeReceiver);
    
        for (int i = 0; i < pIntArray.Length; i++)
        {
            Console.WriteLine("{0}", pIntArray[i]);
        }
    
        Marshal.FreeCoTaskMem(IntegerArrayReceiver);
    }

    Note the following pertinent points about the function above :

    • An IntPtr (IntegerArrayReceiver ) is used to receive the pointer to the integer array created and returned by the ReturnIntegerArray() API.
    • After the ReturnIntegerArray() is called, IntegerArrayReceiver will contain the address of the array.
    • We must manually create an integer array (pIntArray) and copy the array elements from the IntegerArrayReceiver array.
    • This is done by using Marshal.Copy().
    • After the data element copying is done, we display the values of the pIntArray.
    • Thereafter, we use Marshal.FreeCoTaskMem() to manually free the IntegerArrayReceiver array which was allocated by ::CoTaskMemAlloc().

    3. I shall post again to give an example for the SAFEARRAY technique.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by optimus_prime Monday, February 20, 2012 5:42 AM
    Saturday, February 18, 2012 12:49 PM
  • Hello optimus_prime,

    1. Returning an Array of Integers via SAFEARRAY.

    1.1 The following is a sample C++ API that performs this :

    template <class T, VARTYPE v>
    void CreateSafeArray
    (
      T* lpT,
      ULONG ulSize,
      SAFEARRAY*& pSafeArrayReceiver
    )
    {
      SAFEARRAYBOUND	rgsabound[1];
      ULONG				ulIndex = 0;
    
      // Initialise receiver.
      pSafeArrayReceiver = NULL;
    
      if (lpT)
      {
    	rgsabound[0].lLbound = 0;
        rgsabound[0].cElements = ulSize;
    
        pSafeArrayReceiver = (SAFEARRAY*)SafeArrayCreate
    	(
          (VARTYPE)v,
          (unsigned int)1,             
          (SAFEARRAYBOUND*)rgsabound
        );
      }
      
      if (pSafeArrayReceiver == NULL)
      {
    	return;
      }
    
      for (ulIndex = 0; ulIndex < ulSize; ulIndex++)
      {
    	long	lIndexVector[1];
    
    	lIndexVector[0] = ulIndex;
    
        SafeArrayPutElement
    	(
          (SAFEARRAY*)pSafeArrayReceiver,  
          (long*)lIndexVector,
    	  (void*)(&(lpT[ulIndex]))
        );
      }
    
      return;
    }
    
    extern "C" DLLFUNCTIONEXPOSETEST_API void ReturnIntegerSafeArray(/*[out]*/ SAFEARRAY** ppSafeArrayReceiver)
    {
    	// Define an array of 10 integers.
    	int iIntegerArray[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    	
    	CreateSafeArray<int, VT_I4>(iIntegerArray, 10, *ppSafeArrayReceiver);
    }
    

    1.2. There are 2 functions included in the code listing above :

    • CreateSafeArray<>()
    • ReturnIntegerSafeArray()

    1.3 CreateSafeArray<>() is a templated helper function that helps to create SAFEARRAYs of various types. The particular type that we are interested in is the integer type which corresponds to the VARIANT type VT_I4. CreateSafeArray<>() takes a C-style array of integers and creates the corresponding SAFEARRAY for it using SAFEARRAY APIs.

    1.4 The ReturnIntegerSafeArray() function is the API that will be called from managed code.

    2. Sample C# Client Code.

    2.1 A sample C# client app is listed below :

    [DllImport("DLLFunctionExposeTest.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void ReturnIntegerSafeArray([Out][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] out int[] IntegerArrayReceiver);
    
    static void DoTestIntegerSafeArray()
    {
        int[] IntegerArray = null;
    
        ReturnIntegerSafeArray(out IntegerArray);
    
        for (int i = 0; i < IntegerArray.Length; i++)
        {
            Console.WriteLine("{0}", IntegerArray[i]);
        }
    }
    Note the following pertinent points about the function above :
    • A managed array of integers (IntegerArray) is declared but is not instantiated.
    • This is because the interop marshaler will instantiate the managed array for us after it receives a SAFEARRAY from the ReturnIntegerSafeArray() API.
    • Note how simple the use of the ReturnIntegerSafeArray() API really is.
    • This is directly to do with the way the ReturnIntegerSafeArray() API is declared (see its DllImport declaration).
    • The ReturnIntegerSafeArray() declaration indicates that it returns (as an "out" parameter) a managed array of integers which is marshaled as a SAFEARRAY of type VarEnum.VT_I4.
    • Now after the ReturnIntegerSafeArray() API completes, the returned SAFEARRAY is used to create the expected managed array.
    • The SAFEARRAY is the best match between an unmanaged array and a managed array.
    • This is because a SAFEARRAY, like its managed counterpart, intrinsically contains the data type of the elements that it contains.
    • A SAFEARRAY also intrinsically contains array dimensional and size (per-dimension) information, just like its managed counterpart.
    • The interop marshaler internally uses SAFEARRAY APIs to extract data elements from the SAFEARRAY and creates a managed array using the information.
    • After the corresponding managed array has been created, the returned SAFEARRAY is automatically destroyed using SafeArrayDestroy().
    • This is unlike the manual method where we had to call Marshal.FreeCoTaskMem() to free the memory of the array created from unmanaged code.

    2.2 My advise to you is to use the SAFEARRAY technique. It provides clean C# client code.

    3. I shall return again to provide some advise on string arrays.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by optimus_prime Monday, February 20, 2012 5:42 AM
    Saturday, February 18, 2012 3:18 PM

All replies

  • Hello optimus_prime,

    1. Returning an Array of Integers from Unmanaged Code (e.g. C++) to Managed Code (e.g. C#).

    1.1 I shall cover this topic first.

    1.2 I shall cover string arrays in another post.

    1.3 Basically, there are 2 ways to return an array of integers from unmanaged code to managed code.

    1.4 The first is by manual memory allocation/deallocation, managed array creation and data setting.

    1.5 The second is by the use of SAFEARRAYs. The use of SAFEARRAYs provides the advantage of automatic memory allocation/deallocation and managed array creation and data setting.

    2. Returning an Array of Integers via Manual Memory Allocation/Deallocation.

    2.1 The following is a sample C++ API that performs this :

    // ReturnIntegerArray() creates in memory an array of
    // 10 integers and returns to the caller a pointer to
    // this array.
    extern "C" DLLFUNCTIONEXPOSETEST_API void ReturnIntegerArray
    (
      /*[out]*/ int** ppIntegerArrayReceiver, 
      /*[out]*/ int* iSizeReceiver
    )
    {	
    	*iSizeReceiver = 10;
    	*ppIntegerArrayReceiver = (int*)::CoTaskMemAlloc(sizeof(int) * 10);
    	
    	for (int i = 0; i < 10; i++)
    	{
    		(*ppIntegerArrayReceiver)[i] = i;
    	}
    }
    

    Note the following pertinent points about the function above :

    • The CoTaskMemAlloc() API is used to allocate the memory for the array.
    • The other API that can be used is GlobalAlloc().
    • Do not use the "new" keyword or the malloc() C library function.
    • The managed counterpart method for CoTaskMemAlloc() (i.e. the method for memory freeing) is Marshal.FreeCoTaskMem().
    • The managed counterpart method for GlobalAlloc() is Marshal.FreeHGlobal().
    • Note that there are no counterpart methods for "new" and malloc(). "new" is C++ compiler-dependent and malloc() is C-lib dependent.
    • Note also that an "out" parameter for indicating the size of the returned array (i.e. iSizeReceiver) is necessary because the caller will not know the length of the returned array.

    2.2 A sample C# client app is listed below :

    [DllImport("DLLFunctionExposeTest.dll", CallingConvention=CallingConvention.Cdecl)]
    public static extern void ReturnIntegerArray([Out] out IntPtr IntegerArrayReceiver, [Out] out int iSizeReceiver);
    
    static void DoTestIntegerArray()
    {
        IntPtr  IntegerArrayReceiver = IntPtr.Zero;
        int     iSizeReceiver = 0;
    
        ReturnIntegerArray(out IntegerArrayReceiver, out iSizeReceiver);
    
        int[] pIntArray = new int[iSizeReceiver];
    
        Marshal.Copy(IntegerArrayReceiver, pIntArray, 0, iSizeReceiver);
    
        for (int i = 0; i < pIntArray.Length; i++)
        {
            Console.WriteLine("{0}", pIntArray[i]);
        }
    
        Marshal.FreeCoTaskMem(IntegerArrayReceiver);
    }

    Note the following pertinent points about the function above :

    • An IntPtr (IntegerArrayReceiver ) is used to receive the pointer to the integer array created and returned by the ReturnIntegerArray() API.
    • After the ReturnIntegerArray() is called, IntegerArrayReceiver will contain the address of the array.
    • We must manually create an integer array (pIntArray) and copy the array elements from the IntegerArrayReceiver array.
    • This is done by using Marshal.Copy().
    • After the data element copying is done, we display the values of the pIntArray.
    • Thereafter, we use Marshal.FreeCoTaskMem() to manually free the IntegerArrayReceiver array which was allocated by ::CoTaskMemAlloc().

    3. I shall post again to give an example for the SAFEARRAY technique.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by optimus_prime Monday, February 20, 2012 5:42 AM
    Saturday, February 18, 2012 12:49 PM
  • Hello optimus_prime,

    1. Returning an Array of Integers via SAFEARRAY.

    1.1 The following is a sample C++ API that performs this :

    template <class T, VARTYPE v>
    void CreateSafeArray
    (
      T* lpT,
      ULONG ulSize,
      SAFEARRAY*& pSafeArrayReceiver
    )
    {
      SAFEARRAYBOUND	rgsabound[1];
      ULONG				ulIndex = 0;
    
      // Initialise receiver.
      pSafeArrayReceiver = NULL;
    
      if (lpT)
      {
    	rgsabound[0].lLbound = 0;
        rgsabound[0].cElements = ulSize;
    
        pSafeArrayReceiver = (SAFEARRAY*)SafeArrayCreate
    	(
          (VARTYPE)v,
          (unsigned int)1,             
          (SAFEARRAYBOUND*)rgsabound
        );
      }
      
      if (pSafeArrayReceiver == NULL)
      {
    	return;
      }
    
      for (ulIndex = 0; ulIndex < ulSize; ulIndex++)
      {
    	long	lIndexVector[1];
    
    	lIndexVector[0] = ulIndex;
    
        SafeArrayPutElement
    	(
          (SAFEARRAY*)pSafeArrayReceiver,  
          (long*)lIndexVector,
    	  (void*)(&(lpT[ulIndex]))
        );
      }
    
      return;
    }
    
    extern "C" DLLFUNCTIONEXPOSETEST_API void ReturnIntegerSafeArray(/*[out]*/ SAFEARRAY** ppSafeArrayReceiver)
    {
    	// Define an array of 10 integers.
    	int iIntegerArray[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    	
    	CreateSafeArray<int, VT_I4>(iIntegerArray, 10, *ppSafeArrayReceiver);
    }
    

    1.2. There are 2 functions included in the code listing above :

    • CreateSafeArray<>()
    • ReturnIntegerSafeArray()

    1.3 CreateSafeArray<>() is a templated helper function that helps to create SAFEARRAYs of various types. The particular type that we are interested in is the integer type which corresponds to the VARIANT type VT_I4. CreateSafeArray<>() takes a C-style array of integers and creates the corresponding SAFEARRAY for it using SAFEARRAY APIs.

    1.4 The ReturnIntegerSafeArray() function is the API that will be called from managed code.

    2. Sample C# Client Code.

    2.1 A sample C# client app is listed below :

    [DllImport("DLLFunctionExposeTest.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void ReturnIntegerSafeArray([Out][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] out int[] IntegerArrayReceiver);
    
    static void DoTestIntegerSafeArray()
    {
        int[] IntegerArray = null;
    
        ReturnIntegerSafeArray(out IntegerArray);
    
        for (int i = 0; i < IntegerArray.Length; i++)
        {
            Console.WriteLine("{0}", IntegerArray[i]);
        }
    }
    Note the following pertinent points about the function above :
    • A managed array of integers (IntegerArray) is declared but is not instantiated.
    • This is because the interop marshaler will instantiate the managed array for us after it receives a SAFEARRAY from the ReturnIntegerSafeArray() API.
    • Note how simple the use of the ReturnIntegerSafeArray() API really is.
    • This is directly to do with the way the ReturnIntegerSafeArray() API is declared (see its DllImport declaration).
    • The ReturnIntegerSafeArray() declaration indicates that it returns (as an "out" parameter) a managed array of integers which is marshaled as a SAFEARRAY of type VarEnum.VT_I4.
    • Now after the ReturnIntegerSafeArray() API completes, the returned SAFEARRAY is used to create the expected managed array.
    • The SAFEARRAY is the best match between an unmanaged array and a managed array.
    • This is because a SAFEARRAY, like its managed counterpart, intrinsically contains the data type of the elements that it contains.
    • A SAFEARRAY also intrinsically contains array dimensional and size (per-dimension) information, just like its managed counterpart.
    • The interop marshaler internally uses SAFEARRAY APIs to extract data elements from the SAFEARRAY and creates a managed array using the information.
    • After the corresponding managed array has been created, the returned SAFEARRAY is automatically destroyed using SafeArrayDestroy().
    • This is unlike the manual method where we had to call Marshal.FreeCoTaskMem() to free the memory of the array created from unmanaged code.

    2.2 My advise to you is to use the SAFEARRAY technique. It provides clean C# client code.

    3. I shall return again to provide some advise on string arrays.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by optimus_prime Monday, February 20, 2012 5:42 AM
    Saturday, February 18, 2012 3:18 PM
  • Hello optimus_prime,

    1. Returning an Array of strings from Unmanaged Code (e.g. C++) to Managed Code (e.g. C#).

    1.1 Just like the integer type that we have just discussed, we can return arrays of strings from unmanaged code to managed code via the 2 afore-mentioned general techniques :

    • Via manual memory allocation/deallocation, managed array creation and data setting.
    • Via SAFEARRAYs.

    2. Returning an Array of strings via Manual Memory Allocation/Deallocation, Managed Array Creation and Data Setting.

    2.1 This technique is possible and is exactly the same in principle to the corresponding one we have just seen for the integer type.

    2.2 However, note that strings are different from integers. They are more complex. C-style strings are NULL-terminated character arrays instead of one single unit of data like the integer.

    2.3 I recommend that you refer to this article for an actual example :

    Returning an Array of Strings from C++ to C# Part 1 (https://limbioliong.wordpress.com/2011/08/14/returning-an-array-of-strings-from-c-to-c-part-1/)

    3. Returning an Array of strings via SAFEARRAY.

    3.1 The principle is exactly the same as the corresponding one we have seen for the integer type.

    3.2 I recommend that you refer to this article for an actual example :

    Returning an Array of Strings from C++ to C# Part 2 (https://limbioliong.wordpress.com/2011/08/14/returning-an-array-of-strings-from-c-to-c-part-2/)

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    Saturday, February 18, 2012 5:17 PM