Andy
Andy

Reputation: 3789

How do I call a function in C++ that returns a string from C#?

I have the following function in C++ that I want to call from C#:

std::string getString();

How do I best do this (using Pinvoke)?

Am I correct in assuming that return allocated memory from C++ to C# is problematic and that it would be better (easier) to allocate a large string in C# and then pass it on to C++ for writing?

In this case I guess I should wrap the C++ function with C ?:

extern "C"
{
    __declspec(dllexport) void get_string(int size, char *buffer)
    {
       call getString and strncpy to buffer 
    } 
}

Upvotes: 2

Views: 385

Answers (2)

xanatos
xanatos

Reputation: 111830

The classical way to do it is:

__declspec(dllexport) void get_string(int size, char *buffer)
{
    std::string str = getString();
    strncpy(buffer, str.c_str(), size);
    buffer[size - 1] = '\0';
} 

C# side:

[DllImport("NativeLibrary", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void get_string(int len, [Out] StringBuilder sb);

and then (very important! You must pre-size the StringBuilder)

var sb = new StringBuilder(100);
get_string(sb.Capacity, sb);
string str = sb.ToString();

You can use a char[], but it is more complex, because then you have to manually "trim" the \0.

There is a more complex way to do it with one less copy of memory... But it is a little more complex:

C++ side:

__declspec(dllexport) void get_string(void (*func)(const char*))
{
    std::string str = getString();
    func(str.c_str());
}

C# side:

[DllImport("NativeLibrary", CallingConvention = CallingConvention.Cdecl)]
public static extern void get_string(StringFromIntPtr.FromIntPtrDelegate func);

public class StringFromIntPtr
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void FromIntPtrDelegate(IntPtr ptr);

    public string Value { get; protected set; }

    public void FromIntPtr(IntPtr ptr)
    {
        Value = Marshal.PtrToStringAnsi(ptr);
    }
}

Then you use like this:

var sfip = new StringFromIntPtr();
get_string(sfip.FromIntPtr);
string str = sfip.Value;

The point is that you pass to the C++ code a delegate to a C# method that knows how to handle a raw pointer to a string (through the use of Marshal.PtrToStringAnsi for example), and the C++ code uses it. Note that it would be much easier to do this in C++/CLI (because you wouldn't need the delegate, the C++/CLI can use both std::string and System.String^)

Upvotes: 4

KABoissonneault
KABoissonneault

Reputation: 2369

If you are using C++/CLI, you can return System::String too. You can construct the System::String from getString().c_str()

Upvotes: 3

Related Questions