Search code examples
c#wpfpinvoke

C# WPF passing UTF16 string to a function that accepts char *


I've created a wpf project which has a helper static class that contains all my c++ backend code. One such function is defined as:

public static unsafe class Backend {

    [DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public extern static void write(void* ptr, char* path);
}
public partial class MainWindow : Window
    {
        public MainWindow()
        {
            string path = "mypath";
            InitializeComponent();
            unsafe
            {
                char *p; //convert
                void* myObj = Backend.init_obj(1920, 1080);
                Backend.gen(myObj);
                Backend.write(myObj, p);
            }
        }
    }

The void* ptr is actually my object that is casted in order to marshall it onto the C# side. The problem I face is that whenever I try to invoke this with a string literal in wpf, I get that Visual C# cannot convert this because string literals are encoded in UTF16. Naturally I tried many things other than manually copying the relevant bytes to a char array. Any tips?


Solution

  • One of the things the CLR can do pretty well for interop with C/C++ code is marshalling data structures between managed and unmanaged code. Since strings are pretty important, a lot of work went into making strings marshal as well as possible.

    As a side note, you're using void* for the context object that's created by init and passed to write. Since you're just handing it back, you can replace it with IntPtr and avoid unsafe blocks altogether. IntPtr is always the size of a pointer in the current architecture.

    First, let's change the declaration of the imported functions. CharSet.Ansi tells it to marshal strings as ANSI. The ptr parameter becomes IntPtr

    [DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public extern static IntPtr init(int width, int height);
    
    [DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public extern static void gen(IntPtr ptr);
    
    [DllImport("Mandel.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public extern static void write(IntPtr ptr, string path);
    

    And from there, you can figure out how to modify the function to deallocate ptr and any others you have to call.

    Using those functions becomes a lot easier and a lot cleaner. You don't need the unsafe block and you can pass path directly to write.

    public MainWindow()
    {
        string path = "mypath";
        InitializeComponent();
    
        IntPtr myObj = Backend.init_obj(1920, 1080);
        Backend.gen(myObj);
        Backend.write(myObj, path);
    }
    

    Original comment that got it working:

    Instead of trying to create the char* parameter yourself, change the declaration so the second parameter is string and let the Runtime marshal it for you. Because it's an ANSI string, you're never going to get full unicode fidelity but that's a problem created by the C++ code.