如何调试iOS模拟器/设备上原生依赖抛出的DllNotFoundException
问题背景
开发一款支持Android、iOS及桌面系统的.NET库,其他平台运行正常,但iOS平台出现异常。此前本地部署iOS版本可正常运行,但基于该库的应用被App Store拒绝,原因是原生依赖必须封装在Apple Framework中。重构nupkg将原生依赖放入xcframework结构后,iOS csproj可正常构建,且.NET程序集与原生依赖似乎已随应用部署,但应用加载原生依赖时抛出System.DllNotFoundException: 'nerdbank_qrcodes'异常,Visual Studio调试输出仅显示该错误信息。
当前nupkg结构
D:\PACKAGES\NUGET\NERDBANK.QRCODES\0.2.55-BETA-G209A879921 | .nupkg.metadata | nerdbank.qrcodes.0.2.55-beta-g209a879921.nupkg | nerdbank.qrcodes.0.2.55-beta-g209a879921.nupkg.sha512 | nerdbank.qrcodes.nuspec | README.md | THIRD_PARTY_DEPENDENCIES.txt | THIRD_PARTY_LICENSES.yml | +---lib | +---net8.0 | | Nerdbank.QRCodes.dll | | Nerdbank.QRCodes.xml | | | +---net8.0-ios17.5 | | | Nerdbank.QRCodes.dll | | | Nerdbank.QRCodes.xml | | | | | \---Nerdbank.QRCodes.resources | | | manifest | | | | | \---nerdbank_qrcodes.xcframework | | | Info.plist | | | | | +---ios-arm64 | | | \---nerdbank_qrcodes.framework | | | Info.plist | | | nerdbank_qrcodes | | | | | \---ios-arm64_x86_64-simulator | | \---nerdbank_qrcodes.framework | | Info.plist | | nerdbank_qrcodes | | | \---net8.0-windows7.0 | Nerdbank.QRCodes.dll | Nerdbank.QRCodes.xml | \---runtimes +---android-arm64 | \---native | libnerdbank_qrcodes.so | +---android-x64 | \---native | libnerdbank_qrcodes.so | +---linux-arm64 | \---native | libnerdbank_qrcodes.so | +---linux-x64 | \---native | libnerdbank_qrcodes.so | +---osx-arm64 | \---native | libnerdbank_qrcodes.dylib | +---osx-x64 | \---native | libnerdbank_qrcodes.dylib | +---win-arm64 | \---native | nerdbank_qrcodes.dll | \---win-x64 \---native nerdbank_qrcodes.dll
原生Framework处理脚本
# copy Info.plist and the binary into the appropriate .framework directory structure # so that when NativeBindings.targets references it with ResolvedFileToPublish, it will be treated appropriately. $RustTargetBaseDir = "$repoRoot/src/nerdbank-qrcodes/target" $RustDylibFileName = "libnerdbank_qrcodes.dylib" $DeviceRustOutput = "$RustTargetBaseDir/aarch64-apple-ios/$Configuration/$RustDylibFileName" $SimulatorX64RustOutput = "$RustTargetBaseDir/x86_64-apple-ios/$Configuration/$RustDylibFileName" $SimulatorArm64RustOutput = "$RustTargetBaseDir/aarch64-apple-ios-sim/$Configuration/$RustDylibFileName" $DeviceFrameworkDir = "$repoRoot/bin/$Configuration/device/nerdbank_qrcodes.framework" $SimulatorFrameworkDir = "$repoRoot/bin/$Configuration/simulator/nerdbank_qrcodes.framework" New-Item -Path $DeviceFrameworkDir,$SimulatorFrameworkDir -ItemType Directory -Force | Out-Null Write-Host "Preparing Apple iOS and iOS-simulator frameworks" Copy-Item $IntermediatePlistPath "$DeviceFrameworkDir/Info.plist" Copy-Item $IntermediatePlistPath "$SimulatorFrameworkDir/Info.plist" Write-Host "Created Info.plist with version $version" if ($IsMacOS) { # Rename the binary that contains the arm64 architecture for device. lipo -create -output $DeviceFrameworkDir/nerdbank_qrcodes $DeviceRustOutput install_name_tool -id "@rpath/nerdbank_qrcodes.framework/nerdbank_qrcodes" "$DeviceFrameworkDir/nerdbank_qrcodes" chmod +x "$DeviceFrameworkDir/nerdbank_qrcodes" # Create a universal binary that contains both arm64 and x64 architectures for simulator. lipo -create -output $SimulatorFrameworkDir/nerdbank_qrcodes $SimulatorX64RustOutput $SimulatorArm64RustOutput install_name_tool -id "@rpath/nerdbank_qrcodes.framework/nerdbank_qrcodes" "$SimulatorFrameworkDir/nerdbank_qrcodes" chmod +x "$SimulatorFrameworkDir/nerdbank_qrcodes" }
排查方向与调试步骤
确认xcframework是否被嵌入到最终App包
构建完成后找到生成的.app包,右键选择「显示包内容」,查看Frameworks目录下是否存在nerdbank_qrcodes.framework。若不存在,说明NuGet包结构或MSBuild配置有误,导致xcframework未被正确复制到App包中。验证Framework二进制的架构与路径配置
在Mac终端执行以下命令:# 检查支持的架构 lipo -info /path/to/nerdbank_qrcodes.framework/nerdbank_qrcodes # 检查install_name配置 otool -L /path/to/nerdbank_qrcodes.framework/nerdbank_qrcodes确保设备版本的Framework包含arm64架构,模拟器版本包含x86_64和arm64;同时
otool输出中的id必须为@rpath/nerdbank_qrcodes.framework/nerdbank_qrcodes。检查DllImport的库名是否正确
确认.NET代码中DllImport声明的库名是nerdbank_qrcodes(与Framework目录名一致,不带.framework后缀),而非之前动态库的名称(如libnerdbank_qrcodes)。查看Xcode控制台的详细日志
使用Xcode连接设备调试App,查看Console面板的输出,通常会包含加载失败的具体原因(如签名无效、架构不兼容、依赖缺失等)。检查NuGet包的资源配置
当前xcframework放置在lib/net8.0-ios17.5/Nerdbank.QRCodes.resources/目录下,需确认MSBuild能识别该结构并正确处理。可在项目的.csproj中显式添加xcframework引用,或检查NuGet包的.targets文件是否正确配置ResolvedFileToPublish,确保xcframework被纳入App包。验证Framework的签名有效性
App Store要求所有Framework必须正确签名,执行以下命令检查签名状态:codesign -vvv /path/to/nerdbank_qrcodes.framework
可能的操作错误点
xcframework的Info.plist配置不符合规范
需确保xcframework的Info.plist中正确声明各切片的平台与架构:设备切片的SupportedPlatform为iOS,SupportedArchitecture为arm64;模拟器切片对应配置正确。动态库重命名不彻底
从Rust生成的.dylib文件重命名为Framework内的二进制时,需确保直接命名为nerdbank_qrcodes(与Framework目录同名),不能保留.dylib后缀。NuGet包结构未遵循.NET iOS规范
iOS平台的原生Framework需放置在正确的NuGet目录结构中,确保MSBuild能自动识别并嵌入到App包,避免手动放置导致的路径识别问题。
内容的提问来源于stack exchange,提问作者Andrew Arnott

