64

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
asutherland
  • 2,849
  • 4
  • 32
  • 50

2 Answers2

92

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);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • nm you editted in an answer to my question. Trying it now, thanks! – asutherland Dec 23 '13 at 22:24
  • You just pass a string and let the p/invoke marshaller do the rest. – David Heffernan Dec 23 '13 at 22:25
  • great answer. clean & clear. just a note that if you don't know the length of the string to be returned, one approach is to make an additional function which returns the size of the buffer required. so first you call that, then allocate your StringBuilder. eg `static extern int fooBufSize()`. – orion elenzil Mar 12 '16 at 18:31
  • 2
    @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. – David Heffernan Mar 12 '16 at 18:42
  • @DavidHeffernan You're right that's typically how it's done in standard libraries. I prefer the explicitness of making a separate function. Or adding a flag parameter along the lines of "justGetBufferSize". Actually, either way is fine. I mainly added the comment to discourage junior folks from guessing at a buffer size that's large enough. Sometimes you can know a maximum size required, but guessing at a sufficient buffer size is a great way to get a mysterious crash down the road! – orion elenzil Mar 12 '16 at 18:50
  • @DavidHeffernan what about passing strings from C# dll to unmanaged c++ code? – AleX_ Sep 20 '16 at 22:55
  • 1
    @DavidHeffernan Thanks, Is there a reason you used stringBuilder instead of String Except for performance concerns? – AleX_ Sep 21 '16 at 15:44
  • @Alex StringBuilder is needed to pass data back to caller – David Heffernan Sep 21 '16 at 16:16
  • @DavidHeffernan, Thanks. for some reason I couldn't pass references to my strings using this method. However I got what I wanted to do by using Marshalling as described in this article http://www.codeproject.com/Questions/184183/Unmanaged-c-to-managed-c-dll-calls-passing-strings , – AleX_ Sep 21 '16 at 22:49
  • When returning the length... should the length be the size of the string + 1 for the null terminator? – Ross Oct 24 '16 at 09:44
  • Genius! Works fine – MasterJedi Dec 29 '16 at 09:58
  • @fnc12 That's the second half of the answer – David Heffernan Apr 29 '20 at 17:38
  • 1
    How to return string as a return value not out parameter? – fnc12 Apr 30 '20 at 16:11
  • @fnc12 Why do you need to do that? – David Heffernan Apr 30 '20 at 20:21
  • @fnc12 not really, because you need to allocate with a shared allocator in the C++ code, and then deallocate using that same shared allocator in the C# code. – David Heffernan Apr 30 '20 at 22:38
  • @DavidHeffernan anyway I found how to do it in another place. Thank you – fnc12 May 01 '20 at 13:18
  • 1
    @fnc12 Make sure you don't leak memory – David Heffernan May 01 '20 at 13:25
15

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");
     }
 } 
Jonny
  • 796
  • 2
  • 10
  • 23