2

I'm calling the doThis function in a .c file in a Win32 DLL.

#include <stdio.h>

__declspec(dllexport) double doThis( char *message)
{
    printf("do nothing much");
    return 32.5;
}

using this calling code:

[DllImport(@"\\vmware-host\Shared Folders\c-sharp\Hot\MusicIO\Debug\HelloWorld.dll", 
    CallingConvention=CallingConvention.Cdecl)]
public static extern double doThis(string message);


private void button1_Click(object sender, EventArgs e)
{
    double returned = doThis("what 2");
    MessageBox.Show("Click " + returned);
}

That works fine, but I want the function to return a char *... and return the message variable.

When I change the doThis to return a char *, and the calling code to expect a string, the Win32 Host crashes at runtime.

Any advice?

[weirdly, I think I had this working just before]

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • So I'm changing declaration to `__declspec(dllexport) char *doThis( char *message)` and then returning the `message`... on the calling side, ` public static extern string doThis(string message);`... – Dan Rosenstark Sep 14 '15 at 19:53
  • [http://stackoverflow.com/questions/20752001/passing-strings-from-c-sharp-to-c-dll-and-back-minimal-example](http://stackoverflow.com/questions/20752001/passing-strings-from-c-sharp-to-c-dll-and-back-minimal-example) – 4566976 Sep 14 '15 at 20:03
  • Thanks @4566976, useful but I'm very glad to have the accepted answer below. – Dan Rosenstark Sep 14 '15 at 20:56

1 Answers1

2

Let's suppose for a while this signature worked:

__declspec(dllexport) char* doThis(char* message)

You call it from C# and then you have a char*. You copy it over to a string, and then... then what? What do you do with that char*?

Do you call free on it? The free of which C runtime library by the way? Or maybe you shouldn't since the pointer may be from static memory? You don't know, and the .NET mashaller doesn't know either.


The proper way to handle this is to pass a second char* parameter, that points to some buffer you allocated, and you are responsible for freeing.

Well, in C# that doesn't really have to be you. The marshaller can handle this for you.

So define a signature like this:

__declspec(dllexport) double doThis(char* message, char* output, int maxOutputLength)

The maxOutputLength parameter is a security measure, to let your C code know the maximum length of the message. Use it as you see fit in your C code.

Note: In C++ code, message would be a const char*, while output would remain a char*.


On the C# side, the signature would involve a StringBuilder:

[DllImport(@"HelloWorld.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern double doThis(string message, StringBuilder output, int maxOutputLength);

Then, you allocate a StringBuilder with some initial capacity, and pass it that:

var output = new StringBuilder(1024);
double returned = doThis("what 2", output, output.Capacity);
var outputStr = output.ToString();

And the marshaller handles the plumbing for you.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
Lucas Trzesniewski
  • 50,214
  • 11
  • 107
  • 158
  • Amazing answer with lots of amazing points, including the difference if it were C++ and the consideration of what you would do with the object once returned. Thank you. – Dan Rosenstark Sep 14 '15 at 20:55
  • One additional question: if it's an array of StringBuilder objects, could I pass... an `ArrayList`? – Dan Rosenstark Sep 16 '15 at 16:51
  • 1
    I don't think so. You could pass `char**` (or `IntPtr`) directly and manage the memory yourself, or you could use C++/CLI as an interop layer. P/Invoke works well only for the most common cases, for anything more complicated you'll have to do it yourself. – Lucas Trzesniewski Sep 16 '15 at 17:23