【Unity】Swift のネイティブプラグインとの byte 配列のやり取りの仕方

## 本記事について

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

Swift のネイティブプラグイン開発における byte 配列の渡し方、受け取る時のやり方について説明します。

例として CoreBluetoothForUnity の関数を用いますが、特に CoreBluetooth 関連の概念を知っている必要はありません。

## 環境

- CoreBluetoothForUnity 0.4.5

## byte 配列を引数として渡す

### C#

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

```NativeMethods.cs
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
internal static extern void cb4u_mutable_characteristic_set_value(SafeNativeMutableCharacteristicHandle handle, byte[] dataBytes, int dataLength);
```

byte 配列そのものと、配列の長さを引数として渡しています。

固定長な byte 配列は Blittable なためマーシャリングは不要です。

https://learn.microsoft.com/ja-jp/dotnet/framework/interop/blittable-and-non-blittable-types

### Swift

[リンク](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.5/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CoreBluetoothForUnity.swift#L495-L504)

```CoreBluetoothForUnity.swift
@_cdecl("cb4u_mutable_characteristic_set_value")
public func cb4u_mutable_characteristic_set_value(_ characteristicPtr: UnsafeRawPointer, _ dataBytes: UnsafePointer<UInt8>?, _ dataLength: Int32) {
    let instance = Unmanaged<CB4UMutableCharacteristic>.fromOpaque(characteristicPtr).takeUnretainedValue()
    
    if let dataBytes = dataBytes {
        instance.value = Data(bytes: dataBytes, count: Int(dataLength))
    } else {
        instance.value = nil
    }
}
```

byte 配列は `UnsafePointer<UInt8>?` として渡しています。
あとはそれをよしなに扱えばいいですが、ここでは Data に変換しています。

[Data.init(bytes:count:)](https://developer.apple.com/documentation/foundation/data/1780158-init)

## byte 配列を受け取る

以下の手順で byte 配列を受け取ります。

1. ネイティブプラグインに byte 配列の長さを問い合わせる
2. 受け取った長さで byte 配列を作成する
3. ネイティブプラグインに byte 配列(のポインタ)を渡して中身を詰めてもらう

```.cs
internal byte[] Value
{
    get
    {
        var dataLength = NativeMethods.cb4u_mutable_characteristic_value_length(_handle);
        var data = new byte[dataLength];
        int result = NativeMethods.cb4u_mutable_characteristic_value(_handle, data, dataLength);

        if (result == 0)
        {
            return null;
        }
        return data;
    }
}
```

長さが 0 の場合でも、空か null かを判断するために byte 配列を作成しています。

以下に詳細を示します。

### C#

[リンク](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.5/Packages/com.teach310.core-bluetooth-for-unity/Runtime/NativeMethods.cs#L254-L258)

```NativeMethods.cs
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
internal static extern int cb4u_mutable_characteristic_value_length(SafeNativeMutableCharacteristicHandle handle);

[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
internal static extern int cb4u_mutable_characteristic_value(SafeNativeMutableCharacteristicHandle handle, byte[] dataBytes, int dataLength);
```

#### 方向属性をつけていない理由

In, Out の属性はマーシャリングの動作を指定するための Attribute です。
固定長な byte 配列は Blittable であり、マーシャリングは発生しないため属性をつける必要がないと判断しました。

### Swift

[リンク](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.5/Plugins/CoreBluetoothForUnity/Sources/CoreBluetoothForUnity/CoreBluetoothForUnity.swift#L473-L493)

```CoreBluetoothForUnity.swift
@_cdecl("cb4u_mutable_characteristic_value_length")
public func cb4u_mutable_characteristic_value_length(_ characteristicPtr: UnsafeRawPointer) -> Int32 {
    let instance = Unmanaged<CB4UMutableCharacteristic>.fromOpaque(characteristicPtr).takeUnretainedValue()
    
    return Int32(instance.valueLength)
}

@_cdecl("cb4u_mutable_characteristic_value")
public func cb4u_mutable_characteristic_value(_ characteristicPtr: UnsafeRawPointer, _ dataBytes: UnsafeMutablePointer<UInt8>, _ dataLength: Int32) -> Int32 {
    let instance = Unmanaged<CB4UMutableCharacteristic>.fromOpaque(characteristicPtr).takeUnretainedValue()
    
    guard let value = instance.value else {
        return 0
    }
    
    value.withUnsafeBytes { (valueBytes: UnsafeRawBufferPointer) in
        let valueBytesPtr = valueBytes.bindMemory(to: UInt8.self).baseAddress!
        dataBytes.update(from: valueBytesPtr, count: Int(dataLength))
    }
    return 1
}
```

UnsafeMutablePointer<UInt8> への書き込みには [update(from:count:)](https://developer.apple.com/documentation/swift/unsafemutablepointer/update(from:count:)) を使っています。

この from に渡すコピー元のポインタを得るために value (Data 型)から[withUnsafeBytes](https://developer.apple.com/documentation/swift/array/withunsafebytes(_:)) や bindMemory を使用しています。

## その他のやり方

- [NSData](https://developer.apple.com/documentation/foundation/nsdata) を使うと、byte 配列の取得処理を共通化することはできそうです。

## おわりに

本記事では byte 配列の渡し方、受け取り方について説明しました。
byte 配列の受け取りは、ありそうで情報が全然見当たりませんでした。
特に Data からの byte 配列取得のあたりは Copilot に大いに助けられました。
あまり使う頻度は多くなさそうではありますが、参考になれば幸いです！