不久前,我在多个平台上的Xamarin Forms上做了一个有趣的项目:
我们需要创建一个库,该库可以连接到我们的多个项目: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文件(动态库文件)
不同平台上的可能架构
安卓系统超人的iOSMacOS的我们需要收集发布模式下所需的平台和体系结构的本机文件。
生成并准备本机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的文件添加到该项目中,并对该项目进行两次组装,以获取所需的体系结构集:
然后,我们可以使用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的所有功能。