67

I am trying to make the absolute simplest minimal example of how to pass strings to and from a C++ DLL in C#.

My C++ looks like this:

using std::string;

extern "C" {
    string concat(string a, string b){
        return a + b;
    }
}

With a header like

using std::string;

extern "C" {
    // Returns a + b
    __declspec(dllexport) string concat(string a, string b);
}

My C# is

[DllImport("*****.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern string concat(string a, string b);
}

And I am calling it with: Console.WriteLine(concat("a", "b"));

But this gives a System.AccessViolationException. This seems like it out to be the most trivial thing to deal with, but I am completely stuck on it. When I tried to do a similar experiment with a function "Add" that took two doubles and returned a double I had no problems.

2 Answers 2

97

You cannot pass a C++ std::string across an interop boundary. You cannot create one of those in your C# code. So your code can never work.

You need to use interop friendly types at the interop boundary. For instance, null-terminated arrays of characters. That works well when you allocate and deallocate the memory in the same module. So, it's simple enough when passing data from C# to C++.

C++

void foo(const char *str)
{
    // do something with str
}

C#

[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(string str);

....

foo("bar");

In the other direction you would typically expect the caller to allocate the buffer, into which the callee can write:

C++

void foo(char *str, int len)
{
    // write no more than len characters into str
}

C#

[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(StringBuilder str, int len);

....

StringBuilder sb = new StringBuilder(10);
foo(sb, sb.Capacity);
Sign up to request clarification or add additional context in comments.

19 Comments

nm you editted in an answer to my question. Trying it now, thanks!
@orion Thanks. Normally you'd do it with one function. The size param would be ref int. Pass null to the SB to indicate you want to know length. Then call a second time.
@DavidHeffernan Thanks, Is there a reason you used stringBuilder instead of String Except for performance concerns?
How to return string as a return value not out parameter?
@fnc12 Make sure you don't leak memory
|
17

This is the simplest way I like - pass a string in, and use a lambda to get the response

C#

 public delegate void ResponseDelegate(string s);

 [DllImport(@"MyDLL.dll", EntryPoint ="Foo", CallingConvention = CallingConvention.StdCall)]
 public static extern void Foo(string str, ResponseDelegate response);
 ...
 
 Foo("Input", s =>
 {
    // response is returned in s - do what you want with it
 });

C++

 typedef void(_stdcall *LPEXTFUNCRESPOND) (LPCSTR s);

 extern "C"
 {
     __declspec(dllexport) void __stdcall Foo(const char *str, LPEXTFUNCRESPOND respond) 
     {
         // Input is in str
         // Put your response in respond()
         respond("HELLO");
     }
 } 

3 Comments

That's really unconventional, but also extremely cute!
Can you please explain what the ResponseDelegate is in this example?
@anti - apologies - added to the answer

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.