Passing strings from C# to C++ DLL and back -- minimal example
Asked Answered
O

2

66

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.

Oceanography answered 23/12, 2013 at 22:17 Comment(0)
D
95

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);
Displease answered 23/12, 2013 at 22:19 Comment(19)
nm you editted in an answer to my question. Trying it now, thanks!Oceanography
You just pass a string and let the p/invoke marshaller do the rest.Displease
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().Luciolucita
@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.Displease
@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!Luciolucita
@DavidHeffernan what about passing strings from C# dll to unmanaged c++ code?Antonelli
@DavidHeffernan Thanks, Is there a reason you used stringBuilder instead of String Except for performance concerns?Antonelli
@Alex StringBuilder is needed to pass data back to callerDisplease
@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 codeproject.com/Questions/184183/… ,Antonelli
When returning the length... should the length be the size of the string + 1 for the null terminator?Breach
Genius! Works fineWinglet
@fnc12 That's the second half of the answerDisplease
How to return string as a return value not out parameter?Reflect
@Reflect Why do you need to do that?Displease
@Reflect 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.Displease
@DavidHeffernan anyway I found how to do it in another place. Thank youReflect
@Reflect Make sure you don't leak memoryDisplease
@Reflect care to share where you found how to return string values from C# to C++ ? What's the way to do it in general? I have the same requirement, in order to use C# code from C++, as a library, where the main program is C++. Thx!Summertree
Just to add on top, i'm by no mean expert in C#, anyhow official document mentions performance implication of using StringBuilder which involves 4 allocations if used as part of marshaling. So just to be awared. learn.microsoft.com/en-us/dotnet/framework/interop/…Clan
V
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");
     }
 } 
Varela answered 6/2, 2019 at 21:58 Comment(3)
That's really unconventional, but also extremely cute!Displease
Can you please explain what the ResponseDelegate is in this example?Greaseball
@Greaseball - apologies - added to the answerVarela

© 2022 - 2024 — McMap. All rights reserved.