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;
}