Xamarin与C SDK一起工作

不久前,我在多个平台上的Xamarin Forms上做了一个有趣的项目:

  • 安卓系统
  • 的iOS
  • 超人
  • MacOS的

我们需要创建一个库,该库可以连接到我们的多个项目:Xamarin.Forms,Java中的Android,Cordova,并且还允许第三方开发人员以最小的努力在其项目中使用我们的SDK。

团队决定用C语言编写一个库,并根据需要将其连接到我们的项目。 这一解决方案使我们能够为SDK项目建立一个代码库,并且在移植代码和复制测试以覆盖和验证代码时,我们不必为不同平台分别复制该库,而可能会遇到问题。

没错,最后,在Xamarin平台上“结交”具有不同平台的C库变得非常困难。 这篇简短的文章将描述我们如何做到这一点,也许对某人有用,并且可以节省项目时间。

对于我们的Xamarin项目,我们制作了另一个nuget程序包,该程序包是C库的包装,使您可以在一处进行所有必要的更改以扩展SDK以及以某种方式扩展SDK本身。

我们的Xamarin项目包括四个平台,每个平台都有其自己的体系结构,并且在每个平台上我们都需要以其自己的本机文件格式构建一个C库。

本机文件扩展名


  • Android-* .so文件;
  • 通用Windows平台(UWP)-* .dll文件;
  • iOS-* .a文件(一个静态库文件,实际上是一个胖文件,它将存储我们需要的所有架构);
  • MacOS-* .dylib文件(动态库文件)

不同平台上的可能架构


安卓系统

  • 手臂
  • 臂64
  • x86
  • x64

超人

  • x86
  • x64

的iOS

  • armv7
  • armv7s
  • i386
  • x86_64
  • 臂64

MacOS的
  • x86_64

我们需要收集发布模式下所需的平台和体系结构的本机文件。

生成并准备本机SDK文件


通用Windows平台(UWP)


我们正在为两个x86和x64体系结构构建一个C项目。 之后,我们将需要两个* .dll文件。

安卓系统


为Android项目创建本机文件。 我们需要创建一个Xamarin C ++项目。 将我们的C文件和头文件添加到Shared项目中。 之后,您需要组装一个具有所有必要架构(arm,arm64,x86,x64)的项目。 这将为Android项目提供* .so文件。

的iOS


要为iOS项目创建本机文件,我们可以使用与Android相同的Xamarin C ++项目,但是有细微差别。 我们需要连接到MacOS来构建C ++项目。 但是为此,我们需要在macOS上安装vcremote。 没错,在最新更新之后,现在根本不可能做。 也许以后微软会注意它并修复它的安装,但是不幸的是事实并非如此。

因此,我们必须走另一条路。 在Xcode中,我们需要为iOS创建一个Cocos Touch静态库项目。 如何执行此操作,我们可以在这里阅读 。 我们将来自C SDK的文件添加到该项目中,并对该项目进行两次组装,以获取所需的体系结构集:

  • 适用于iPhone模拟器
  • 对于iPhone

然后,我们可以使用MacOS上的终端命令“ lipo”检查静态库的内部版本中包含哪些体系结构。 例如,我们可以进行这样的调用:

lipo -info /path_to_your_a_file/lib.a 

结果应该是这样的:

 Architectures in the fat file: /path_to_your_a_file/lib.a are : armv7 armv7s i386 x86_64 arm6 

准备好静态库文件后,我们可以将它们组合为一个胖文件,并在一个文件中列出所有体系结构的列表,再次使用terminal命令:

 lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a 

MacOS的


在MacOS上,一切将变得非常简单。 我们需要再次使用terminal命令将静态库文件转换为动态库文件:

 clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib 

仅此而已。 我们将获得所需的* .dylib文件。

Nuget包


由于我们制作了一个nuget包并为其中的Xamarin项目添加了特定的逻辑,因此我们需要为C SDK做一个包装。 在C#中,要连接C方法,我们需要使用DllImport属性。 但是这里又有细微差别。 我们需要使用const作为本机C文件的路径。 此外,每个项目都有自己的文件路径,甚至文件本身的名称也将不同。 因此,我们不得不做些改进,并为此编写自己的包装器。

因此,我们描述C文件方法的主类。

 public abstract class BaseLibraryClass { public abstract int Init (IntPtr value); } 

