跟我做⼀个⾼德地图的 iOS / Android MAUI控件(iOS 原生库绑定)

上篇介绍了⼀些做⾼德地图的 iOS / Android MAUI 控件的主要知识。⽽今天我会重点介绍 iOS 原⽣库绑定的知识, 并告诉⼤家在绑定原⽣库过程的⼀些技巧,希望给到⼩伙伴⼀些启发。.

认识 iOS 动态库和静态库

在绑定之前,我们需要学习⼀下 iOS 的动态库和静态库。最简单理解的⽅式是在 iOS 中静态库是以 .a 后缀结尾,动态库是以 .dylib 后缀结尾。⽆论静态库和动态库都可以打包成 Framework 。

01  静态库和动态库的区别

1.静态库的特点是编译时会把库⽂件直接拷⻉⼀份到⽬标应⽤程序,⽽这个拷⻉是驻留在⽬标应⽤程序里面的,所以编译完成后,静态库的⽂件就没有⽤了。但有个缺点就是,因为需要拷⻉,所以⽣成的应⽤程序的容量会较大;

2.动态库和静态库刚好是相反,编译的时候是不会拷⻉到⽬标应⽤程序⾥⾯的,所以⽣成应⽤程序的体积较小,⽽且⼀个动态库可以共享给多个应⽤程序使⽤。但⽣成应⽤程序是依赖于动态库,这也导致经常会出现动态库找不到的情况。

我们来拆解⼀下⾼德地图基础的 SDK - AMapFoundationKit.framework

跟我做⼀个⾼德地图的 iOS / Android MAUI控件(iOS 原生库绑定)

这里就包含了对应的头⽂件信息,模块信息,以及静态库。你可以清晰看到⾼德地图打包成 Framrwork 的实现。这也是我们对库概念的认识,编译好的⼆进制代码,向外暴露头⽂件给第三⽅开发者使用。

Shapie 是⼀个⾮常好⽤的转换⼯具,它⽀持在 macOS 下对 Objective-C 的库的转。通过 Sharpie 可以对库⽂件给出的头⽂件进⾏转换完成 C# 的绑定。在 MAUI 前身 Shapie ⼯具就已经存在 , 我经常就利⽤这个⼯具做转换。

因为这次⾼德地图的功能我⽤到 3D ,所以我会对⾼德的 AMapFoundationKit.Framework 和MAMapKit.framework 两个 Framework 进⾏绑定转换。

02  转换 AMapFoundationKit.Framework
sharpie bind -framework AMapFoundationKit.framework -sdk iphoneos15.5

03  转换 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 的项⽬分别是:

01  针对于 AMapFoundationKit.Framework 的项⽬构建
dotnet new iosbinding -o iOS.AMap.Foundation
02  对于 MAMapKit.framework 的项⽬构建
dotnet new iosbinding -o iOS.AMap.3

⽣成好后,需要把 AMapFoundationKit.framework 放到 iOS.AMap.Foundation 的⽬录下,MAMapKit.framework 放到 iOS.AMap.3D ⽬录下。并把⽣成的 StructsAndEnums.cs 和 ApiDefinitions.cs 放到对应⽬录。

跟我做⼀个⾼德地图的 iOS / Android MAUI控件(iOS 原生库绑定)

项目设置调整
1.在 Sharpie ⽣成的⽬录下 StructsAndEnum.cs ,⽽在构建的 Binding ⽬录下是 ApiDefinition.cs,要把它替换掉。所以要对 .csproj 项⽬进⾏修改。
<ItemGroup><ObjcBindingApiDefinition Include="ApiDefinitions.cs" /><ObjcBindingCoreSource Include="StructsAndEnums.cs" /></ItemGroup>

2. 对 iOS.AMap.Foundation 进⾏编译

01  在 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 做转换时,⼀些转换没做好导致的,这个时候你就需要⼀个⼀个进⾏调整。

跟我做⼀个⾼德地图的 iOS / Android MAUI控件(iOS 原生库绑定)

02  归类一下出错信息

*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 (CLLocationCoordinate2Dcoordinate);[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

1. 对 iOS.AMap.3D 进⾏编译

添加对 iOS.AMap.Foundation的引⽤

因为 MAMapKit.framework 依赖于 AMapFoundationKit.framework , 所以 iOS.AMap.3D 是依赖于

iOS.AMap.Foundation

<ItemGroup><ProjectReferenceInclude="..\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 QuartzCoreCoreLocation CoreTelephony SystemConfiguration Security AdSupportJavaScriptCore</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 QuartzCoreCoreLocation CoreTelephony SystemConfiguration Security AdSupportJavaScriptCore</Frameworks><LinkerFlags>-lz -lstdc++ -lc++</LinkerFlags></NativeReference></ItemGroup><ItemGroup><ProjectReferenceInclude="..\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, MAAnnotationViewview);

*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 项⽬验证⼀下
  1. 在 Sharpie ⽣成的⽬录下 StructsAndEnum.cs ,⽽在构建的 Binding ⽬录下是 ApiDefinition.cs,要把它替换掉。所以要对 .csproj 项⽬进⾏修改。

跟我做⼀个⾼德地图的 iOS / Android MAUI控件(iOS 原生库绑定)

具体实现请到我的 GitHub Repo 下载 :

https://github.com/kinfey/AMapMAUIControls/tree/main/samples/iOS.Bindings/AMap.iOS.Demo

小结

原⽣库绑定虽然⽐较多繁琐的事情,但是实际上也是⼗分治愈的,当你看到编译通过的那⼀刻,你就会明⽩个中的快乐。还有⼀点,很多⼈认为跨平台移动开发不需要平台的基础知识了,实际还是需要。特别在这种原⽣库的绑定上,就需要你既会 C# ⼜会Objective-C 。希望该例⼦能给各位有所启发。请⼤家期待下⼀篇。