From ea9b23e0e1c6ed32715c5548c1aa372ae75ef8e2 Mon Sep 17 00:00:00 2001 From: Tarmo Pikaro Date: Sun, 13 Jan 2013 11:00:03 +0200 Subject: [PATCH] _wrapper suffix, add autogenerated files manually to project. --- examples/Hello/Interop.h | 203 ++++++++++++++++++++++++++++++++++++ examples/Hello/premake4.lua | 8 +- 2 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 examples/Hello/Interop.h diff --git a/examples/Hello/Interop.h b/examples/Hello/Interop.h new file mode 100644 index 00000000..4572a509 --- /dev/null +++ b/examples/Hello/Interop.h @@ -0,0 +1,203 @@ +// ------------------------------------------------------------------------------------------- // +// clix.hpp (from http://blog.nuclex-games.com/mono-dotnet/cxx-cli-string-marshaling) +// +// Marshals strings between .NET and C++ using C++/CLI (Visual C++ 2005 and later only). +// Faster and cleaner than the System::Interop method because it uses garbage collected memory. +// Use at your own leisure. No warranties whatsoever provided. +// +// Original code by Markus Ewald +// Updated version including several improvements suggested by Neil Hunt +// +// Licensed under the IBM CPL (free of charge, closed source commercial use is okay) +// ------------------------------------------------------------------------------------------- // +#pragma once + +#include +#include + +// 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) { + // Constructs a std::[w]string in case someone gave us a char * to choke on + return marshalCxxString(string); + } + + template + static System::String ^marshalCxxString( + const typename StringTypeSelector::Type &cxxString + ) { + typedef typename StringTypeSelector::Type SourceStringType; + size_t byteCount = cxxString.length() * sizeof(SourceStringType::value_type); + + if(byteCount == 0) return System::String::Empty; + + // Copy the C++ string contents into a managed array of bytes + array ^bytes = gcnew array(byteCount); + { pin_ptr pinnedBytes = &bytes[0]; + memcpy(pinnedBytes, cxxString.c_str(), byteCount); + } + + // Now let one of .NET's encoding classes do the rest + return decode(bytes); + } + + private: + // Converts a byte array based on the selected encoding + template static System::String ^decode(array ^bytes); + template<> static System::String ^decode(array ^bytes) { + return System::Text::Encoding::Default->GetString(bytes); + } + template<> static System::String ^decode(array ^bytes) { + return System::Text::Encoding::UTF8->GetString(bytes); + } + template<> static System::String ^decode(array ^bytes) { + return System::Text::Encoding::Unicode->GetString(bytes); + } + }; + + // Marshals to C++ strings from .NET + template<> struct StringMarshaler { + + template + static typename detail::StringTypeSelector::Type marshal( + System::String ^string + ) { + typedef typename StringTypeSelector::Type StringType; + + // First, we use .NET's encoding classes to convert the string into a byte array + array ^bytes = encode(string); + + if(bytes->Length == 0) return StringType(); + + // Then we construct our native string from that byte array + pin_ptr pinnedBytes(&bytes[0]); + return StringType( + reinterpret_cast(static_cast(pinnedBytes)), + bytes->Length / sizeof(StringType::value_type) + ); + } + + template<> static std::wstring marshal( + System::String ^string + ) { + // We can directly accesss the characters in the managed string + pin_ptr pinnedChars(::PtrToStringChars(string)); + return std::wstring(pinnedChars, string->Length); + } + + private: + // Converts a string based on the selected encoding + template static array ^encode(System::String ^string); + template<> static array ^encode(System::String ^string) { + return System::Text::Encoding::Default->GetBytes(string); + } + template<> static array ^encode(System::String ^string) { + return System::Text::Encoding::UTF8->GetBytes(string); + } + template<> static array ^encode(System::String ^string) { + return System::Text::Encoding::Unicode->GetBytes(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 + typename detail::IfManaged::Select::Either< + typename detail::StringTypeSelector::Type, + System::String ^ + >::Type marshalString(SourceType string) { + + // Pass on the call to our nifty template routines + return detail::StringMarshaler< + detail::IfManaged::Result ? detail::CxxFromNet : detail::NetFromCxx + >::marshal(string); + + } + +} // namespace clix diff --git a/examples/Hello/premake4.lua b/examples/Hello/premake4.lua index c90ec446..7a1bafe5 100644 --- a/examples/Hello/premake4.lua +++ b/examples/Hello/premake4.lua @@ -27,6 +27,10 @@ project "Hello" buildoptions { common_msvc_copts, "/clr" } files { "**.h", "**.cpp", "./*.lua" } + + -- Autogenerated files, so not available on first build - specify them manually + excludes { "CppCsBind/*.*" } + files { "CppCsBind/hello_wrapper.h", "CppCsBind/hello_wrapper.cpp" } configuration "hello.h" buildrule { @@ -34,12 +38,12 @@ project "Hello" commands = { '..\\..\\bin\\generator.exe -ns=CppCsBind -outdir=CppCsBind -I. hello.h', }, - outputs = { "CppCsBind\\hello.cpp" } + outputs = { "CppCsBind\\hello_wrapper.cpp" } } project "SayHello" - kind "ConsoleApp" + kind "ConsoleApp" language "C#" location "." platforms { "x32" }