Visual Studio 2022 から .NET MAUI(net9.0-ios)で ZebraRFID iOS SDK を組み込んだアプリを Release(IPA)で配布しようとすると、MtouchLink=SdkOnly を境にリンクエラーや TestFlight でのクラッシュが発生することがあります。ここでは、エラーログから原因を特定する考え方と、実務で効く回避策・切り分け手順をまとめます。
Release(IPA)で MtouchLink=SdkOnly にするとリンクエラーになる症状
今回の現象は「C# のビルド」ではなく、IPA 作成の最終工程である ネイティブリンク(clang++)で止まるのが特徴です。現場でよく見る条件は次の通りです。
- プロジェクト:.NET MAUI(TargetFramework は net9.0-ios)
- ビルド:Visual Studio 2022 から Release で IPA(Archive)生成
- 環境:Xcode 16.2、実機向け arm64
- SDK:ZebraRFID iOS SDK(ZebraRfidIntegratedSdk.dll)
- トリガー:<MtouchLink>SdkOnly</MtouchLink> にした時だけ失敗
ビルドログの典型例(抜粋)は以下です。
clang++ exited with code 1
Undefined symbols for architecture arm64:
"_EAAccessoryDidConnectNotification", referenced from:
...
"_EAAccessoryDidDisconnectNotification", referenced from:
...
"_EAAccessoryKey", referenced from:
...
"_OBJC_CLASS_$_EAAccessoryManager", referenced from:
...
"_OBJC_CLASS_$_EASession", referenced from:
...
ld: symbol(s) not found for architecture arm64
一方で、リンカー設定を Don’t link(リンクしない) や Link all(すべてリンク) にするとビルド自体は成功する、という挙動もポイントです。さらに、ビルドが通っても TestFlight 配布後に EXC_BAD_ACCESS / SIGABRT / KERN_INVALID_ADDRESS でクラッシュする、という追加症状が出るケースもあります。
まず押さえる:MtouchLink は「managed トリミング」だが、ネイティブリンクにも波及する
MtouchLink は iOS ビルドにおける「アセンブリのトリミング(不要コード削除)」の強さを決める設定です。ところが、バインディング SDK(.dll + .a)を使うと、SDK 側のメタデータ(必要フレームワークの宣言、リンカーフラグ、force_load 指定など)がビルドの途中で拾われたり拾われなかったりして、結果として clang++ のリンク引数が変わることがあります。SdkOnly でだけ壊れるのは、この“境界部分”が弱い SDK を踏んだ時に起きやすいパターンです。
| 設定 | 何が起きるか | 向いている場面 | 注意点 |
|---|---|---|---|
| Don’t link | トリミングしない | まず動かして切り分けたい/外部 SDK が多い | IPA サイズ増・起動時ロード増になりがち |
| SdkOnly | SDK アセンブリのみトリミング | 自作コードは安全に残しつつ最適化したい | SDK/バインディングの依存宣言が不完全だと「必要なリンク情報が落ちる」ことがある |
| Link all | すべてトリミング | サイズ最小化を最優先したい | 反射やネイティブからのコールバックでクラッシュしやすい |
結論として、外部ネイティブ SDK を含む iOS アプリは Don’t link → SdkOnly →(必要なら)Link all の順で段階的に攻めるのが、最短で安定点に到達しやすいです。
リンクエラーの本質:ExternalAccessory.framework が不足している
未解決になっているシンボルは EAAccessory / EASession など、Apple の ExternalAccessory フレームワーク(MFi アクセサリ)に属するものです。つまり、libintegratedsdk.a が ExternalAccessory を参照しているのに、最終リンクに ExternalAccessory.framework が入っていない状態です。
| 未解決シンボル(例) | 該当フレームワーク | 意味 | まず疑うべきこと |
|---|---|---|---|
| EAAccessoryManager / EASession | ExternalAccessory.framework | MFi アクセサリの管理・セッション | ExternalAccessory をリンクしていない/SDK が宣言していない |
| EAAccessoryDidConnectNotification など | ExternalAccessory.framework | 接続/切断の通知定数 | 同上 |
ここから導ける整理はシンプルです。原因は MAUI というより「ZebraRFID 側のネイティブライブラリ/バインディングの依存宣言不足」である可能性が高い、ということです(特に、同じ SDK のデモでも再現するならなおさらです)。
まずはビルドを通す:ExternalAccessory を明示的に追加する
本来は SDK が自動的に ExternalAccessory.framework を要求すべきですが、現場では「アプリ側で明示」して先に前進するのが現実的です。以下は、再現しやすい順に並べた対処パターンです。
ビルドログを“見える化”してから作業する
リンク引数が変わっているかどうかを確認できないと、対処が当たりか外れか判断できません。Visual Studio 側で ビルド出力を詳細(Diagnostic) にして、clang++ のコマンドラインに何が渡っているか追える状態にします。
- Visual Studio のオプションで「ビルド出力の詳細度」を上げる
- iOS Release のビルドログから clang++ 行を探し、-framework ExternalAccessory があるか確認する
csproj で -framework ExternalAccessory を追加する(最も手軽)
まず試すならこの方法が速いです。iOS ターゲットだけに適用するため、TargetFramework 条件を付けます。
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0-ios'">
<MtouchLink>SdkOnly</MtouchLink>
<MtouchExtraArgs>--framework ExternalAccessory</MtouchExtraArgs>
</PropertyGroup>
これで clang++ に ExternalAccessory が渡るようになれば、Undefined symbols は解消するのが通常です。解消しない場合は「引数が適用されていない」か「別のフレームワーク/ライブラリも不足している」可能性があるため、まずは clang++ の引数を確認してください。
NativeReference で依存を宣言する(ライブラリがプロジェクト内にある場合)
ネイティブの .a をプロジェクト内に持っている/参照できるなら、依存関係は NativeReference に寄せる方が保守性が上がります。
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0-ios'">
<NativeReference Include="Platforms/iOS/libintegratedsdk.a"
Kind="Static"
ForceLoad="True"
Frameworks="ExternalAccessory" />
</ItemGroup>
ForceLoad は静的リンク時の“取り込まれない事故”を避けるための保険です。SDK によっては不要ですが、アクセサリ系 SDK は初期化コードをカテゴリや自動実行ブロックに置くことがあるため、まずは付けて検証すると切り分けが速くなります。
それでもダメな場合に試す「リンカーフラグ」
Undefined symbols が ExternalAccessory 以外にも広がる場合や、ビルドは通るが実行時に SDK の初期化が動かない場合は、静的ライブラリ特有の取り込み問題を疑います。iOS では次のフラグが効くことがあります。
- -ObjC:Objective-C のカテゴリ/拡張が落ちる問題の回避
- -all_load / -force_load:静的ライブラリのオブジェクトを強制的に取り込む
ただし、これらは副作用(重複シンボルなど)もあるため、むやみに常用せず、まずは「どのフラグが必要か」を特定する使い方に留めるのが安全です。
SDK(ネイティブライブラリ/バインディング)側を疑うべきチェックポイント
ここまでで「ExternalAccessory を足せばビルドが通る」なら、SDK の依存宣言不足がほぼ確定です。通らない/別の不具合が出る場合は、次の観点で SDK の構成そのものを疑います。
| 観点 | なぜ重要か | 確認例 |
|---|---|---|
| arm64 を含むか | 実機配布では arm64 が必須 | Mac のターミナルで lipo -info libintegratedsdk.a を実行 |
| 必要フレームワークが宣言されているか | 宣言されないと MAUI 側が自動でリンクできない | バインディングの LinkWith/NativeReference に Frameworks 指定があるか |
| Xcode 16.2 / .NET 9 のサポート有無 | ツールチェーン更新でビルド/ランタイムが変わる | ベンダーのサポート表・リリースノートに明記されているか |
この段階で「Zebra 提供のデモでも同様」「dll を差し替えても再現」という状況なら、ベンダーへの問い合わせが最短です。アプリ側で無理に“場当たり的なフラグ”を増やすより、SDK の更新版や正しいバインディング手順をもらった方が、将来の保守コストを大きく下げられます。
TestFlight 配布後のクラッシュを切り分ける
TestFlight でのみ落ちる問題は、ビルドエラーよりも原因が分散しやすいです。まずは「Release の何が Debug と違うのか」を押さえておくと、切り分けの精度が上がります。
| Debug と違う点 | 起きがちな症状 | 対処の方向性 |
|---|---|---|
| AOT/最適化が強い | ネイティブのメモリ破壊が顕在化(EXC_BAD_ACCESS 等) | クラッシュログをシンボリケートし、SDK 内で落ちているならベンダーへ |
| トリミングの影響 | 反射・イベント購読・コールバック登録が欠けて SIGABRT/例外 | Link all を避ける、保持設定を追加する |
| 権限/Info.plist の差分が露呈 | 特定 API 到達時に SIGABRT | Bluetooth/外部アクセサリ/ネットワーク等の必要キーを確認 |
最優先は「クラッシュログのシンボリケート」
EXC_BAD_ACCESS や KERN_INVALID_ADDRESS は、ログがシンボリケートされていないと“勘”でしか進められません。TestFlight のクラッシュログは、dSYM を揃えることで関数名まで追えるようになります。
- IPA/Archive 生成時の dSYM を保管する
- Xcode Organizer(または配布基盤)でクラッシュログを symbolicate する
- スタックトレース上位が libintegratedsdk.a / Zebra の関数 なら、アプリ側の設定だけでの解決は難しい
Link all でだけ落ちる場合の“現実解”
サイズを削りたい気持ちは分かりますが、外部 SDK が反射や内部コールバックに依存していると、Link all はクラッシュの引き金になりがちです。現実解は次のどれかです。
- 配布は SdkOnly で止める(安定優先)
- Link all を使うなら、SDK が要求する保持設定(Preserve/linker 設定)をベンダーに要求する
- どうしても自前で保持する場合は、SDK が反射で触る型/メソッドを特定して “残す”
保持する対象が分からないまま闇雲に残すと、最適化の意味が薄れます。ベンダーが「MAUI/.NET iOS 向けの保持設定(linker 設定)」を持っているか、まずは確認するのが安全です。
現場で使える切り分けチェックリスト
最後に、作業の抜け漏れを減らすためのチェックリストを置いておきます。順に潰すだけで「アプリ設定か、SDK か」がかなり早く見えます。
| チェック | 見る場所 | OK の目安 |
|---|---|---|
| clang++ の引数に ExternalAccessory が入っている | Release ビルドログ | -framework ExternalAccessory が見える |
| ビルド成功パターンを固定できている | Don’t link / SdkOnly / Link all | どの設定で通り、どれで落ちるか説明できる |
| Info.plist の外部アクセサリ設定 | Platforms/iOS/Info.plist | UISupportedExternalAccessoryProtocols が SDK 指定通り |
| TestFlight のクラッシュログがシンボリケートされている | App Store Connect / Xcode Organizer | 関数名が追える(objc_msgSend だけではない) |
| クラッシュ箇所が SDK 内か app 内か判定できている | シンボリケート済みログ | libintegratedsdk.a の関数名が出ているか、managed 例外か判別できる |
ベンダー問い合わせを通すための情報セット
SDK 側の問題が濃厚なら、問い合わせの質がそのまま解決速度になります。次の情報を 1 セットにして送ると、往復回数を減らせます。
- .NET MAUI / .NET 9 / TargetFramework(net9.0-ios)
- Visual Studio 2022、Xcode 16.2、iOS バージョン、端末機種
- ZebraRfidIntegratedSdk.dll とネイティブライブラリのバージョン
- SdkOnly のときのリンクエラーログ全文(Undefined symbols の部分)
- csproj 側で ExternalAccessory を明示リンクしたかどうか
- TestFlight のクラッシュログ(可能ならシンボリケート済み)
- 最小再現手順(デモでも再現するならその手順)
伝えるべき要点は「ExternalAccessory 依存があるのに、SDK から必要フレームワーク指定が伝播していない可能性がある」「.NET 9 + Xcode 16.2 の組み合わせで再現する」です。ここまで整理して渡すと、ベンダー側も調査ポイントを外しにくくなります。
まとめ
.NET MAUI(.NET 9)で ZebraRFID iOS SDK を組み込んだ際、Release(IPA)で MtouchLink=SdkOnly にすると Undefined symbols(EAAccessory 系)が出る場合は、ExternalAccessory.framework のリンク不足が第一容疑です。まずは csproj で ExternalAccessory を明示してビルドを通し、TestFlight のクラッシュはログをシンボリケートして “SDK 内で落ちているか” を確定させるのが、最短での解決につながります。

コメント