目次
1. この記事の目的
既存のC++の実装をUnityでも使いたい時にこの記事を参照してください。
C++のクラスを使って実装されていれば、そのクラスをUnityで管理できます。
サンプルコードをGitHubにあげました。
2. 概要
C++のクラスを管理するために、C#でラッパークラスを実装します。
ラッパークラスはC++のインスタンスのポインタを持ち、そのインスタンスのメソッドを呼びたい時は、そのインスタンスのポインタにアクセスして処理を実行します。
今回は簡単なカウンターのクラスをC++で実装して、C#のラッパークラスを実装します。
3. C++のコード
まずカウンタークラスのヘッダファイルは次の通りです。
#ifndef NativeCounter_hpp #define NativeCounter_hpp #include <stdio.h> class NativeCounter { public: NativeCounter(); ~NativeCounter(); void Increment(); int GetCount(); private: int count; }; #endif /* NativeCounter_hpp */
次に実装の方は以下のようになります。
#include "NativeCounter.hpp" NativeCounter::NativeCounter() { count = 0; } NativeCounter::~NativeCounter() { } void NativeCounter::Increment() { count++; } int NativeCounter::GetCount() { return count; }
あとはこのクラスにアクセスするためのインタフェースを定義します。
こちらもまずはヘッダから。
インタフェースとなるメソッドは、マングリングを回避するためにextern "C"で囲みます。
また、ネイティブの関数で名前が衝突すると大変なので、ドメインやクラス名で工夫しましょう。
#ifndef NativeInterface_hpp #define NativeInterface_hpp #include <stdio.h> #include "NativeCounter.hpp" extern "C" { NativeCounter* com_shogonir_NativeCounter_Create(); void com_shogonir_NativeCounter_Destroy(NativeCounter* instance); void com_shogonir_NativeCounter_Increment(NativeCounter* instance); int com_shogonir_NativeCounter_GetCount(NativeCounter* instance); } #endif /* NativeInterface_hpp */
次に実装は次のようになります。
ネイティブでエラーが起きるとアプリがおちるなどの致命的な不具合になりますので、渡されたインスタンスが有効かのチェックは絶対にいれるようにしましょう。
また、インスタンスを破棄した場合はポインタをnullptrにすることでチェックを簡単にしましょう。
#include "NativeInterface.hpp" NativeCounter* com_shogonir_NativeCounter_Create() { return new NativeCounter(); } void com_shogonir_NativeCounter_Destroy(NativeCounter* instance) { if (instance == nullptr) { return; } delete instance; instance = nullptr; } void com_shogonir_NativeCounter_Increment(NativeCounter* instance) { if (instance == nullptr) { return; } instance->Increment(); } int com_shogonir_NativeCounter_GetCount(NativeCounter* instance) { if (instance == nullptr) { return -1; } return instance->GetCount(); }
4. C#のコード
C++のコードが準備できましたので、次にC#のコードをかいていきましょう。
C++で実装されたNativeCounterのラッパークラスManagedCounterを実装します。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; namespace Shogonir.NativeInstance { public class ManagedCounter { private IntPtr _instance; [DllImport ("Native", EntryPoint="com_shogonir_NativeCounter_Create")] private static extern IntPtr _Create(); [DllImport ("Native", EntryPoint="com_shogonir_NativeCounter_Destroy")] private static extern void _Destroy(IntPtr instance); [DllImport ("Native", EntryPoint="com_shogonir_NativeCounter_Increment")] private static extern void _Increment(IntPtr instance); [DllImport ("Native", EntryPoint="com_shogonir_NativeCounter_GetCount")] private static extern int _GetCount(IntPtr instance); public ManagedCounter() { _instance = _Create(); } private bool IsDestroyed() { return _instance == IntPtr.Zero; } ~ManagedCounter() { if (IsDestroyed()) { return; } _Destroy(_instance); _instance = IntPtr.Zero; } public void Increment() { if (IsDestroyed()) { return; } _Increment(_instance); } public int GetCount() { if (IsDestroyed()) { return -1; } return _GetCount(_instance); } } }
まずextern "C" で定義したインタフェースにアクセスするために、DllImportで関数を定義します。
DllImport("Native")とすることで、Native.bundleからリンクできる関数を探します。
EntryPoint="FuncName"とすることで、その関数名とリンクできます。
また関数の引数や返り値はリンクする関数と合わせる必要があります。
C++側でポインタはC#のIntPtrとします。
また、C#側でもエラーが起きないよう細心の注意を払います。
C#のデストラクタが呼ばれるとインスタンスのポインタをIntPtr.Zero(nullptr)にセットし、あらゆる操作が呼ばれた際にポインタが有効かをチェックします。
5. まとめ
この記事ではC++のクラスをUnityで管理する方法を紹介しました。
C++ですでに実装された資産を有効活用したい際にお役に立てればと思います。
ネイティブでのエラーは致命的になりますので、細心の注意を払いましょう。
今回はC++のsoなどをどうやってUnityのプロジェクトに取り込むかを省略してしまいましたので、また別の記事で紹介したいと思います。