【Unity】iOSネイティブプラグイン開発を完全に理解する - サンプルコードをSwiftだけで完結出来るように置き換える

本編「[【Unity】iOSネイティブプラグイン開発を完全に理解する](https://qiita.com/mao_/items/5b33c90e533a538570b8)」の**追記記事**です。
[記事中での用語や略称](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E8%A8%98%E4%BA%8B%E4%B8%AD%E3%81%A7%E8%A7%A3%E8%AA%AC%E3%81%99%E3%82%8B%E8%A8%80%E8%AA%9E%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)についてはそのまま本編に倣う形とし、記事の内容自体もある程度は本編を読み進めているのを前提に記載していきます。

---

先日辺りに @fuziki さんが書かれた以下の記事を試す機会があり、実際に自分がObjC++で実装したiOSネイティブプラグインを書き直してみた所、 **「ほんまにSwiftだけで完結できるやんけ...」** となったので、遅ればせながらも[本編](https://qiita.com/mao_/items/5b33c90e533a538570b8)にある各章のサンプルをSwiftオンリーの実装に書き換える形で補足して行ければと思います。

- 参考:
    - [Swift だけで Unity の iOS の Native Plugin を作る](https://qiita.com/fuziki/items/955c2b35514bcfc37969)
    - [[Unity]Swift だけで作ったNative PluginでUnityにコールバック](https://qiita.com/fuziki/items/b51353e9fe8f59640cc3)

特にSwiftでの**ポインタの扱い**や、**ARC(自動参照カウント)から手動での参照カウントへの切り替え**と言ったメモリに関する操作周りはObjC++には無い新しい要素が出てくるので、そこらも適宜補足しつつ解説して行ければと思います。[^1]

[^1]: ObjC++元いObjC自体はC言語の完全上位互換なので、ポインタ自体の扱いは言語仕様的に楽と言えば楽だった


---

ここからは[本編](https://qiita.com/mao_/items/5b33c90e533a538570b8)にある以下の章の内容をSwiftオンリーの実装に置き換える形で解説していきます。


- [最小構成から見るネイティブプラグイン基礎](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E6%9C%80%E5%B0%8F%E6%A7%8B%E6%88%90%E3%81%8B%E3%82%89%E8%A6%8B%E3%82%8B%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E5%9F%BA%E7%A4%8E)
- [ネイティブコード側でインスタンス化したオブジェクトの管理](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%B3%E3%83%BC%E3%83%89%E5%81%B4%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E5%8C%96%E3%81%97%E3%81%9F%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E7%AE%A1%E7%90%86)
- [ネイティブコードからC#のメソッドを呼び出す](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89c%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99)

:::note warn
他にも本編を除いて以下の付録記事でもObjC++でサンプルを実装してますが、基本的にはこの記事の内容を把握出来ていれば自前で置きかえ自体は可能かと思われるので、以下については割愛します。

- [ネイティブプラグインの設計について](https://qiita.com/mao_/items/15d05d25a99ab290fa50)
- [ネイティブプラグインを実装するには](https://qiita.com/mao_/items/88aeac26adb17194ebdc)
- [Xcodeの設定を自動化する](https://qiita.com/mao_/items/c678f93ee04608492788)
:::


# 「[最小構成から見るネイティブプラグイン基礎](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E6%9C%80%E5%B0%8F%E6%A7%8B%E6%88%90%E3%81%8B%E3%82%89%E8%A6%8B%E3%82%8B%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E5%9F%BA%E7%A4%8E)」を置き換える

こちらは本編で実装している[Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/minimumExample/Assets/MinimumExample/Plugins/iOS/Example.mm)の内容が**そのままSwiftでの実装(`Example.swift`)に置き換わるイメージになります。**

プロジェクト及び置き換えた`Example.swift`のコード全体はこちら。

- [swift/minimumExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/swift/minimumExample)
    - [Example.swift](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/swift/minimumExample/Assets/MinimumExample-Swift/Plugins/iOS/Example.swift)


## Exampleクラスを置き換える

ObjC++で実装されているExampleクラスをSwiftでの実装に置き換えると以下のようになります。
今回の例だとSwift化に伴い、`NSObject`の継承は不要となっているので消してます。

```swift:Example.swift
import Foundation

public class Example {
    /// ログに"Hello World"と出力して2を返す。
    /// NOTE: ここではクラスメソッド(静的関数)として実装
    ///
    /// - Returns: 2固定
    public static func printHelloWorld() -> Int32 {
        // ログ出力
        print("Hello World")

        // 戻り値を返す
        return 2
    }
}
```

そもそも「Swiftでクラスを実装する際に`NSObject`を継承すべきか？」の話については、パフォーマンスなどの観点から基本的には必要な場合を除いて不要になっているという認識です。

- 参考: [SwiftでClassを作るときにNSObjectを継承するべき？](https://mpon.hatenablog.com/entry/2015/08/13/174157)


## `extern "C"`で外部宣言している関数を置き換える

こちらは[今回の参考記事](https://qiita.com/fuziki/items/955c2b35514bcfc37969#swift-%E3%81%A7%E9%96%A2%E6%95%B0%E3%82%92%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B)にもある通り、`@_cdecl`を用いて**Swift上からCの関数として定義するようにします。**


```swift:Example.swift
// `@_cdecl("[メソッド名]")`を使うことでCの関数として定義することが可能
// NOTE: この関数が実際にUnity(C#)から呼び出される
@_cdecl("printHelloWorld")
public func printHelloWorld() -> Int32 {

    // ↑で実装している`Example.printHelloWorld`を呼び出すだけ。
    // NOTE: クラスメソッド(静的関数)として実装しているので、クラスをインスタンス化せずに直接呼び出せる
    return Example.printHelloWorld()
}
```

あとは通常通りにC#から呼び出すだけです。

```csharp:Example.cs
// Swiftで実装した`Example`クラスのP/Invoke

/// <summary>
/// `printHelloWorld`の呼び出し
/// </summary>
/// <remarks>
/// NOTE: Example.swiftの`@_cdecl`で定義した関数をここで呼び出す
/// - iOSのプラグインは静的に実行ファイルにリンクされるので、`DllImport`にはライブラリ名として「__Internal」を指定する必要がある
/// - `EntryPoint`にSwift側で定義されている名前を渡すことでC#側のメソッド名は別名を指定可能
/// </remarks>
[DllImport("__Internal", EntryPoint = "printHelloWorld")]
static extern Int32 PrintHelloWorld();
```





# 「[ネイティブコード側でインスタンス化したオブジェクトの管理](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%B3%E3%83%BC%E3%83%89%E5%81%B4%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E5%8C%96%E3%81%97%E3%81%9F%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E7%AE%A1%E7%90%86)」を置き換える

こちらも本編で実装している[Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/instanceMethodExample/Assets/InstanceMethodExample/Plugins/iOS/Example.mm)の内容が**そのままSwiftでの実装(`Example.swift`)に置き換わるイメージになります。**

プロジェクト及び置き換えた`Example.swift`のコード全体はこちら。

- [swift/instanceMethodExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/swift/instanceMethodExample)
    - [Example.swift](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/swift/instanceMethodExample/Assets/InstanceMethodExample/Plugins/iOS/Example.swift)


## Exampleクラスを置き換える

先ずは元のObjC++で実装されているExampleクラスをSwiftでの実装に置き換えます。
元がシンプルなので、そのまますんなりと置き換えられるかと思います。

```swift:Example.swift
import Foundation

public class Example {

    // メンバ変数
    private var member: Int32 = 0

    /// メンバ変数に値を設定
    func setMember(with value: Int32) {
        member = value
    }

    /// ログに"Hello World"と出力してメンバ変数(`self.member`)に設定された値を返す
    func printHelloWorldWithMember() -> Int32 {
        // ログ出力
        print("Hello World : [\(member)]")

        // 戻り値を返す
        return member
    }
}
```

## `extern "C"`で外部宣言している関数を置き換える

ここからはSwift上でポインタを扱う必要が出てくる都合上、若干複雑になってくる(と言うよりかは新しい知識が出てくる)ので順に解説していきます。

### ▼ インスタンスの生成

ここで行っていることを解説すると以下のようになります。

1. インスタンスの生成
2. **[Unmanaged](https://developer.apple.com/documentation/swift/unmanaged)を利用して、生成したインスタンスをARC(自動参照カウント)の管理下から外す**
    - その上で[passRetained](https://developer.apple.com/documentation/swift/unmanaged/1541288-passretained)を経由することで手動で参照カウンタをインクリメント(retain)しつつ、インスタンスに対応する`Unmanaged`型を取得
3. 更に[Unmanaged.toOpaque](https://developer.apple.com/documentation/swift/unmanaged/2140548-toopaque)で**インスタンスの生ポインタ(`UnsafeMutableRawPointer`)を取得**
    - 取得できるポインタは[UnsafeMutableRawPointer](https://developer.apple.com/documentation/swift/unsafemutablerawpointer)と言う**ミュータブルな型**で返ってくるので、こちらを[UnsafeRawPointer](https://developer.apple.com/documentation/swift/unsaferawpointer)と言う**イミュータブルな型**に変換して返す[^2] [^3]

[^2]: コメントでも記載しているが、P/Invokeと言う性質を踏まえると処理自体には特に意味は無いかもだが、「コード上で書き換える意図は無い」と言うのを自明にするために敢えて型変換を行っている

[^3]: なので、ここは実際には型変換を行わずとも`UnsafeMutableRawPointer`型のまま返しても処理的には問題はない


```swift:Example.swift
// インスタンス化
// NOTE: 戻り値のポインタをC#側でIntPtrなどで保持し、インスタンスメソッドの呼び出し時に渡して使う
@_cdecl("createExample")
public func createExample() -> UnsafeRawPointer {
    let instance = Example()

    // `Unmanaged`を利用することで参照型をARC(自動参照カウント)の管理下から外すことが可能
    // → 以下の処理は自前で参照カウンタをインクリメント(retain)しつつ、インスタンスに対応する`Unmanaged`型を取得している
    let unmanaged = Unmanaged<Example>.passRetained(instance)

    // `Unmanaged.toOpaque`でインスタンスの生ポインタを取得可能
    // ただし、型が`UnsafeMutableRawPointer`なので、一応は意図を明示的にするために`UnsafeRawPointer`に変換している。
    // (P/Invokeに於いてはあまり意味は無いかもだが..)
    return UnsafeRawPointer(unmanaged.toOpaque())
}
```

`Unmanaged`や`UnsafeMutableRawPointer`なりと色々出てきてますが、順に解説していきます。

#### ★ `Unmanaged`型について

Swiftには`Unmanaged`(正確には`Unmanaged<T>`)と言う型があり、こちらを使うことで以下のようなメモリに関する**アンマネージドな操作**を行うことが可能となります。

- 参照型のインスタンスをARC(自動参照カウント)の管理下から外し、手動での管理[^5]に切り替える
- 管理下から外した参照型のインスタンスの生ポインタを取得
    - 逆に生ポインタを`Unmanaged`型へと変換し、参照型のインスタンスを取得することも可能

こちらの詳細については以下の記事が参考になります。

- [Swiftで参照型の値から生ポインタを作る方法](https://qiita.com/omochimetaru/items/2aa00c7cb0eef4e57999)
    - [API Reference - Unmanaged](https://developer.apple.com/documentation/swift/unmanaged)

[^5]: 所謂MRC(Manual Reference Counting)と言うやつ？(`Unmanaged`を利用した手動管理もMRCと呼んで良いのかは不明)


:::note alert
この性質から`Unmanaged`を用いた上で**誤ったメモリ操作を行うと、メモリリークやクラッシュと言った事故**を引き起こしてしまうので、取り扱いには注意する必要があります。

→ e.g. 参照カウンタをインクリメント(retain)したままデクリメント(release)を行わない、解放済みのインスタンスに対するメモリアクセスなど
:::


#### ★ Swiftでのポインタ型の扱いについて

Swiftではコード上でポインタ型を表すためには`UnsafePointer`型及びそれらの派生系を利用する必要があります。

こちらについては以下の記事が参考になります。

- [素晴らしいSwiftのポインタ型の解説](https://qiita.com/omochimetaru/items/c95e0d36ae7f1b1a9052)

ちなみに、[Unmanaged.toOpaque](https://developer.apple.com/documentation/swift/unmanaged/2140548-toopaque)でポインタを取得した場合には「型情報を持たない`UnsafeMutableRawPointer`と言う型」が返ってきます。

その上でもし値を書き換える想定が無い場合にはイミュータブル版である`UnsafeRawPointer`に型変換しておくのが安全かもしれません。

- [API Reference - UnsafeRawPointer](https://developer.apple.com/documentation/swift/unsaferawpointer)
- [API Reference - UnsafeMutableRawPointer](https://developer.apple.com/documentation/swift/unsafemutablerawpointer)


#### ★ Swift側で返した`UnsafeRawPointer`型は、C#上で`IntPtr`型として受け取れる

閑話休題。

Swift側にて`@_cdecl`で定義した`func createExample() -> UnsafeRawPointer`は、通常通りにC#側でIntPtrとして受け取ることが出来ます。

```csharp:Example.cs
// ObjectiveC++コードで実装した`Example`クラスのP/Invoke

// ネイティブコード側にあるExampleクラスのインスタンス化
// NOTE: 戻り値はインスタンスのポインタ
[DllImport("__Internal", EntryPoint = "createExample")]
static extern IntPtr CreateExample();
```


### ▼ インスタンスの解放

インスタンスの解放は以下のようになります。

C#から渡された生ポインタを`Unmanaged`型へと戻し、[release](https://developer.apple.com/documentation/swift/unmanaged/1538352-release)を叩くことで参照カウンタをデクリメント(release)して解放します。

```swift:Example.swift
// 解放
@_cdecl("releaseExample")
public func releaseExample(_ instancePtr: UnsafeRawPointer) {
    // 生ポインタ(UnsafeRawPointer)は`fromOpaque`に渡すことでUnmanaged型に変換可能
    let unmanaged = Unmanaged<Example>.fromOpaque(instancePtr)

    // `createExample()`でインクリメント(retain)した参照カウンタをデクリメント(release)することで解放
    unmanaged.release()
}
```


### ▼ インスタンスメソッドの呼び出し。

インスタンスメソッドの呼び出し手順について、先ずは先述した通りにC#から渡された生ポインタを`Unmanaged`型へと戻し、更にここから[takeUnretainedValue](https://developer.apple.com/documentation/swift/unmanaged/1539180-takeunretainedvalue)を叩くことで **参照カウンタをインクリメント(retain)せずに`Example`クラスのインスタンスを取得します。**

後はインスタンスまで取得できたらメソッドを呼び出すだけです。

```swift:Example.swift
// 以下はインスタンスメソッドの呼び出し
// NOTE: 第一引数にはインスタンス化した際に保持しているポインタを渡す
@_cdecl("setMember")
public func setMember(_ instancePtr: UnsafeRawPointer, _ value: Int32) {
    // `Unmanaged<T>.takeUnretainedValue`でUnmanaged型をインスタンスに戻すことが可能
    // ここではメソッドを呼び出したいだけであり、参照カウンタはそのままで居て欲しいので`takeUnretainedValue`を利用している
    // NOTE: 逆にインスタンスに戻す際に参照カウンタをインクリメント(retain)したい場合には`takeRetainedValue`が使える
    let instance = Unmanaged<Example>.fromOpaque(instancePtr).takeUnretainedValue()
    instance.setMember(with: value)
}

@_cdecl("printHelloWorldWithMember")
public func printHelloWorldWithMember(_ instancePtr: UnsafeRawPointer, _ value: Int32) -> Int32 {
    // `setMember`と同上
    let instance = Unmanaged<Example>.fromOpaque(instancePtr).takeUnretainedValue()
    return instance.printHelloWorldWithMember()
}
```

ちなみにコメントにも記載してますが、もしインスタンスに戻す際に参照カウンタをインクリメント(retain)したい場合には[takeRetainedValue](https://developer.apple.com/documentation/swift/unmanaged/1539754-takeretainedvalue)を叩くことで操作することが可能です。





# 「[ネイティブコードからC#のメソッドを呼び出す](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89c%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99)」を置き換える

最後に表題の章の実装を置きかえます。

とは言え、こちらも本編で実装している[Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/callbackExample/Assets/CallbackExample/Plugins/iOS/Example.mm)の内容が**そのままSwiftでの実装(`Example.swift`)に置き換わるイメージになります。**

C#側のコードは大幅には変わっていないものの、以下の記事を参考にすると、ちゃんと`UnmanagedFunctionPointer`や`MarshalAs`と言った各種Attributeを付けたほうが良さそう...と思ったので、こちらを付け加える改修だけ加えてます。[^4]

[^4]: 無くても動くと言えば動くが...有りと無しの挙動の違いを理解しきれていない...調査する機会があったら理解でき次第に追記予定

- [[Unity]Swift だけで作ったNative PluginでUnityにコールバック](https://qiita.com/fuziki/items/b51353e9fe8f59640cc3)


プロジェクト及び置き換えた`Example.swift`のコード全体はこちら。

- [swift/callbackExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/swift/callbackExample)
    - [Example.swift](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/swift/callbackExample/Assets/CallbackExample/Plugins/iOS/Example.swift)
    - [Example.cs](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/swift/callbackExample/Assets/CallbackExample/Scripts/Example.cs) (※ロジックはそのままで、P/Invokeに関するAttributeだけ付け加えてます)


# 関数ポインタを定義

Swift型では以下のように`@convention(c)`を付けることで**C言語の関数ポインタ形式**で定義することが出来ます。
(参考: [Swiftのattributeまとめ[Swift4対応] - @convention](https://qiita.com/shtnkgm/items/a793f26445f2b8390bee#convention))

```swift:Example.swift
// NOTE: C#側で定義している以下のデリゲート型に対応する関数ポインタ
// > delegate void SampleCallbackDelegate(Int32 num);
//
// ちなみに`@convention(c)`はC言語の関数ポインタ形式であることを指す
public typealias SampleCallbackDelegate = @convention(c) (Int32) -> Void
```

こちらはC#のコード上では以下のデリゲート型及びコールバックに対応します。

```csharp:Example.cs
// 登録するメソッド(ここで言う`SampleCallback`)と同じフォーマットのデリゲート
// NOTE: ネイティブコード側で定義している以下の関数ポインタに対応する
// > typedef void (* sampleCallbackDelegate)(int32_t);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void SampleCallbackDelegate([MarshalAs(UnmanagedType.I4)] Int32 num);


// 実際にネイティブコードから呼び出されるメソッド
// NOTE: iOS(正確に言うとAOT)の場合には「staticメソッドな上で`MonoPInvokeCallbackAttribute`を付ける必要がある」
[AOT.MonoPInvokeCallbackAttribute(typeof(SampleCallbackDelegate))]
static void SampleCallback(Int32 num)
{
    Debug.Log($"ネイティブコードから呼び出された : {num}");
}
```


# Exampleクラスを置き換える

こちらも特に特筆すべき項目はなく、そのまま置き換えられるかと思います。

```swift:Example.swift
public class Example {

    // メンバ変数
    // NOTE: `registerSampleCallback`から渡されるC#のコールバックを持つ
    private var sampleCallbackDelegate: SampleCallbackDelegate? = nil

    /// コールバックの登録
    ///
    /// NOTE:
    /// メンバ変数`sampleCallbackDelegate`にC#から渡されるコールバックを登録
    /// (C#風に言い換えるとデリゲート型のフィールドにメソッドを渡すイメージ)
    func registerSampleCallback(_ delegate: @escaping SampleCallbackDelegate) {
        sampleCallbackDelegate = delegate
    }

    /// `sampleCallbackDelegate`に登録してあるコールバックを呼び出す
    func callSampleCallback() {
        sampleCallbackDelegate?(2);
    }
}
```


## `extern "C"`で外部宣言している関数を置き換える

### ▼ インスタンスの生成と解放

こちらは前の章と同じです。(なので詳細については割愛)

```swift:Example.swift
// インスタンスの生成・解放
// インスタンス化
// NOTE: 戻り値のポインタをC#側でIntPtrなどで保持し、インスタンスメソッドの呼び出し時に渡して使う
@_cdecl("createExample")
public func createExample() -> UnsafeRawPointer {
    let instance = Example()
    let unmanaged = Unmanaged<Example>.passRetained(instance)
    return UnsafeRawPointer(unmanaged.toOpaque())
}

// 解放
@_cdecl("releaseExample")
public func releaseExample(_ instancePtr: UnsafeRawPointer) {
    let unmanaged = Unmanaged<Example>.fromOpaque(instancePtr)
    unmanaged.release()
}
```

### ▼ コールバックの登録と呼び出し

前の章の内容さえ把握出来ていれば、こちらもほぼ直感的に書くことが出来るかと思います。
ちなみに、この例だとコールバック自体はスコープ外でも保持されるので`@escaping`を付ける必要がります。

```swift:Example.swift
// C#から渡されるコールバックをインスタンスに登録
@_cdecl("registerSampleCallback")
public func registerSampleCallback(_ instancePtr: UnsafeRawPointer, _ delegate: @escaping SampleCallbackDelegate) {

    // NOTE: こういう感じに直接呼び出すこともできる
    //delegate(1)

    // インスタンスに登録
    let instance = Unmanaged<Example>.fromOpaque(instancePtr).takeUnretainedValue()
    instance.registerSampleCallback(delegate)
}

// インスタンスに登録したコールバックを呼び出し
@_cdecl("callSampleCallback")
public func callSampleCallback(_ instancePtr: UnsafeRawPointer) {
    let instance = Unmanaged<Example>.fromOpaque(instancePtr).takeUnretainedValue()
    instance.callSampleCallback()
}
```