C++/CLIでString^(UTF-16)とchar*(UTF-8)の相互変換を調べたのでメモ。
久し振りに仕事でちょっとしたフレームワーク作ってます。
昔を思い出すこの感じ。嫌いじゃないです。
※Shift-JISは一切考慮してません
#include "msclr/marshal.h" #include "msclr/marshal_windows.h" #include "msclr/marshal_cppstd.h" #include "msclr/marshal_atl.h" #include <cstdint> #include <iostream> #include <string> // string, wstring #include <codecvt> // wstring_convert, codecvt_utf8_utf16 #include <memory> using namespace System; namespace convutil { inline std::wstring utf8_to_utf16(const std::string& from) { std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> conv; return conv.from_bytes(from); } std::string utf16_to_utf8(const std::wstring& from) { std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> conv; return conv.to_bytes(from); } inline String^ ToCliString(const std::string& from) { return msclr::interop::marshal_as<String^>(utf8_to_utf16(from)); } inline String^ ToCliString(const char* from) { return msclr::interop::marshal_as<String^>(utf8_to_utf16(from)); } inline std::string ToStlString(String^ from) { return msclr::interop::marshal_as<std::string>(from); } template<size_t toSize> inline const char* ToCharArray(String^ from, char(&to)[toSize]) { auto context = gcnew msclr::interop::marshal_context(); try { // 一度 std::wstring に変換してから std::string(utf-8)に変換 // ムダがあるような気もする auto temp = context->marshal_as<std::wstring>(from); auto temp2 = utf16_to_utf8(temp); strncpy_s(to, toSize, temp2.c_str(), _TRUNCATE); return to; } finally{ // 確実に削除。ぶっちゃけ例外でない気がするのでムダtryかも。 delete context; } } } // namespace convutil // Native struct Employee { int32_t id; char name[64]; char address[256]; }; // Managed ref struct ST_Employee { Int32 id; String^ name; String^ address; }; namespace msclr { namespace interop { // Native → Managed 変換 template<> inline ST_Employee^ marshal_as<ST_Employee^, Employee>(const Employee& from) { auto to = gcnew ST_Employee(); to->id = from.id; to->name = convutil::ToCliString(from.name); to->address = convutil::ToCliString(from.address); return to; } // Managed → Native 変換 template<> ref class context_node<Employee*, ST_Employee^> : public context_node_base { public: context_node(Employee*& to_obj, ST_Employee^ from_obj) { if (toPtr_ != nullptr) { delete toPtr_; } // toPtr_ に値をコピーする toPtr_ = new Employee(); toPtr_->id = from_obj->id; convutil::ToCharArray(from_obj->name, toPtr_->name); convutil::ToCharArray(from_obj->address, toPtr_->address); to_obj = toPtr_; } ~context_node() { this->!context_node(); } protected: !context_node() { // メモリはちゃんと開放しましょう if (toPtr_ != nullptr) { delete toPtr_; toPtr_ = nullptr; } } private: Employee * toPtr_; marshal_context context_; }; } // namespace interop } // namespace msclr using namespace msclr::interop; int main(array<System::String ^> ^args) { // native to managed { Employee native_emp; native_emp.id = 1; strcpy_s(native_emp.name, u8"私です!!"); strcpy_s(native_emp.address, "nihon no dokokadayo"); auto managed_emp = marshal_as<ST_Employee^>(native_emp); Console::WriteLine("managed id = {0}", managed_emp->id); Console::WriteLine("managed name = {0}", managed_emp->name); Console::WriteLine("managed address = {0}", managed_emp->address); } // managed to native { auto managed_emp = gcnew ST_Employee(); managed_emp->id = 100; managed_emp->name = "私はだれ??"; managed_emp->address = "iega naidesu..."; marshal_context context; auto native_emp = context.marshal_as<Employee*>(managed_emp); using std::cout; using std::endl; cout << "native id = " << native_emp->id << endl; cout << "native name = " << native_emp->name << endl; // UTF-8だから文字化けするよ! cout << "native address = " << native_emp->address << endl; } return 0; }