Microsoft Build 2022 ⼤會上正式釋出了 .NET MAUI , 對於 .NET 開發者可以⽤ C# 完成跨平臺的前端應⽤開發。對⽐起 MAUI 的前身 Xamarin , MAUI 除了可以⽤傳統的原⽣開發模式外,還⽀持了 Blazor 的混合式開發。這也讓更多⽅向的開發⼈員能進⼊到跨平臺的應⽤開發中來。有⼈會提出雲原⽣時代,前端開發還重要嗎 ?實際上,多端應⽤相容是雲原⽣不可缺少的⻔⾯。互聯⽹時代,有很多出⾊的應⽤,併發布了針對第三⽅應⽤的 SDK,開發者可以結合這些 SDK 做相關的解決⽅案。通過 MAUI 能調⽤這些 SDK 嗎?我會通過系列⽂章去和⼤家介紹。
為何要繫結原⽣ SDK
我們知道⼀個應⽤可以融⼊不同的場景,例如⼀個打⻋應⽤就需要地圖,例如⼀個拍照應⽤就需要社交,例如⼀個如果你是傳統的物聯⽹應⽤你需要⼀個藍⽛的通訊協議。拿來主義就是⼀個節省的⽅式,可以結合第三⽅提供的 SDK 來完成應⽤的開發。對於 .NET 開發⼈員會是⼀個難點,因為習慣性地去調⽤ DLL ,但在iOS / Android 原⽣開發上,實際上是有不同的庫調⽤機制。在 Xamarin 時代,就有不少開發者去⽤ C# 繫結第三⽅的庫,例如在中國市場就有⽀付寶,微信,⾼德地圖等。到了 MAUI 有什麼不⼀樣呢?在⼤致上是和Xamarin 繫結⽅式⼀樣。但由於 MAUI 融⼊到了 .NET 6,實際上就是⼀個項⽬⽂件格式的改變。現階段你可以通過命令⾏的⽅式快速構建 iOS / Android 的繫結項⽬。
▌MAUI iOS 庫的繫結
dotnet new iosbinding -o iOS.AMapSDK.Binding
要做 iOS / macOS的繫結你除了建立繫結項⽬外,你還需要安裝 Shapie ⼯具 (https://aka.ms/objectivesharpie)做對應轉換, 可以通過命令⾏去針對 iOS 的動態庫和靜態庫做對應轉換。這⾥補充⼀點你的 Xcode環境是必須要安裝的。下⾯是⼀個簡單的轉換語句,更多具體⼤家可以關注我的該系列的 iOS 庫⽂件繫結⽂章。
sharpie bind -framework /your path/AMapFoundationKit.framework -sdk
iphoneos15.5
▌MAUI Android 庫的繫結
dotnet new android-bindinglib -o Droid.AMapSDK.Binding
Android 的繫結和 iOS 不⼀樣,直接把第三⽅庫 Android SDK 的 jar 或者 aar 包放進去編譯即可。
如果你希望瞭解更多可以關注本系列 Android 庫繫結的系列⽂章。
控制元件定製
在 Xamarin.Forms 中,通過渲染器機制對跨平臺各⾃控制元件的引⽤,並且依賴於 INotifyPropertyChanged 。.NET MAUI 沒取消了渲染器機制,⽽是引⼊了⼀種稱為 Handler 的模式。有了 Handlers 更靈活 ,⽽且在需要時更容易擴充套件或覆蓋。
這是 MAUI 全新的 Handler 模式
我們通過 Handler 機制可以構建好⾼德地圖的 MAUI 控制元件
你可以通過 https://github.com/kinfey/AMa... 使⽤體驗 MAUI 的⾼德 Android / iOS 控制元件
介紹了⼀些做⾼德地圖的 iOS / Android MAUI 控制元件的主要知識之後,接下來將重點介紹 iOS 原⽣庫繫結的知識, 並告訴⼤家在繫結原⽣庫過程的⼀些技巧,希望給到⼩夥伴⼀些啟發。
認識 iOS 動態庫和靜態庫
在繫結之前,我們需要學習⼀下 iOS 的動態庫和靜態庫。最簡單理解的⽅式是在 iOS 中靜態庫是以 .a 字尾結尾,動態庫是以 .dylib 字尾結尾。⽆論靜態庫和動態庫都可以打包成 Framework 。
▌靜態庫和動態庫的區別
- 靜態庫的特點是編譯時會把庫⽂件直接拷⻉⼀份到⽬標應⽤程式,⽽這個拷⻉是駐留在⽬標應⽤程式⾥⾯的,所以編譯完成後,靜態庫的⽂件就沒有⽤了。但有個缺點就是,因為需要拷⻉,所以⽣成的應⽤程式的容量會較⼤。
- 動態庫和靜態庫剛好是相反,編譯的時候是不會拷⻉到⽬標應⽤程式⾥⾯的,所以⽣成應⽤程式的體積較⼩,⽽且⼀個動態庫可以共享給多個應⽤程式使⽤。但⽣成應⽤程式是依賴於動態庫,這也導致經常會出現動態庫找不到的情況。
我們來拆解⼀下⾼德地圖基礎的 SDK - AMapFoundationKit.framework
這⾥就包含了對應的頭⽂件資訊,模組資訊,以及靜態庫。你可以清晰看到⾼德地圖打包成 Framrwork 的實現。這也是我們對庫概念的認識,編譯好的⼆進位制程式碼,向外暴露頭⽂件給第三⽅開發者使⽤。
通過 Sharpie ⼯具⽣成 C# 調⽤的接⼝
Shapie 是⼀個⾮常好⽤的轉換⼯具,它⽀持在 macOS 下對 Objective-C 的庫的轉。通過 Sharpie 可以對庫⽂件給出的頭⽂件進⾏轉換完成 C# 的繫結。在 MAUI 前身 Shapie ⼯具就已經存在 , 我經常就利⽤這個⼯具做轉換。
因為這次⾼德地圖的功能我⽤到 3D ,所以我會對⾼德的 AMapFoundationKit.Framework 和MAMapKit.framework 兩個 Framework 進⾏繫結轉換。
▌轉換 AMapFoundationKit.Framework
sharpie bind -framework AMapFoundationKit.framework -sdk iphoneos15.5
▌轉換 MAMapKit.framework
sharpie bind -framework MAMapKit.framework -sdk iphoneos15.5
補充:MAMapKit.framework 依賴於 AMapFoundationKit.framework ,所以要放在⼀個相同的⽬錄下。
這⾥⾯要注意,你需要安裝好 Xcode ,建議安裝到最新 ,並對應最新的 iOS SDK , 當然你也可以根據需要繫結不同版本的 iOS SDK , 你可以通過⼀次是命令檢視環境
sharpie xcode -sdks
通過命令⾏繫結⽣成的是兩個⽂件是 StructsAndEnums.cs 和 ApiDefinitions.cs ,StructsAndEnums.cs 對應的是⼀些常量和列舉型別,ApiDefinitions.cs 對應的是⼀些接⼝和⽅法 。
建立 MAUI 的 iOS 繫結項⽬
這⾥建立需要注意,現在 Visual Studio 2022 的模版都沒有完成,現在⼤家⽤命令⾏建立,因為我們有兩個項⽬,需要建立兩個 Binding 的項⽬分別是針對於 AMapFoundationKit.Framework 的項⽬構建
dotnet new iosbinding -o iOS.AMap.Foundation
針對於 MAMapKit.framework 的項⽬構建
`
dotnet new iosbinding -o iOS.AMap.3D
`
⽣成好後,需要把 AMapFoundationKit.framework 放到 iOS.AMap.Foundation 的⽬錄下,MAMapKit.framework 放到 iOS.AMap.3D ⽬錄下。並把⽣成的 StructsAndEnums.cs 和 ApiDefinitions.cs 放到對應⽬錄。
項⽬設定調整
1. 在 Sharpie ⽣成的⽬錄下 StructsAndEnum.cs ,⽽在構建的 Binding ⽬錄下是 ApiDefinition.cs , 要把它替換掉。所以要對 .csproj 項⽬進⾏修改
<ItemGroup>
<ObjcBindingApiDefinition Include="ApiDefinitions.cs" />
<ObjcBindingCoreSource Include="StructsAndEnums.cs" />
</ItemGroup>
2. 對 iOS.AMap.Foundation 進⾏編譯
▌在 AMapFoundationKit.framework.csproj 增加對 Framework 的引⽤
<ItemGroup>
<NativeReference Include="AMapFoundationKit.framework">
<Kind>Framework</Kind>
<ForceLoad>True</ForceLoad>
<SmartLink>False</SmartLink>
</NativeReference>
</ItemGroup>
Kind :原⽣繫結型別可以是 Framwork 也可以是 StaticLibary
ForceLoad :強載入,選擇 True
SmartLink :智慧連結
完成的項⽬.csproj 設定為
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-ios</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IsBindingProject>true</IsBindingProject>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoBindingEmbedding>false</NoBindingEmbedding>
</PropertyGroup>
<ItemGroup>
<ObjcBindingApiDefinition Include="ApiDefinitions.cs" />
<ObjcBindingCoreSource Include="StructsAndEnums.cs" />
</ItemGroup>
<ItemGroup>
<NativeReference Include="AMapFoundationKit.framework">
<Kind>Framework</Kind>
<ForceLoad>True</ForceLoad>
<SmartLink>False</SmartLink>
</NativeReference>
</ItemGroup>
</Project>
編譯 iOS.AMap.Foundation , 你會覺得奔潰,因為⾮常多的出錯資訊。這是因為 Shapie 做轉換時,⼀些轉換沒做好導致的,這個時候你就需要⼀個⼀個進⾏調整
▌歸類⼀下出錯資訊
- The type or namespace name 'VerifyAttribute' could not be found
這類資訊時因為轉換時候沒有確認好屬性,所以會增加 VerifyAttribute 欄位,這個⼀般情況下把這個欄位註釋掉就可以了,如
static class CFunctions
{
// NSString * AMapEmptyStringIfNil (NSString *s);
[DllImport ("__Internal")]
// [Verify (PlatformInvoke)]
static extern NSString AMapEmptyStringIfNil (NSString s);
// extern CLLocationCoordinate2D AMapCoordinateConvert
(CLLocationCoordinate2D coordinate, AMapCoordinateType type);
[DllImport ("__Internal")]
// [Verify (PlatformInvoke)]
static extern CLLocationCoordinate2D AMapCoordinateConvert
(CLLocationCoordinate2D coordinate, AMapCoordinateType type);
// extern BOOL AMapDataAvailableForCoordinate (CLLocationCoordinate2D
coordinate);
[DllImport ("__Internal")]
// [Verify (PlatformInvoke)]
static extern bool AMapDataAvailableForCoordinate
(CLLocationCoordinate2D coordinate);
}
The type or namespace name 'AMapFoundationKit'
名稱空間問題,這個你需要為 StructsAndEnums.cs 和 ApiDefinitions.cs 增加命名控制元件就可以了,你可以直接⽤ AMapFoundationKit ,也可以⾃⼰修改喜歡的名字 ,我這⾥⽤ iOS.AMap.Foundation 名字和項⽬對應
- Duplicate 'Static' attribute
這個是因為 ApiDefinitions.cs 的 Constants 重複定義了,這個就需要重新整理歸併為⼀個就可以了
- Unsupported type for Fields: bool for 'iOS.AMap.Foundation.Constants _amapLocationOverseas'.e
型別不對應導致編譯不通過,這個時候我修改為
[Field ("_amapLocationOverseas", "__Internal")]
IntPtr _amapLocationOverseas { get; }
這樣你就可以編譯通過 iOS.AMap.Foundation
3. 對 iOS.AMap.3D 進⾏編譯
▌新增對 iOS.AMap.Foundation的引⽤
因為 MAMapKit.framework 依賴於 AMapFoundationKit.framework , 所以 iOS.AMap.3D 是依賴於iOS.AMap.Foundation
<ItemGroup>
<ProjectReference
Include="..\iOS.Amap.Foundation\iOS.Amap.Foundation.csproj" />
</ItemGroup>
▌引⼊ MAMapKit.framework
<ItemGroup>
<NativeReference Include="MAMapKit.framework">
<Kind>Framework</Kind>
<ForceLoad>True</ForceLoad>
<SmartLink>True</SmartLink>
<Frameworks>GLKit OpenGLES UIKit Foundation CoreGraphics QuartzCore
CoreLocation CoreTelephony SystemConfiguration Security AdSupport
JavaScriptCore</Frameworks>
<LinkerFlags>-lz -lstdc++ -lc++</LinkerFlags>
</NativeReference>
</ItemGroup>
這個和 AMapFoundationKit.framework 不⼀樣的, 需要新增 Framework 編譯時需要依賴的項, 以及⽤到的編譯⽅式 ,這個和你繫結的 framework 有關, 我這⾥選擇⾼德地圖,所以按照它們的⽂檔要求做了相關設定。
完成的項⽬.csproj 設定為
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-ios</TargetFramework>
<RootNamespace>iOS.Amap._3D</RootNamespace>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IsBindingProject>true</IsBindingProject>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoBindingEmbedding>false</NoBindingEmbedding>
</PropertyGroup>
<ItemGroup>
<ObjcBindingApiDefinition Include="ApiDefinitions.cs" />
<ObjcBindingCoreSource Include="StructsAndEnums.cs" />
</ItemGroup>
<ItemGroup>
<NativeReference Include="MAMapKit.framework">
<Kind>Framework</Kind>
<ForceLoad>True</ForceLoad>
<SmartLink>True</SmartLink>
<Frameworks>GLKit OpenGLES UIKit Foundation CoreGraphics QuartzCore
CoreLocation CoreTelephony SystemConfiguration Security AdSupport
JavaScriptCore</Frameworks>
<LinkerFlags>-lz -lstdc++ -lc++</LinkerFlags>
</NativeReference>
</ItemGroup>
<ItemGroup>
<ProjectReference
Include="..\iOS.Amap.Foundation\iOS.Amap.Foundation.csproj" />
</ItemGroup>
</Project>
編譯 iOS.AMap.3D,你會⽐之前更奔潰,這個時候你需要有⾜夠的耐⼼, 除了和之前差不多的出錯資訊外,還有⼀些新的狀況,我這⾥列舉⼀下
- Type 'MAMapViewDelegate' already defines a member called 'MapView' with the same parameter types
造成這個原因是因為⽅法重名了,這也是 Objective-C 宣告式語法和傳統語法不⼀樣的地⽅,所以你要針對這個做重新命名
如這個
// @optional -(void)mapView:(MAMapView *)mapView didAnnotationViewTapped:
(MAAnnotationView *)view;
[Export ("mapView:didAnnotationViewTapped:")]
void MapView (MAMapView mapView, MAAnnotationView view);
修改為
// @optional -(void)mapView:(MAMapView *)mapView didAnnotationViewTapped:
(MAAnnotationView *)view;
[Export ("mapView:didAnnotationViewTapped:")]
void MapViewDidAnnotationViewTapped (MAMapView mapView, MAAnnotationView
view);
The type or namespace name 'IMAOverlay' could not be found
這個是命名出錯,在 ApiDefinitions.cs ⽂件中你可以找到 MAOverlay[Protocol] interface MAOverlay : IMAAnnotation { // @required -(CLLocationCoordinate2D)coordinate; [Abstract] [Export ("coordinate")] // [Verify (MethodToProperty)] CLLocationCoordinate2D Coordinate { get; } // @required -(MAMapRect)boundingMapRect; [Abstract] [Export ("boundingMapRect")] // [Verify (MethodToProperty)] MAMapRect BoundingMapRect { get; } }
所以把所有 IMAOverlay 替換為 MAOverlay 即可。
- The type or namespace name 'AutoGeneratedName' could not be found
把 AutoGeneratedName 取消
- Constant value '-1' cannot be converted to a 'ulong'
指定型別錯誤 AllCorners = ~0x0 改為 AllCorners = 0x0
Do not know how to make a signature for CoreLocation.CLLocationCoordinate2D in parameter`coordinates'
C# 是沒有指標的,在 Sharpie 轉換時出錯了
- 'MAMapView_UserLocation.HeadingFilter': cannot declare instance members in a static class
// @property (nonatomic) CLLocationDegrees headingFilter;
[Export ("headingFilter")]
double HeadingFilter( { get; set; })
這個定義要換成
// @property (nonatomic) CLLocationDegrees headingFilter;
[Export ("headingFilter")]
double HeadingFilter();
- Cannot convert type 'Foundation.NSObject' to 'nint'
- // @property (nonatomic, weak) id<MAOverlayRenderDelegate>
- rendererDelegate;
- [NullAllowed, Export ("rendererDelegate", ArgumentSemantic.Weak)]
- NSObject WeakRendererDelegate { get; set; }
修改為
// @property (nonatomic, weak) id<MAOverlayRenderDelegate>
rendererDelegate;
[NullAllowed, Export ("rendererDelegate", ArgumentSemantic.Weak)]
IntPtr WeakRendererDelegate { get; set; }
或者排除是⼀個漫⻓的過程,但編譯成功⼀刻你會⾮常興奮,這樣我們就把 AMapFoundationKit.framework和 MAMapKit.framework 繫結成功了。
嘗試建立⼀個 .NET for iOS 項⽬驗證⼀下
- 具體實現請到我的 GitHub Repo 下載 :
https://github.com/kinfey/AMa...
小結
原⽣庫繫結雖然⽐較多繁瑣的事情,但是實際上也是⼗分治癒的,當你看到編譯通過的那⼀刻,你就會明⽩箇中的快樂。還有⼀點,很多⼈認為跨平臺移動開發不需要平臺的基礎知識了,實際還是需要。特別在這種原⽣庫的繫結上,就需要你既會 C# ⼜會 Objective-C 。希望該例⼦能給各位有所啟發。請⼤家期待下⼀篇 Android 原生庫繫結。
相關資源
- 通過 Microsoft Docs 瞭解 MAUI
- 通過 Microsoft Learn 學習 MAUI
- 通過 Microsoft Docs 瞭解
- 通過 Microsoft Learn 學習 MAUI
- 使⽤⾼德地圖 SDK for iOS 請訪問
- 瞭解 iOS 原⽣庫繫結的內容,請訪問
長按識別二維碼
關注微軟中國MSDN
點選瞭解MAUI