本記事について
この記事は CoreBluetoothForUnity Advent Calendar 2023 の12日目の記事です。
CoreBluetoothForUnity でのアンマネージドなオブジェクトの生成と破棄について説明します。
前提
CoreBluetoothForUnity はネイティブプラグインを Swift で書いているため、Swift での事例紹介です。
環境
- CoreBluetoothForUnity 0.4.4
生成
ネイティブ側の生成コード (Swift)
CBCentralManager を例に説明します。
CBCentralManager は 以下のネイティブなクラスと紐づいています。
CB4UCentralManager の中身は本記事においては重要ではないため特に見る必要はありません。
そして、このインスタンスを生成して以下のように Unity 側にインスタンスを渡します。
@_cdecl("cb4u_central_manager_new")
public func cb4u_central_manager_new(_ optionsPtr: UnsafeRawPointer?) -> UnsafeMutableRawPointer {
... // optionsを作る処理
return Unmanaged.passRetained(CB4UCentralManager(options)).toOpaque()
}
passRetained により、参照カウントを増やしつつ Unmanaged オブジェクトを作成します。
そして、Unmanaged が持つ toOpaque により、オブジェクトをポインタに変換して返しています。
keijiro さんの UnitySwiftPluginTest リポジトリのコードでは、このポインタをOpaquePointerに変換しています。
が、これに関してはやらなくても動いたのと、どういうメリットがあるのかわからなかったためやっていません。
Unity 側の生成コード (C#)
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
internal static extern SafeNativeCentralManagerHandle cb4u_central_manager_new(IntPtr options);
SafeNativeCentralManagerHandle という型として返るようにしています。
SafeHandle について
keijiro さんの MEMO で知りました。
生成したアンマネージドオブジェクトは使い終わったら破棄しないとメモリリークします。
で、エラーや、並列処理等何らかの要因で意図した破棄処理ができなかった場合でも GC によって破棄されるようにするという Dispose パターンがあります。
以下の記事が詳しくてとても頭に入りやすかったです。
そして、SafeHandle クラスを使うとデストラクタ等用意しなくて良いため楽。
というものな認識です。
使い方は SafeHandleZeroOrMinusOneIsInvalid のドキュメントに詳しく記述されています。
IntPtr から暗黙的に変換が行われるため、返り値や引数にできます。
SafeHandle の使いづらさについて
実装していくうちに SafeHandle は使いづらいと思う場面がありました。
- SafeHandle から IntPtr に変換するときの処理内容がわからない (私が見つけられてないだけかもしれません)
- ネイティブプラグインからのコールバックの引数を SafeHandle にすることはできない。
- クラスであること (IntPtr は値型)
- 配列として渡せない (IntPtrなら渡せる)
これらより、いくつかの処理においては DangerousGetHandle で中身の IntPtr を取り出して使っています。
参考にした Xamarin や ARKit Plugin のコードでも SafeHandle は使われていませんでした。
internal なオブジェクトであれば IntPtr を保持して IDisposable を実装した struct を用意するほうが良いのかなと今は思っています。
破棄
Unmanaged<AnyObject>.fromOpaque(handle).release()
Swift においてクラスは全て AnyObject として扱うことができます。
そして、Unmanaged の型指定するときに AnyObject を使うことができます。
従って全クラスこれで破棄(参照カウントを減らす)できそうでした。
各 SafeHandle の ReleaseHandle
で呼び出しています。
参考