【Unity】アンマネージドなオブジェクトの管理方法

## 本記事について

この記事は [CoreBluetoothForUnity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity) の12日目の記事です。

CoreBluetoothForUnity でのアンマネージドなオブジェクトの生成と破棄について説明します。

## 前提

CoreBluetoothForUnity はネイティブプラグインを Swift で書いているため、Swift での事例紹介です。

## 環境

- CoreBluetoothForUnity 0.4.4

## 生成

#### ネイティブ側の生成コード (Swift)

CBCentralManager を例に説明します。

CBCentralManager は 以下のネイティブなクラスと紐づいています。

[CB4UCentralManager.swift](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CB4UCentralManager.swift)

CB4UCentralManager の中身は本記事においては重要ではないため特に見る必要はありません。

そして、このインスタンスを生成して以下のように Unity 側にインスタンスを渡します。

```CoreBluetoothForUnity.swift
@_cdecl("cb4u_central_manager_new")
public func cb4u_central_manager_new(_ optionsPtr: UnsafeRawPointer?) -> UnsafeMutableRawPointer {
    ... // optionsを作る処理
    
    return Unmanaged.passRetained(CB4UCentralManager(options)).toOpaque()
}
```

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CoreBluetoothForUnity.swift#L25-L34

[passRetained](https://developer.apple.com/documentation/swift/unmanaged/passretained(_:)) により、参照カウントを増やしつつ [Unmanaged オブジェクト](https://developer.apple.com/documentation/swift/unmanaged)を作成します。

そして、Unmanaged が持つ [toOpaque](https://developer.apple.com/documentation/swift/unmanaged/toopaque()) により、オブジェクトをポインタに変換して返しています。

keijiro さんの UnitySwiftPluginTest リポジトリの[コード](https://github.com/keijiro/UnitySwiftPluginTest/blob/main/Plugin/Sources/SwiftPlugin/SwiftPlugin.swift#L14)では、このポインタを[OpaquePointer](https://developer.apple.com/documentation/swift/opaquepointer)に変換しています。

が、これに関してはやらなくても動いたのと、どういうメリットがあるのかわからなかったためやっていません。

#### Unity 側の生成コード (C#)

```NativeMethods.cs
        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        internal static extern SafeNativeCentralManagerHandle cb4u_central_manager_new(IntPtr options);
```

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Packages/com.teach310.core-bluetooth-for-unity/Runtime/NativeMethods.cs#L25C75-L26

SafeNativeCentralManagerHandle という型として返るようにしています。

## SafeHandle について

[keijiro さんの MEMO](https://github.com/keijiro/Memo/blob/main/Pages/CSharpInterop.md#safehandle) で知りました。

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Packages/com.teach310.core-bluetooth-for-unity/Runtime/SafeNativeCentralManagerHandle.cs

生成したアンマネージドオブジェクトは使い終わったら破棄しないとメモリリークします。
で、エラーや、並列処理等何らかの要因で意図した破棄処理ができなかった場合でも GC によって破棄されるようにするという Dispose パターンがあります。

以下の記事が詳しくてとても頭に入りやすかったです。

https://qiita.com/Zuishin/items/9efc9c8cbb98300bbc64

そして、SafeHandle クラスを使うとデストラクタ等用意しなくて良いため楽。
というものな認識です。

使い方は SafeHandleZeroOrMinusOneIsInvalid のドキュメントに詳しく記述されています。

https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.safehandle?view=net-7.0

IntPtr から暗黙的に変換が行われるため、返り値や引数にできます。

### SafeHandle の使いづらさについて

実装していくうちに SafeHandle は使いづらいと思う場面がありました。

- SafeHandle から IntPtr に変換するときの処理内容がわからない (私が見つけられてないだけかもしれません)
- ネイティブプラグインからのコールバックの引数を SafeHandle にすることはできない。
- クラスであること (IntPtr は値型)
- 配列として渡せない (IntPtrなら渡せる)

これらより、いくつかの処理においては [DangerousGetHandle](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.safehandle.dangerousgethandle?view=net-7.0#system-runtime-interopservices-safehandle-dangerousgethandle) で中身の IntPtr を取り出して使っています。

参考にした Xamarin や ARKit Plugin のコードでも SafeHandle は使われていませんでした。

internal なオブジェクトであれば IntPtr を保持して IDisposable を実装した struct を用意するほうが良いのかなと今は思っています。

## 破棄

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/FoundationForUnity/FoundationForUnity.swift#L3-L6

```FoundationForUnity.swift
Unmanaged<AnyObject>.fromOpaque(handle).release()
```

Swift においてクラスは全て AnyObject として扱うことができます。

そして、[Unmanaged](https://developer.apple.com/documentation/swift/unmanaged) の型指定するときに AnyObject を使うことができます。
従って全クラスこれで破棄(参照カウントを減らす)できそうでした。

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Packages/com.teach310.core-bluetooth-for-unity/Runtime/SafeNativeCentralManagerHandle.cs#L28

各 SafeHandle の `ReleaseHandle` で呼び出しています。

## 参考

https://qiita.com/mao_/items/07466e169d08cbeff221