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
How to Load Dll (Module Could Not Be Found Hresult: 0X8007007E)
How to Create a Custom Authorizeattribute in ASP.NET Core
Performance Differences Between Debug and Release Builds
How to Find Out Which Process Is Locking a File Using .Net
Difference Between Task and Thread
How to Convert Utf-8 Byte[] to String
How to Get Parent Process in .Net in Managed Way
How to Convert Json to Xml or Xml to Json
Pre & Post Increment Operator Behavior in C, C++, Java, & C#
How to Convert a Byte Array to a Hexadecimal String, and Vice Versa
What Are the Differences Between a Multidimensional Array and an Array of Arrays in C#
How to Generate a Random Integer in C#
How to Escape a Double Quote in a Verbatim String Literal
Is There a Difference Between "Throw" and "Throw Ex"
Observablecollection Not Noticing When Item in It Changes (Even With Inotifypropertychanged)