Search code examples
c#pinvokereturn-typeunicode-string

Unicode string not working with void in P/Invoke


This is an incredibly strange issue and I have no idea what could be causing it...

In a C++ DLL, I have a method, for demonstration purposes this method is extremely simple, you just pass two character pointers in, one is the source and one is the destination. Quite simply, all that happens when you run the method is that it copies all of the character from one of the strings into the other.

This is the method:

extern "C" __declspec(dllexport) void GetPersonName(wchar_t* destination, wchar_t* source) {
    unsigned int i = 0;
    for (i = 0; i < wcslen(source); i++) {
        destination[i] = source[i];
    }
}

And, in C#, I call this method, using DllImport. When I call it, I pass in a StringBuilder as the destination, and then just pass in a Unicode string as the source (this issue doesn't happen if I'm just passing in an ASCII string). Just to demonstrate I added two Chinese characters to it (这个).

[DllImport("CPPDLLImport.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern void GetPersonName(StringBuilder output, string str);

...

 var str = new StringBuilder();
 GetPersonName(str, "Hi这个");

And, it definitely doesn't put what I expected into the StringBuilder - it puts this in: "Hi这个摨玗öö".

At first, I assumed that I have made a mistake in my C++/C# code, and so, in order to debug it I decided to return "i" (in the C++ method), so that I could examine how far the for loop went, and, for some reason, when I did that, it suddenly worked.

When I set the C++ method to return an integer (and put that into a variable in C#, like var i = GetPersonName...), for some reason, it suddenly put the correct string (which is just what the "source" was) into the StringBuilder.

I then tested it a bit further and made it return a Boolean instead of an integer, and just slapped return true at the end - and, once again, it worked fine.

So, whenever the return type is NOT void, it works?

Why am I seeing this behavior? And, how can I make it such that even when the method is set to "void", it gives the correct output.


Solution

  • You have failed to null terminate the destination string in your C++ code. Add that null terminator and your code will be improved.

    size_t len = wcslen(source);    
    for (size_t i = 0; i < len; i++) {
        destination[i] = source[i];
    }
    destination[len] = 0;
    

    Note also that this code avoids calling wcslen on every iteration of the loop. And of course you may equally well use the standard library functionality to perform a C string copy, there is no need to write another implementation of this.

    You still need to set an appropriate capacity for the string builder object or you run the risk of buffer overflow with longer strings. And also it looks like you might have a cdecl vs stdcall calling convention mismatch.