Marshaling C++ std::string in C# is possible and doable and it is done
Marshaling C++ std::string in C# is difficult, developers asked questions for help on the web everywhere for many years since .NET was born and PInvoke Interop became a must between C++ and .NET.(C++/CLI uses PInvoke as well, it is implicit P/Invoke, the actual P/Invoke implementation is invisible to the developers, but it still uses P/Invoke), I have a list of questions asked on the web here, they are all different situations, but all they want to do is to able to marshal or access the std::string from C#.
C# Strings in C++
Interface to C++ (unmanaged) DLL and std::string or std::wstring
Pass C# string to C++ and pass C++ result (string, char*.. whatever) to C#
All the answers point out that there is no way you can do in C# because the nature of std:string. Is that really true that you really do not have any way to marshal a std::string in C#? It is really a simple object in C++, right?
A std::string in C++, is not a sequence of char like char*, char[], it is an instance of C++ class, which means you will have to access it through the interface of the std::string class. You can not simply marshal a std::string in C++ to string, StringBuilder in C#, it is not going to work, it is just wrong, the .NET run-time will throw out memory access violation exception.
C# Wrapper Library for C++ Run-Time Classes
All that said, it is difficult to create and access a std::string from C#, but the truth is that you can, you can access any instance of C++ class from C# if it can be accessed from C++ code via dynamic linking. That is where the C# Wrapper Library for C++ run-time comes into play, it will make the std::string to C# string marshaling so easy. The C# Wrapper Library implements wrapper classes for most of the run-time standard C++ classes. It is certainly a big helper when automatically generating a C# wrapper for C++ DLL, It is also very useful when you have C DLL involving some standard C++ classes in its interface of C style methods. For any standard classes, such as std::string, std::wstring, std::iostream, std::fstream, you won’t be able to create or access them without such a wrapper library in C#.
A C++ std::string can be created and accessed from C# by using its C# wrapper class, StdString.(Same to std::wstring, it has wrapper class, StdWString)
Metadata of StdString, the C# wrapper class of std::string
I am showing the complete metadata of the C# wrapper class of StdString.
using System; using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace NSpeech.Win32.StdLib { public class StdString : StdLibExpressBaseClass { public StdString(); public StdString(IntPtr handle); public StdString(StdString that); public StdString(string s); public StdString(StdString str, ulong pos); public StdString(StdStringCIterator first, StdStringCIterator last); public StdString(StdStringCRIterator first, StdStringCRIterator last); public StdString(string s, ulong n); public StdString(ulong n, char c); public StdString(IntPtr thisObject, IntPtr thatObject, bool initialize); public StdString(StdString str, ulong pos, ulong len); public static implicit operator string(StdString o); public static implicit operator StdString(string o); protected override int StorageSize { get; } public string TemplateClassName { get; } public char this[ulong index] { get; set; } public StdString append(StdString right); public StdString append(string ptr); public StdString append(StdStringCIterator first, StdStringCIterator last); public StdString append(StdStringCRIterator first, StdStringCRIterator last); public StdString append(string first, string last); public StdString append(string ptr, ulong count); public StdString append(ulong count, char ch); public StdString append(StdString right, ulong roff, ulong count); public StdString assign(StdString right); public StdString assign(string ptr); public StdString assign(StdStringCIterator first, StdStringCIterator last); public StdString assign(StdStringCRIterator first, StdStringCRIterator last); public StdString assign(string first, string last); public StdString assign(string ptr, ulong count); public StdString assign(ulong count, char ch); public StdString assign(StdString right, ulong roff, ulong count); public char at(ulong off); public char back(); public StdStringCIterator begin(); public string c_str(); public ulong capacity(); public StdStringCIterator cbegin(); public StdStringCIterator cend(); protected override void CleanupNativeResource(); public void clear(); public int compare(StdString right); public int compare(string ptr); public int compare(ulong off, ulong n0, StdString right); public int compare(ulong off, ulong n0, string ptr); public int compare(ulong off, ulong n0, string ptr, ulong count); public int compare(ulong off, ulong n0, StdString right, ulong roff, ulong count); public ulong copy(StringBuilder ptr, ulong count, ulong off); public StdStringCRIterator crbegin(); public StdStringCRIterator crend(); public string data(); public bool empty(); public StdStringCIterator end(); public StdStringIterator erase(StdStringCIterator p); public StdStringIterator erase(StdStringCIterator first, StdStringCIterator last); public StdString erase(ulong off, ulong count); public ulong find(char ch, ulong off); public ulong find(StdString right, ulong off); public ulong find(string ptr, ulong off); public ulong find(string ptr, ulong off, ulong count); public ulong find_first_not_of(char ch, ulong off); public ulong find_first_not_of(StdString right, ulong off); public ulong find_first_not_of(string ptr, ulong off); public ulong find_first_not_of(string ptr, ulong off, ulong count); public ulong find_first_of(char ch, ulong off); public ulong find_first_of(StdString right, ulong off); public ulong find_first_of(string ptr, ulong off); public ulong find_first_of(string ptr, ulong off, ulong count); public ulong find_last_not_of(char ch, ulong off); public ulong find_last_not_of(StdString right, ulong off); public ulong find_last_not_of(string ptr, ulong off); public ulong find_last_not_of(string ptr, ulong off, ulong count); public ulong find_last_of(char ch, ulong off); public ulong find_last_of(StdString right, ulong off); public ulong find_last_of(string ptr, ulong off); public ulong find_last_of(string ptr, ulong off, ulong count); public char front(); public StdStringIterator insert(StdStringCIterator where); public StdStringIterator insert(StdStringCIterator p, char c); public StdString insert(ulong off, StdString right); public StdString insert(ulong off, string ptr); public void insert(StdStringCIterator p, StdStringCIterator first, StdStringCIterator last); public void insert(StdStringCIterator p, StdStringCRIterator first, StdStringCRIterator last); public void insert(StdStringCIterator where, string first, string last); public void insert(StdStringCIterator p, ulong n, char c); public StdString insert(ulong off, string ptr, ulong count); public StdString insert(ulong off, ulong count, char ch); public StdString insert(ulong off, StdString right, ulong roff, ulong count); public ulong length(); public ulong max_size(); protected IntPtr operator_get_set(ulong off); public StdString operator_plus_equal(char ch); public StdString operator_plus_equal(StdString right); public StdString operator_plus_equal(string ptr); public void pop_back(); public void push_back(char ch); public StdStringCRIterator rbegin(); public StdStringCRIterator rend(); public StdString replace(StdStringCIterator i1, StdStringCIterator i2, StdString str); public StdString replace(StdStringCIterator i1, StdStringCIterator i2, string s); public StdString replace(ulong off, ulong n0, StdString right); public StdString replace(ulong off, ulong n0, string ptr); public StdString replace(StdStringCIterator i1, StdStringCIterator i2, StdStringCIterator first, StdStringCIterator last); public StdString replace(StdStringCIterator first, StdStringCIterator last, string first2, string last2); public StdString replace(StdStringCIterator i1, StdStringCIterator i2, string s, ulong n); public StdString replace(StdStringCIterator i1, StdStringCIterator i2, ulong n, char c); public StdString replace(ulong off, ulong n0, string ptr, ulong count); public StdString replace(ulong off, ulong n0, ulong count, char ch); public StdString replace(ulong off, ulong n0, StdString right, ulong roff, ulong count); public void reserve(ulong newcap); public void resize(ulong newsize); public void resize(ulong newsize, char ch); public ulong rfind(char ch, ulong off); public ulong rfind(StdString right, ulong off); public ulong rfind(string ptr, ulong off); public ulong rfind(string ptr, ulong off, ulong count); public void shrink_to_fit(); public ulong size(); public StdString substr(ulong off, ulong count); public void swap(StdString right); public override string ToString(); public class Marshaller : ICustomMarshaler { public Marshaller(); public void CleanUpManagedData(object ManagedObj); public void CleanUpNativeData(IntPtr pNativeData); public static ICustomMarshaler GetInstance(string pstrCookie); public int GetNativeDataSize(); public IntPtr MarshalManagedToNative(object ManagedObj); public object MarshalNativeToManaged(IntPtr pNativeData); } } }
C# Wrapper Generator for C++ DLL
The class of StdString was generated by C# Wrapper Generator for C++ DLL automatically, it was pre-generated and put in the C# wrapper library for C++ runtime for convenience, since there won’t be difference between a pre-generated one and the one generated for a custom C++ DLL later, and a Custom C++ DLL mostly won’t have a std::string exported in the DLL anyway.
If you look into the StdString class, you will find all the methods you are familiar in C++. If you have pieces of code you have from C++ to manipulate the strings, you have do the same in C# now, the only thing is it will affect the performance because you will be calling each C++ method through P/Invoke.
Example of marshaling std::string from C#
Let’s write a real C function involving accepting a std::string and returning a std::string.
#include <string> extern "C" __declspec(dllexport) std::string Encrypt(const std::string& s) { // It is an example, it does not really encrypt anything for the std::string of s. return s; }
There are different ways to declare the P/Invoke signature in C#.
You can either marshal std::string to IntPtr or StdString depending on your preference.
Marshal std::string to IntPtr
// Let's just marshal everything to pointer using IntPtr, to C#, they are really just // memory pointer no matter whey they represent in native C++. [DllImport("SampleDll.DLL")] public static IntPtr Encrypt(IntPtr text); // Calling the method string text = "My Text"; // A returned IntPtr can be used to construct a StdString and then a // C# string. string encryptedText = new StdString(Encrypt(new StdString(text));
Marsahl std::string to StdString
// Let's marshal std::string to StdString which can be converted to and converted// from a C# string implicitly [DllImport("SampleDll.DLL")] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StdStringVector.Marshaller))] public static StdString Encrypt([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StdStringVector.Marshaller))] StdString text); // Calling the method string text = "My Text"; // A C# string can be converted to StdString implicitly. // A StdString can be converted to C# string implicitly. string encryptedText = Encrypt(text);
You have seen how a std::string can be marshaled from C# to C++ by using StdString from the C# Wrapper Library for C++ DLL. Without such a library, passing or accessing a std::string from C# is just so difficult, P/Invoke do not necessarily mean difficult, with certain tools such as the C# Wrapper Library for C++ DLL.