none
Return char arrays from C++ to C# RRS feed

  • Question

  • Hi,

    I have a C++ project in which I have reutnr some variables from C++ to C#.

    These char variables are in main program: 

    char test1[MAX_Q_LEN], test2[MAX_Q_LEN], test3[MAX_Q_LEN];

    After I finish doing something with these variables in my C program, I have to return the values of these variables in a C# program.

    ReturnChar.h

    extern "C" RETURNCHAR_API char* __cdecl testString();

    ReturnChar.cpp

    1. #include "stdafx.h"
    2. #include "ReturnChar.h"
    3.  
    4. // This is an example of an exported variable
    5. RETURNCHAR_API int nReturnChar=0;
    6.  
    7. extern "C" RETURNCHAR_API char* testString()
    8. {
    9.         char test1[5] = "abcd";
    10.         return test1;
    11. }

    CharTest C#                   

    public static class ImportTest    {        [DllImport("ReturnChar.dll", EntryPoint = "testString", CallingConvention = CallingConvention.Cdecl)]        public static extern StringBuilder testString();    }    /// <summary>    /// Interaction logic for MainWindow.xaml    /// </summary>    public partial class MainWindow : Window    {        public MainWindow()        {            InitializeComponent();            try            {                StringBuilder sb = ImportTest.testString();                textBox1.Text = sb.ToString();            }            catch(Exception ex)            {                MessageBox.Show(ex.Message);            }        }    }

    I tried with the above approach but I am not getting the output on the textbox. Also, this should not be an independent function because the values shoud be fetched only after executing the main which will give the right values in these variables. Is this the correct approach to do the above?

    Any suggestions?






    Monday, February 20, 2012 4:46 AM

