Std::String in C#

std::string in C#?

std::string and c# string are not compatible with each other. As far as I know the c# string corresponds to passing char* or wchar_t* in c++ as far as interop is concerned.

One of the reasons for this is that There can be many different implementations to std::string and c# can't assume that you're using any particular one.

C# 'String' to C++ 'std::string'

As per the documentation, C# marshals string objects as simple "pointer to a null-terminated array of ANSI characters", or const char * in C++ terms.

If you change the typedef of GetOutputString to return a const char *, everything will work.

Alternative to std::string in C#

The following code will return "1305" for input "‚‡…ƒ". The trick was to figure out which code page was used when the string was mangled. It was code page 1252.

static public string DeMangleCode(string argMangledCode)
{
Encoding enc = Encoding.GetEncoding(1252);
byte[] argMangledCodeBytes = enc.GetBytes(argMangledCode);
List<byte> unencrypted = new List<byte>();
for (int temp = 0; temp < argMangledCodeBytes.Length; temp++)
{
unencrypted.Add((byte)(argMangledCodeBytes[temp] ^ (434 + temp) % 255));
}
return enc.GetString(unencrypted.ToArray());
}

passing string from C++/CLI class library to C#

For an interop scenario, you need to return a string object you'll be able to read from .NET code.

Don't return a std::string (there is no such thing in C#) or a const char * (readable from C# but you'd have to manage memory deallocation) or things like that. Return a System::String^ instead. This is the standard string type in .NET code.

This will work:

public: System::String^ getStringFromCpp()
{
return "Hello World";
}

But if you actually have a const char * or std::string object you'll have to use the marshal_as template:

#include <msclr/marshal.h>
public: System::String^ getStringFromCpp()
{
const char *str = "Hello World";
return msclr::interop::marshal_as<System::String^>(str);
}

Read Overview of Marshaling in C++ for more details.


To convert a System::String^ to std::string you can also use the marshal_as template, as explained in the above link. You just need to include a different header:

#include <msclr/marshal_cppstd.h>
System::String^ cliStr = "Hello, World!";
std::string stdStr = msclr::interop::marshal_as<std::string>(cliStr);

C++ string to C# using a struct

The C++ code is wrong.

imInfo.barcodeType = (unsigned char*)type.c_str();
imInfo.barcodeData = (unsigned char*)data.c_str();

c_str() gives you a pointer to the start of a string of characters, yes. You can pass that to a function, fine. But you can't return it! Who owns the memory? What is its lifetime? When is it deallocated? C++ doesn't have automatic memory management - you can't just return objects and pointers and expect them to magically work. You can't even rely on this bad behaviour to produce errors, as you've just seen - you get garbage; sometimes you get the right thing, sometimes the wrong thing, sometimes you introduce a horrible security flaw.

By the point ScanBarcode returns, type and data are destroyed. The pointer returned by c_str() isn't pointing to the data you think it's pointing to (regardless of the internal implementation of c_str()). You need to copy that data over to shared memory, and make sure it's freed properly. Passing memory around is not an easy thing in the unmanaged world. That's one of the big reasons why managed systems and languages like C# were designed in the first place.

Designing the interop is not trivial. Take examples from actual well-designed C APIs. One important thing is to return the length of the string, for example (it's very rare that you want to pass just char*, without also specifying the length; it's outright crazy to do that if you don't own the memory being pointed to). First, decide who is supposed to allocate and deallocate the memory (this should usually be the same).

You can already see this is a bit of a problem. ImageInfo is allocated by the caller. But the char*fields are allocated by the callee! There is no way to sensibly limit the lifetime of the object to either the C++ or C# side (again, it's not the C# that's the problem - a C++ caller would have the same issue). The usual way this was handled in the olden days was with pre-allocated buffers - that is, imInfo.barcodeType would not be a pointer to a string, but an array. The C# structure would then look like this:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct ImageInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=255 /* this must be synchronized with the C++ code! */)]
public string barcodeType;
}

The C++ side would do something like this (do not use this in production code, it's almost certainly wrong in some way - my C++ is very rusty; consult with a C++ programmer):

struct ImageInfo
{
char barcodeType[255];
}

if (type.barcodeType.length() < 254)
{
strcpy(imInfo.barcodeType, type.c_str());
}

Again, this is extremely naive and probably wrong. It's just to show one way you can approach passing data between the caller and callee in general. Take good care.

How to SWIG std::string& to C# ref string

Just in case somebody is looking for this in the future, I created std_string.i like this for C#. Seems to work for me. Note that I changed the ref to out since it was more appropriate in my case but ref should work as well.

I called %include "std_string.i" from the .i file

/* -----------------------------------------------------------------------------
* std_string_ref.i
*
* Typemaps for std::string& and const std::string&
* These are mapped to a C# String and are passed around by reference
*
* ----------------------------------------------------------------------------- */

%{
#include <string>
%}

namespace std {

%naturalvar string;

class string;

// string &

%typemap(ctype) std::string & "char**"
%typemap(imtype) std::string & "/*imtype*/ out string"
%typemap(cstype) std::string & "/*cstype*/ out string"

//C++
%typemap(in, canthrow=1) std::string &
%{ //typemap in
std::string temp;
$1 = &temp;
%}

//C++
%typemap(argout) std::string &
%{
//Typemap argout in c++ file.
//This will convert c++ string to c# string
*$input = SWIG_csharp_string_callback($1->c_str());
%}

%typemap(argout) const std::string &
%{
//argout typemap for const std::string&
%}

%typemap(csin) std::string & "out $csinput"

%typemap(throws, canthrow=1) string &
%{ SWIG_CSharpSetPendingException(SWIG_CSharpApplicationException, $1.c_str());
return $null; %}

}

The reason I need to define argout for const std::string& is because SWIG will get confused and override const std::string& also with the typemap. So I explicitly tell it not to override in my case (You may have a different use case)

For Python I created something like this:

%typemap(argout)std::string&
{
//typemap argout std::string&
PyObject* obj = PyUnicode_FromStringAndSize((*$1).c_str(),(*$1).length());

$result=SWIG_Python_AppendOutput($result, obj);
}

%typemap(argout) const std::string &
%{
//argout typemap for const std::string&
%}


Related Topics



Leave a reply



Submit