本記事について
この記事は CoreBluetoothForUnity Advent Calendar 2023 の19日目の記事です。
ネイティブプラグインから C# のコールバックを呼ぶ際に文字列を渡す方法のうち、文字列そのもののポインタを渡す方法について説明します。
やること
C# から文字列を NSString のポインタとして受け取る場合はざっくり以下の流れになります。
C# 側からネイティブプラグイン側に破棄を指示しています。
この流れでは C# 側から処理が始まっていますが、コールバックの場合はネイティブプラグイン側から呼ばれることになるので、以下のようになります。
この場合コールバックの処理は同期のため、コールバックの処理終了後にネイティブプラグイン側に処理を入れることが可能です。
であるならば、C# 側で破棄を指示せず、ネイティブプラグイン側で文字列を破棄しても問題ないはずです。
さらに、破棄をネイティブプラグイン側で行うのであれば NSString のポインタじゃなくて最初から文字列渡してしまえばいいのでは?というのが以下です。
とてもシンプルになりました。
長くなりましたが、本記事ではこれをどうやって書くのかを説明します。
環境
- CoreBluetoothForUnity 0.4.6
コード
例として示す delegate は以下です。
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void CB4UCentralManagerDidConnectHandler(IntPtr centralPtr, IntPtr peripheralIdPtr);
public typealias CB4UCentralManagerDidConnectHandler = @convention(c) (UnsafeRawPointer, UnsafePointer<CChar>) -> Void
peripheralIdPtr が文字列のポインタです。
ペリフェラルは CoreBluetooth の用語であり、本記事では気にする必要はありません。
Swfit
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
let peripheralId = peripheral.identifier.uuidString
peripheralId.withCString { (uuidCString) in
didConnectHandler?(selfPointer(), uuidCString)
}
}
peripheralId は String です。
withCString を使って null 終端の C 文字列を取得し、コールバックの引数として渡しています。
この関数を抜けると Swift によって文字列は勝手に破棄されます。
C#
[AOT.MonoPInvokeCallback(typeof(NativeMethods.CB4UCentralManagerDidConnectHandler))]
static void DidConnect(IntPtr centralPtr, IntPtr peripheralIdPtr)
{
GetDelegate(centralPtr)?.DidConnect(Marshal.PtrToStringUTF8(peripheralIdPtr));
}
Marshal.PtrToStringUTF8
を使って文字列を取得するのが楽です。
Marshal.PtrToStringUTF8 内で文字列はマネージドメモリにコピーされるため、元の文字列が破棄されても問題ありません。
おわりに
本記事では Swift のネイティブプラグインにおいてコールバックで文字列を渡す方法としてはおそらくこれが一番簡単だろうと思うものを紹介しました。
withCString
にたどり着くために少し時間かかったため、Swift のネイティブプラグインを書く際に参考になれば幸いです!