Answers

  • Hello optimus_prime,

    1. There are several ways to return C-style strings (i.e. NULL-terminated character arrays) from unmanaged code to managed code.

    2. To do so using the style presented in the OP, you need to take note of the following significant points :

    2.1 As mentioned by Mario Cossi, you cannot return a pointer to a local string variable for reasons that Mario cited.

    2.2 You need to specify to the interop marshaler how the unmanaged string is represented when it is returned from the testString() API. In your case, this will be a pointer to an ANSI NULL-terminated character array.

    2.3 You need to allocate a character buffer (in unmanaged code) that can be used by the interop marshaler to construct an equivalent managed string.

    2.4 The memory allocation of the character buffer (in unmanaged code) must be performed using the CoTaskMemAlloc() API.

    2.5 The use of the CoTaskMemAlloc() function is a necessary protocol to enable the interop marshaler to automatically free the allocated memory buffer (using Marshal.FreeCoTaskMem()).

    3. The following is a sample API which adheres to the above points :

    extern "C" RETURNCHAR_API char* testString2()
    {
                    char test1[5] = "abcd";
    		size_t stSize = strlen(test1) + sizeof(char);
    		char* pszReturn = NULL;
    
    		pszReturn = (char*)::CoTaskMemAlloc(stSize);
    		// Copy the contents of test1
    		// to the memory pointed to by pszReturn.
    		strcpy_s(pszReturn, stSize, test1);
    		// Return pszReturn.
    		return pszReturn; 
    }
    

    4. To use the above testString2() function in C#, its DllImport declaration must be done correctly. The following is an example :

    public static class ImportTest 
    {
        [DllImport("ReturnChar.dll", EntryPoint = "testString2", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string testString2();
    }

    4.1 Note the use of the [return:] statement and the MarshalAsAttribute associated with it. Together they indicate that the return value from testString2() is a pointer to a null-terminated ANSI character string.

    4.2 The following is a sample C# test function that calls testString2() :

    static void DoTest()
    {
        string sb = ImportTest.testString2();
        Console.WriteLine(sb.ToString());
    }
    

    4.3 What happens under the covers is that the interop marshaler will take the returned pointer to the ANSI NULL-terminated character array and then dynamically create a managed string from it.

    4.4 The great thing is that after the managed string has been created, the returned pointer will be automatically freed by the interop marshaler using Marshal.FreeCoTaskMem(). This is the reason why the character buffer allocated from the unmanaged function (e.g. testString2()) must be done using CoTaskMemAlloc().

    5. To read more about returning C-style strings from unmanaged code to managed code, please refer to :

    Returning Strings from a C++ API to C# (http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/).

    - Bio.


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

    • Marked as answer by optimus_prime Wednesday, February 22, 2012 4:14 PM
    Wednesday, February 22, 2012 2:28 PM

All replies

  • Several things to work out.

    1) .NET stores strings in Unicode. In C/C++, a char is just one byte. You must provide the appropriate Marshalling information for C# to be able to do the conversion for you. Or else, use a TCHAR on the C side of the fence.

    2) The C code you showed contains a typical bug. You cannot return a pointer to a local variable: local variables are allocated on the stack frame of the function and that stack frame gets destroyed when the function returns. Your code is essentially returning a pointer to some garbage. What is worse is that sometimes that stack space doesn't get overwritten immediately and that can hide the issue for a long time.

    HTH
    --mc

    Monday, February 20, 2012 11:23 AM
  • Hello optimus_prime,

    1. There are several ways to return C-style strings (i.e. NULL-terminated character arrays) from unmanaged code to managed code.

    2. To do so using the style presented in the OP, you need to take note of the following significant points :

    2.1 As mentioned by Mario Cossi, you cannot return a pointer to a local string variable for reasons that Mario cited.

    2.2 You need to specify to the interop marshaler how the unmanaged string is represented when it is returned from the testString() API. In your case, this will be a pointer to an ANSI NULL-terminated character array.

    2.3 You need to allocate a character buffer (in unmanaged code) that can be used by the interop marshaler to construct an equivalent managed string.

    2.4 The memory allocation of the character buffer (in unmanaged code) must be performed using the CoTaskMemAlloc() API.

    2.5 The use of the CoTaskMemAlloc() function is a necessary protocol to enable the interop marshaler to automatically free the allocated memory buffer (using Marshal.FreeCoTaskMem()).

    3. The following is a sample API which adheres to the above points :

    extern "C" RETURNCHAR_API char* testString2()
    {
                    char test1[5] = "abcd";
    		size_t stSize = strlen(test1) + sizeof(char);
    		char* pszReturn = NULL;
    
    		pszReturn = (char*)::CoTaskMemAlloc(stSize);
    		// Copy the contents of test1
    		// to the memory pointed to by pszReturn.
    		strcpy_s(pszReturn, stSize, test1);
    		// Return pszReturn.
    		return pszReturn; 
    }
    

    4. To use the above testString2() function in C#, its DllImport declaration must be done correctly. The following is an example :

    public static class ImportTest 
    {
        [DllImport("ReturnChar.dll", EntryPoint = "testString2", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string testString2();
    }

    4.1 Note the use of the [return:] statement and the MarshalAsAttribute associated with it. Together they indicate that the return value from testString2() is a pointer to a null-terminated ANSI character string.

    4.2 The following is a sample C# test function that calls testString2() :

    static void DoTest()
    {
        string sb = ImportTest.testString2();
        Console.WriteLine(sb.ToString());
    }
    

    4.3 What happens under the covers is that the interop marshaler will take the returned pointer to the ANSI NULL-terminated character array and then dynamically create a managed string from it.

    4.4 The great thing is that after the managed string has been created, the returned pointer will be automatically freed by the interop marshaler using Marshal.FreeCoTaskMem(). This is the reason why the character buffer allocated from the unmanaged function (e.g. testString2()) must be done using CoTaskMemAlloc().

    5. To read more about returning C-style strings from unmanaged code to managed code, please refer to :

    Returning Strings from a C++ API to C# (http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/).

    - Bio.


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

    • Marked as answer by optimus_prime Wednesday, February 22, 2012 4:14 PM
    Wednesday, February 22, 2012 2:28 PM