// ------------------------------------------------------------------------------------------- // // CppSharp C++/CLI helpers // // String marshaling code adapted from: // http://blog.nuclex-games.com/mono-dotnet/cxx-cli-string-marshaling // // Licensed under the MIT license // ------------------------------------------------------------------------------------------- // #pragma once #if defined(__cplusplus_cli) #include #include #include #include public interface class ICppInstance { property System::IntPtr __Instance { System::IntPtr get(); void set(System::IntPtr); } }; namespace msclr { namespace interop { namespace details { inline _Check_return_ size_t GetUTF8StringSize(System::String^ _str) { cli::pin_ptr _pinned_ptr = PtrToStringChars(_str); auto _size = static_cast( ::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, _pinned_ptr, _str->Length, NULL, 0, NULL, NULL)); if (_size == 0 && _str->Length != 0) { throw gcnew System::ArgumentException(_EXCEPTION_WC2MB); } // adding 1 for terminating nul _size += 1; return _size; } inline _Check_return_ size_t GetUnicodeStringSizeUTF8Source(_In_reads_z_(_count + 1) const char* _str, size_t _count) { if (_count > INT_MAX) { throw gcnew System::ArgumentOutOfRangeException(_EXCEPTION_GREATER_THAN_INT_MAX); } auto _size = static_cast(::MultiByteToWideChar(CP_UTF8, 0, _str, static_cast(_count), NULL, 0)); if (_size == 0 && _count != 0) { throw gcnew System::ArgumentException(_EXCEPTION_MB2WC); } //adding 1 for terminating nul _size += 1; return _size; } inline void WriteUTF8String(_Out_writes_all_(_size) _Post_z_ char* _buf, size_t _size, System::String^ _str) { cli::pin_ptr _pinned_ptr = PtrToStringChars(_str); //checking for overflow if (_size > INT_MAX) { // this should never happen if _size was returned by GetAnsiStringSize() throw gcnew System::ArgumentOutOfRangeException(_EXCEPTION_GREATER_THAN_INT_MAX); } const auto _written = static_cast(::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, _pinned_ptr, _str->Length, _buf, static_cast(_size), NULL, NULL)); if (_written >= _size || (_written == 0 && _size != 1)) // allowing empty string { throw gcnew System::ArgumentException(_EXCEPTION_WC2MB); } _buf[_written] = '\0'; } inline void WriteUnicodeStringUTF8Source(_Out_writes_all_(_size) _Post_z_ wchar_t* _dest, size_t _size, _In_reads_bytes_(_count)const char* _src, size_t _count) { //checking for overflow if (_size > INT_MAX || _count > INT_MAX) { throw gcnew System::ArgumentOutOfRangeException(_EXCEPTION_GREATER_THAN_INT_MAX); } size_t _written = static_cast(::MultiByteToWideChar(CP_UTF8, 0, _src, static_cast(_count), _dest, static_cast(_size))); if (_written >= _size || (_written == 0 && _size != 1)) // allowing empty string { throw gcnew System::ArgumentException(_EXCEPTION_MB2WC); } _dest[_written] = L'\0'; } inline System::String^ InternalUTF8ToStringHelper(_In_reads_z_(_count + 1)const char* _src, size_t _count) { const size_t _size = details::GetUnicodeStringSizeUTF8Source(_src, _count); if (_size > INT_MAX || _size <= 0) { throw gcnew System::ArgumentOutOfRangeException(_EXCEPTION_GREATER_THAN_INT_MAX); } details::char_buffer _wchar_buf(_size); if (_wchar_buf.get() == NULL) { throw gcnew System::InsufficientMemoryException(); } details::WriteUnicodeStringUTF8Source(_wchar_buf.get(), _size, _src, _count); return gcnew System::String(_wchar_buf.get(), 0, static_cast(_size) - 1); } } template inline _To_Type marshal_as_utf8(const _From_Type& _from_object); template <> inline std::string marshal_as_utf8(System::String^ const& _from_obj) { if (_from_obj == nullptr) { throw gcnew System::ArgumentNullException(_EXCEPTION_NULLPTR); } std::string _to_obj; size_t _size = details::GetUTF8StringSize(_from_obj); if (_size > 1) { // -1 because resize will automatically +1 for the NULL _to_obj.resize(_size - 1); char* _dest_buf = &(_to_obj[0]); details::WriteUTF8String(_dest_buf, _size, _from_obj); } return _to_obj; } template <> inline System::String^ marshal_as_utf8(char const* const& _from_obj) { return details::InternalUTF8ToStringHelper(_from_obj, strlen(_from_obj)); } template <> inline System::String^ marshal_as_utf8(const std::string& _from_obj) { return details::InternalUTF8ToStringHelper(_from_obj.c_str(), _from_obj.length()); } template <> inline System::String^ marshal_as_utf8(const std::string_view& _from_obj) { return details::InternalUTF8ToStringHelper(_from_obj.data(), _from_obj.length()); } template <> inline System::String^ marshal_as(const std::string_view& _from_obj) { return details::InternalAnsiToStringHelper(_from_obj.data(), _from_obj.length()); } template <> inline System::String^ marshal_as(const std::wstring_view& _from_obj) { return details::InternalUnicodeToStringHelper(_from_obj.data(), _from_obj.length()); } } } // CLI extensions namespace namespace clix { /// Encoding types for strings enum Encoding { /// ANSI encoding /// /// This is the default encoding you've most likely been using all around in C++. ANSI /// means 8 Bit encoding with character codes depending on the system's selected code page. /// E_ANSI, /// UTF-8 encoding /// /// This is the encoding commonly used for multilingual C++ strings. All ASCII characters /// (0-127) will be represented as single bytes. Be aware that UTF-8 uses more than one /// byte for extended characters, so std::string::length() might not reflect the actual /// length of the string in characters if it contains any non-ASCII characters. /// E_UTF8, /// UTF-16 encoding /// /// This is the suggested to be used for marshaling and the native encoding of .NET /// strings. It is similar to UTF-8 but uses a minimum of two bytes per character, making /// the number of bytes required for a given string better predictable. Be aware, however, /// that UTF-16 can still use more than two bytes for a character, so std::wstring::length() /// might not reflect the actual length of the string. /// E_UTF16, E_UNICODE = E_UTF16 }; // Ignore this if you're just scanning the headers for informations! /* All this template stuff might seem like overkill, but it is well thought out and enables you to use a readable and convenient call while still keeping the highest possible code efficiency due to compile-time evaluation of the required conversion path. */ namespace detail { // Get C++ string type for specified encoding template struct StringTypeSelector; template<> struct StringTypeSelector { typedef std::string Type; }; template<> struct StringTypeSelector { typedef std::string Type; }; template<> struct StringTypeSelector { typedef std::wstring Type; }; // Compile-time selection depending on whether a string is managed template struct IfManaged { struct Select { template struct Either { typedef FalseType Type; }; }; enum { Result = false }; }; template<> struct IfManaged { struct Select { template struct Either { typedef TrueType Type; }; }; enum { Result = true }; }; // Direction of the marshaling process enum MarshalingDirection { CxxFromNet, NetFromCxx }; // The actual marshaling code template struct StringMarshaler; // Marshals to .NET from C++ strings template<> struct StringMarshaler { template static System::String ^marshal(const SourceType& string) { if constexpr (encoding == E_UTF8) return msclr::interop::marshal_as_utf8(string); else return msclr::interop::marshal_as(string); } }; // Marshals to C++ strings from .NET template<> struct StringMarshaler { template static typename detail::StringTypeSelector::Type marshal( System::String^ string ) { using ResultType = typename detail::StringTypeSelector::Type; return msclr::interop::marshal_as(string); } template<> static std::string marshal(System::String^ string) { return msclr::interop::marshal_as_utf8(string); } }; } // namespace detail // ----------------------------------------------------------------------------------------- // // clix::marshalString() // ----------------------------------------------------------------------------------------- // /// Marshals strings between .NET managed and C++ native /// /// This all-in-one function marshals native C++ strings to .NET strings and vice versa. /// You have to specify an encoding to use for the conversion, which always applies to the /// native C++ string as .NET always uses UTF-16 for its own strings. /// /// String to be marshalled to the other side /// The marshaled representation of the string template::Select::Either< typename detail::StringTypeSelector::Type, System::String^>::Type > ResultType marshalString(SourceType string) { constexpr detail::MarshalingDirection direction = detail::IfManaged::Result ? detail::CxxFromNet : detail::NetFromCxx; using StringMarshaler = detail::StringMarshaler; // Pass on the call to our nifty template routines return StringMarshaler::template marshal(string); } } // namespace clix // std::ostream marshaling using a System::IO::TextWriter namespace msclr { namespace interop { namespace details { class text_writer_streambuf : public std::streambuf { public: text_writer_streambuf(const gcroot & tw) : std::streambuf() { m_tw = tw; } ~text_writer_streambuf() { m_tw->Flush(); } int_type overflow(int_type ch) { if (traits_type::not_eof(ch)) { auto c = traits_type::to_char_type(ch); xsputn(&c, 1); } return traits_type::not_eof(ch); } std::streamsize xsputn(const char *_Ptr, std::streamsize _Count) { auto s = gcnew System::String(_Ptr, 0, (int)_Count, System::Text::Encoding::UTF8); m_tw->Write(s); return _Count; } private: gcroot m_tw; }; class text_writer_ostream : public std::ostream { public: text_writer_ostream(const gcroot & s) : std::ios(), std::ostream(0), m_sbuf(s) { init(&m_sbuf); } private: text_writer_streambuf m_sbuf; }; } template<> ref class context_node : public context_node_base { private: std::ostream* toPtr; public: context_node(std::ostream*& toObject, System::IO::TextWriter^ fromObject) { // (Step 4) Initialize toPtr to the appropriate empty value. toPtr = new details::text_writer_ostream(fromObject); // (Step 5) Insert conversion logic here. // (Step 6) Set toObject to the converted parameter. toObject = toPtr; } ~context_node() { this->!context_node(); } protected: !context_node() { // (Step 7) Clean up native resources. delete toPtr; } }; } } // namespace msclr #endif