独自のmarshal_as/marshal_contextの定義

marshal_as, marshal_contextについては、以前に文字列の変換における利用方法を書きました。今回は独自の変換を行う方法について書きます。

変換する型

ここでは例として、以下のネイティブの構造体とマネージクラスの相互変換を行います。

typedef struct ActorNative
{
    char name[64];
    int hp;
    int mp;
}
ActorNative;
ref class ActorManaged
{
public:
    String^ name;
    Int32 hp;
    Int32 mp;
};

ネイティブからマネージへ変換

変換先がマネージ型の場合はGCが効きますので、marshal_asで変換することができます。以下のように、marshal_asの際の処理を記述します。

namespace msclr {
namespace interop
{
    template<>
    inline ActorManaged^ marshal_as<ActorManaged^, ActorNative> (const ActorNative& from)
    {
        ActorManaged^ obj = gcnew ActorManaged();
        obj->name = gcnew String(from.name);
        obj->hp = from.hp;
        obj->mp = from.mp;
        return obj;
    }
}
}

すると、以下のようにmarshal_asを使っての変換が可能となります。

ActorNative native = {"アレックス", 120, 30};

ActorManaged^ managed = marshal_as<ActorManaged^>(native);

マネージからネイティブへ変換

ネイティブ型の値そのものに変換する場合は、上の場合と同様にmarshal_asを定義します。これはマネージのvalue classからネイティブのstructへの変換の場合等が当てはまると思います。

template<>
inline POINT marshal_as<POINT, System::Drawing::Point> (const System::Drawing::Point& from)
{
    POINT p = {from.X, from.Y};
    return p;
}

それ以外の場合、例えば今回のように、マネージ型のref classからネイティブ型のポインタに変換する場合を考えます。これはGCは効きませんので、リソース解放の面倒を見てくれるmarshal_contextを使うことになります。以下のようにして、独自のcontext_nodeクラスを作成します。

namespace msclr {
namespace interop
{
    template<>
    ref class context_node<ActorNative*, ActorManaged^> : public context_node_base
    {
    private:
        ActorNative * _ptr;

    public:
        context_node(ActorNative*& to, ActorManaged^ from)
        {
            _ptr = new ActorNative();
            _ptr->name = static_cast<char*>(Marshal::PtrToHGlobalAnsi(from->name));
            _ptr->hp = from->hp;
            _ptr->mp = from->mp;

            to = _ptr;
        }

        ~context_node()
        {
            this->!context_node();
        }

    protected:
        !context_node()
        {
            Marshal::FreeHGlobal( IntPtr(_ptr->name) );
            delete _ptr;
        }
    };
}
}

これにより、以下のような変換が可能となります。

ActorManaged^ managed = gcnew ActorManaged();
managed->name = "アレックス";
managed->hp = 120;
managed->mp = 30;

marshal_context context;
ActorNative* native = context.marshal_as<ActorNative*>(managed);

変数nativeは、marshal_contextがスコープを抜けると同時に破棄されます。context_nodeの定義でちゃんとデストラクタを実装していれば、自分でdeleteをしたりする必要はありません。

逆にしばらくポインタを保持したい場合はmarshal_contextも保持しておく必要がありますが、そういう場合にはあまりmarshal_contextは向いていないと思います。ネイティブの関数に渡すために一時的に構造体に変換したい、という場合に適しています。