Search code examples
c#cmarshallingunmanagedmanaged

Marshalling char* C function to C#


I am trying to understand how to marshall the char* type by passing and modifying strings back and forth between managed & unmanaged code. Managed to unmanaged code seems to work fine, but the opposite does not work. Is IntPtr suited for this situation?

C

EXPORT char* CharTest(char* ptchar, unsigned char* ptuchar)
{
    ptchar[0] = 'x';
    ptchar[1] = 'y';
    printf("%s    %s\n", ptchar, ptuchar);
    return(ptchar);
}

C#

[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest(string ptchar, string ptuchar);

static void Main()
{
    string ptchar = "ptchar";
    string ptuchar = "ptuchar";

    Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptchar, ptuchar)));
}

Output

xychar    ptuchar
x?J

Thank you!


Solution

  • You could declare the return type of the imported function as string

    [DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    extern static string CharTest(string ptchar, string ptuchar);
    

    But because of the fact that you are actually returning one of the parameters, you would have to rely on the marshaller not freeing the parameter buffer before copying the return buffer.

    You have two further options:

    • Marshal it yourself. Make sure to place it in a try/finally in case of exceptions
    [DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    extern static IntPtr CharTest(IntPtr ptchar, string ptuchar);
    
    static void Main()
    {
        string ptchar = "ptchar";
        string ptuchar = "ptuchar";
        IntPtr ptcharPtr = IntPtr.Zero;
    
        try
        {
            ptcharPtr = Marshal.StringToHGlobalAnsi(ptchar);
            Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptcharPtr, ptuchar)));
        }
        finally
        {
            Marshal.FreeHGlobal(ptcharPtr);
        }
    }
    
    • Declare the parameter as StringBuilder which means it will be copied both ways. In this case you do not need to look at the return value as it will be the same as the parameter.
    [DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    extern static IntPtr CharTest([In, Out] StringBuilder ptchar, string ptuchar);
    
    static void Main()
    {
        StringBuilder ptchar = new StringBuilder("ptchar");
        string ptuchar = "ptuchar";
    
        CharTest(ptchar, ptuchar);
        Console.WriteLine(ptchar);
    }