然后,对于每个平台,我们需要实现一个抽象类。

安卓系统


 internal class BaseLibraryClassDroid : BaseLibraryClass { private const string Path = "lib"; [DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)] private static extern int InitExtern (IntPtr value); public override int Init (IntPtr value) => InitExtern (value); } 

通用Windows平台(UWP)


 internal class BaseLibraryClassx64 : BaseLibraryClass { private const string Path = "lib/x64/lib"; [DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)] private static extern int InitExtern (IntPtr value); public override int Init (IntPtr value) => InitExtern (value); } 

 internal class BaseLibraryClassx86 : BaseLibraryClass { private const string Path = "lib/x86/lib"; [DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)] private static extern int InitExtern (IntPtr value); public override int Init (IntPtr value) => InitExtern (value); } 

的iOS


 internal class BaseLibraryClassIOS : BaseLibraryClass { private const string Path = "__Internal"; [DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)] private static extern int InitExtern (IntPtr value); public override int Init (IntPtr value) => InitExtern (value); } 

MacOS的


 public class BaseLibraryClassMac : BaseLibraryClass { private const string Path = "lib"; [DllImport (Path, EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)] private static extern int InitExtern (IntPtr value); public override int Init (IntPtr value) => InitExtern (value); } 

现在我们需要制作一个包含平台/架构列表的枚举文件:

 public enum PlatformArchitecture { Undefined, X86, X64, Droid, Ios, Mac } 

还有一个在包装器内部使用的工厂:

 public class SdkCoreFactory { public static BaseLibraryClass GetCoreSdkImp () { switch (Init.PlatformArchitecture) { case PlatformArchitecture.Undefined: throw new BaseLibraryClassInitializationException (); case PlatformArchitecture.X86: return new BaseLibraryClassx86 (); case PlatformArchitecture.X64: return new BaseLibraryClassx64 (); case PlatformArchitecture.Droid: return new BaseLibraryClassDroid (); case PlatformArchitecture.Ios: return new BaseLibraryClassIOS (); case PlatformArchitecture.Mac: return new BaseLibraryClassMac (); default: throw new BaseLibraryClassInitializationException (); } } } 

我们还需要Init方法来配置在Xamarin项目中创建的所有内容。

 public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; } } 

将生成的库连接到项目


通用Windows平台(UWP)


我们将生成的库文件复制到文件夹:

  • lib / x86 / lib.dll
  • lib / x64 / lib.dll

当应用程序以Init方法启动时,我们就建立了体系结构:

 Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64; 

安卓系统


对于Android项目,我们需要修复* .csproj文件,保存项目并将* .so文件复制到文件夹中。 在Android项目中,我们指定了生成文件的名称,因为我们在* .csproj文件中指定了文件路径。 将文件复制到文件夹时,我们还需要记住以下几点:

  • armeabi-arm * .so文件
  • armeabi-v7a-arm * .so文件
  • arm64-v8a-arm64 * .so文件
  • x86-x86 * .so文件
  • x64-x64 * .so文件

* .csproj文件的更改:

 <ItemGroup> <AndroidNativeLibrary Include="lib\armeabi\lib.so"> <Abi>armeabi</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\armeabi-v7a\lib.so"> <Abi>armeabi-v7a</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\arm64-v8a\lib.so"> <Abi>arm64-v8a</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\x86\lib.so"> <Abi>x86</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> <AndroidNativeLibrary Include="lib\x86_64\lib.so"> <Abi>x86_64</Abi> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </AndroidNativeLibrary> </ItemGroup> 

并安装nuget软件包的体系结构:

 Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid; 

的iOS


您需要将生成的* .a fat文件添加到项目的根文件夹,并在编译项目时安装其他说明(iOS属性=> iOS构建=>其他mtouch参数)。 安装以下说明:

 -gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a" 

另外,不要忘记在* .a文件的属性中将“生成操作”指定为“无”。

并再次安装nuget软件包的体系结构:

 Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios; 

MacOS的


将我们的* .dylib文件添加到Native References项目中,并指定所需的体系结构:

 Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac; 

经过这些操作后,我们所有平台的项目都拾取了生成的本机文件,并且我们能够在项目内部使用KFOR的所有功能。

Source: https://habr.com/ru/post/zh-CN435210/


All Articles