.NET9時代のiOS向けのNative Library Interop
.NET MAUIアプリでNative LibraryやNative SDKにアクセスするためのパターンとして、Native Library Interopがあります。
このNative Library Interopですが、.NET 9になり方法が少し変わりました。
2025年1月3日現在、日本語版のMicrosoft Learnの該当ページは古い記述のままになっており、その記述のとおりに実行するとリンカーが落ちるような状況になってしまいます。
本記事では翻訳前のドキュメントを元に、実際に試すことが出来るまでの流れを追います。
英語版の公式ドキュメント
ドキュメントから参照されているサンプルプロジェクトリポジトリ
事前準備
macOSで実行していることを前提とします。
必須
- .NET 9
- .NET MAUI Workload
あると便利
iOS向けBindingには、Objective-Cのメソッド名などを記述する必要があります。
例えば (void)initializeWithClientName:(NSString * _Nonnull)clientName outputPortName:(NSString * _Nonnull)outputPortName destinationName:(NSString * _Nonnull)destinationName;
みたいのがあれば、initializeWithClientName:outputPortName:destinationName:
みたいな感じに…
わざわざ調べたりするのは面倒なので、以下のヘルパーツールを使うのが良いです。
- Objective-Sharpie (クリックするとダウンロードが始まります)
Native Libraryのプロジェクト作成
Xcodeでプロジェクトを作成します。
MultiplatformのFrameworkやiOSのFrameworkを作成します。
今回はSampleNativeLibraryという名前でプロジェクトを作成しました。
Swiftファイルをプロジェクトに追加し、こんな感じで文字列を結合して返す関数を作ってみました。
import Foundation
import os
let logger = Logger(subsystem: "com.example.sample", category: "binding")
@objc(Sample)
public class Sample: NSObject {
@objc
public static func greeting(name: String) -> String {
logger.info("User name: \(name, privacy: .public)")
return "Hello, \(name)!"
}
}
またプロジェクトファイルを編集し、SWIFT_INSTALL_OBJC_HEADER
の項目を YES
に変更します。
これがないと ${LibraryName}-Swift.h
が生成されず、Objective-Sharpieでバインディングを自動生成することが出来ません。
またmacOSも対象とする場合、SUPPORTS_MACCATALYST
の項目を YES
に変更します。
バインディングプロジェクトの作成
実際にNative LibraryをBindingするプロジェクトを作成します。
dotnet new list
でテンプレートを調べるとiosbinding
というテンプレートが見つかります。
これを使ってプロジェクトを作成しましょう。
生成されたプロジェクトのcsprojファイルに以下の内容を追記します。
<ItemGroup>
<XcodeProject Include="relative_path_to_native_library_xcode_project/xcode_project.xcodeproj">
<!-- Project名を入れる -->
<SchemeName>SampleNativeLibrary</SchemeName>
<!-- Metadata applicable to @(NativeReference) will be used if set, otherwise the following defaults will be used:
<Kind>Framework</Kind>
<SmartLink>true</SmartLink>
-->
</XcodeProject>
</ItemGroup>
ここが日本語版のMicrosoft Learnの記述だと古くなっていたようで、リンク時に失敗していました。
Binding用のコード追加
このプロジェクトのApiDefinition.cs
にバインディング用のコードを追記していきます。
otool -ov
でexportされているシンボルを見つけてApiDefinition.csに記述するのもいいですが、今回はObjective-Sharpieを利用して定義を生成していきます。
Objective-Sharpieを利用するには、Native Libraryのヘッダファイルが必要となります。
これを用意するために、一度Native Libraryのプロジェクトをビルドします。
Xcodeでビルドしてもよいですが、コマンドラインで行った方がプロジェクトまでのパスを記述しやすいのでコマンドラインで行います。
$ cd SampleNativeLibrary
$ ls .
SampleNativeLibrary
SampleNativeLibrary.xcodeproj
$ xcodebuild -project SampleNativeLibrary.xcodeproj build
同一ディレクトリにbuildディレクトリが生成されます。
次にObjective-Sharpieの引数に渡すための値を調べます。
$ sharpie xcode -sdks
sdk: appletvos18.2 arch: arm64
sdk: iphoneos18.2 arch: arm64 armv7
sdk: ios18.2-macabi arch: x86_64 arm64
sdk: macosx15.2 arch: x86_64 arm64
sdk: watchos11.2 arch: armv7k arm64
インストールされ利用可能なSDKが列挙されるので、この中のiphoneos
の値を控えます。
ここでいうとiphoneos18.2
です。
そして実際にコードの生成を行います。
$ sharpie bind --output=適当なディレクトリ --namespace=Bindingライブラリのnamespace --sdk=iphoneos18.2 -scope build/Release/SampleNativeLibrary.framework/Headers build/Release/SampleNativeLibrary.framework/Headers/*.h
こうすると、指定したディレクトリにApiDefinition.csが生成されます。
これをそのままBindingライブラリのファイルに置き換えると楽です。
Verify
attributeなどが含まれる場合がありますが、内容が正しそうであればattributeを削除しビルドが通るようにします。
実際に出力された結果は以下のとおりです。
using Foundation;
namespace Bindingライブラリのnamespace
{
// @interface Sample : NSObject
[BaseType (typeof(NSObject))]
interface Sample
{
// +(NSString * _Nonnull)greetingWithName:(NSString * _Nonnull)name __attribute__((warn_unused_result("")));
[Static]
[Export ("greetingWithName:")]
string GreetingWithName (string name);
}
[Static]
[Verify (ConstantsInterfaceAssociation)]
partial interface Constants
{
// extern double SampleNativeLibraryVersionNumber;
[Field ("SampleNativeLibraryVersionNumber", "__Internal")]
double SampleNativeLibraryVersionNumber { get; }
// extern const unsigned char[] SampleNativeLibraryVersionString;
[Field ("SampleNativeLibraryVersionString", "__Internal")]
byte[] SampleNativeLibraryVersionString { get; }
}
}
その他の詳しいObjective-Sharpieの使い方は前述したドキュメントを参照してください。
これでBindingライブラリの準備は完了しました。
後はこれをMAUIプロジェクトから参照し利用するだけです。
ネイティブライブラリのデバッグは少々面倒なので、Swiftコードに仕込んだloggerなどをコンソールで頑張っていきましょう。
Discussion