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]

3

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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.
One additional question: if it's an array of StringBuilder objects, could I pass... an ArrayList?
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.