【Unity】アンマネージドオブジェクトををあえて持たずにIDでやり取りしてみた所感

## 本記事について

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

Unity からネイティブプラグインのインスタンスのメソッドを呼び出す際、通常は以下の流れになるかと思います。

1. アンマネージドなオブジェクトを作成し、それを Unity 側に持ってくる
2. P/Invoke のメソッドを呼び出す際にインスタンスのポインタを渡す。
3. ネイティブ側でポインタからインスタンスを取り出し、メソッドを呼び出す。

この方法だと、Unity 側でインスタンスを持っておく必要があります。

以下の方法を使うとネイティブ側のインスタンスを Unity 側で持たずに済みます。

1. ネイティブ側でインスタンスを持っておいて、その ID を Unity 側に渡す。
2. P/Invoke のメソッドを呼び出す際に ID を渡す。
3. ネイティブ側で ID からインスタンスを探し、メソッドを呼び出す。

CoreBluetoothForUnity の一部でこれをやってみたので、その所感を書きます。

## 環境

- CoreBluetoothForUnity 0.4.4

## 結論

期待通りに動きました。
しかし、コード量・考えること増えるため、複雑なクラスで使用はしないほうがいいかなと思いました。

## 実際にやったこと

### クラス関係

クラスの役割は本記事では重要ではないため名前と関係だけ示します。

```
Peripheral -保持-> Service -保持-> Characteristic
```

Peripheral, Service, Characteristic はそれぞれ ID を持っています。

Peripheral はインスタンスのポインタを Unity 側で持ち、Service, Characteristic はインスタンスのポインタを持ちません。

Characteristic にアクセスするには以下の手順となります。

1. Peripheral から ID で Service を取得
2. Service から ID で Characteristic を取得

### Characteristic のメソッドを呼び出す

呼び出しているネイティブ側のメソッドは以下です。

[cb4u_peripheral_characteristic_properties](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CoreBluetoothForUnity.swift#L298-L307)

```CoreBluetoothForUnity.swift
@_cdecl("cb4u_peripheral_characteristic_properties")
public func cb4u_peripheral_characteristic_properties(
    _ peripheralPtr: UnsafeRawPointer,
    _ serviceUUID: UnsafePointer<CChar>,
    _ characteristicUUID: UnsafePointer<CChar>
) -> Int32 {
    let instance = Unmanaged<CB4UPeripheral>.fromOpaque(peripheralPtr).takeUnretainedValue()
    
    return instance.characteristicProperties(CBUUID(string: String(cString: serviceUUID)), CBUUID(string: String(cString: characteristicUUID)))
}
```

[characteristicProperties](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CB4UPeripheral+DelegateCharacteristic.swift#L4)


```CB4UPeripheral+DelegateCharacteristic.swift
extension CB4UPeripheral {
    public func characteristicProperties(_ serviceUUID: CBUUID, _ characteristicUUID: CBUUID) -> Int32 {
        guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
            return serviceNotFound
        }
        guard let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUUID }) else {
            return characteristicNotFound
        }
        
        return Int32(characteristic.properties.rawValue)
    }
}
```

Service がない -> `serviceNotFound` (-2) 、Characteristic がない ->`characteristicNotFound` (-3)
Characteristicがある -> 対象の値を返す

これを Unity 側からは以下のように呼び出しています。

[NativeMethods](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Packages/com.teach310.core-bluetooth-for-unity/Runtime/NativeMethods.cs)

```NativeMethods.cs
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
internal static extern int cb4u_peripheral_characteristic_properties(
    SafeNativePeripheralHandle handle,
    [MarshalAs(UnmanagedType.LPStr), In] string serviceUUID,
    [MarshalAs(UnmanagedType.LPStr), In] string characteristicUUID
);
```

[NativeCharacteristicProxy](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.4/Packages/com.teach310.core-bluetooth-for-unity/Runtime/NativeCharacteristicProxy.cs)

```NativeCharacteristicProxy.cs
internal CBCharacteristicProperties Properties
{
    get
    {
        int result = NativeMethods.cb4u_peripheral_characteristic_properties(_handle, _serviceUUID, _characteristicUUID);

        ExceptionUtils.ThrowIfServiceNotFound(result, _serviceUUID);
        ExceptionUtils.ThrowIfCharacteristicNotFound(result, _characteristicUUID);

        return (CBCharacteristicProperties)result;
    }
}
```

### 考察

良かった点

- Unity 側でインスタンスを持たなくてもいい (親となるインスタンスは除く)
- 単純にメソッド呼び出すだけなら問題ない
- インスタンスの生成と破棄を考えなくていい

悪かった点

- 引数が増える
- 「見つからなかったらXXする」の処理が増える
- インスタンスに対して汎用的に使えるメソッドが使えない (ToString とか)
- エラーの返し方どうする？となる。(今回は返り値が int だったからそのまま返せた)

合計すると、コード量や考えることはむしろ増えていそうな気がします。

## まとめ

Unity 側でインスタンスを持たなくてもいいというメリットはありますが、コード量が増えるため、複雑なクラスで使用することはおすすめしません。

## おわりに

あくまでも今回私が使ってみた感想ですので、もっとメリットはあるかもしれません。
特にダメというわけでもないためやってみたこと自体はよかったかなと思っています。

もしこういう場面では使えるよ！等ありましたらぜひ教えていただけると嬉しいです！