Belum lama ini saya memiliki proyek yang menarik pada Formulir Xamarin untuk beberapa platform:
Kami perlu membuat perpustakaan yang dapat terhubung ke beberapa proyek kami: Xamarin.Form, Android di Jawa, Cordova, dan juga memungkinkan pengembang pihak ketiga untuk menggunakan SDK kami dalam proyek mereka dengan upaya minimal untuk integrasi.
Tim memutuskan untuk menulis perpustakaan di C dan menghubungkannya ke proyek kami sesuai kebutuhan. Solusi ini memungkinkan kami untuk memiliki satu basis kode untuk proyek SDK dan kami tidak harus menduplikasi perpustakaan secara terpisah untuk platform yang berbeda dengan kemungkinan masalah ketika porting kode dan tes duplikat untuk menutup dan memverifikasi kode.
Benar, pada akhirnya ternyata cukup sulit untuk “menjalin pertemanan” perpustakaan C dengan platform berbeda di platform Xamarin. Artikel singkat ini akan menjelaskan bagaimana kami berhasil melakukan ini, dan mungkin itu akan berguna bagi seseorang dan akan menghemat waktu pada proyek.
Untuk proyek Xamarin kami, kami membuat paket nuget lain, yang merupakan pembungkus di atas pustaka C kami dan memungkinkan Anda untuk membuat semua perubahan yang diperlukan di satu tempat untuk memperluas SDK, serta memperluas SDK itu sendiri dalam beberapa cara.
Proyek Xamarin kami meliputi empat platform, setiap platform memiliki arsitekturnya sendiri dan pada setiap platform kami perlu membangun pustaka C dalam format file aslinya.
Ekstensi file asli
- Android - * .so file;
- Universal Windows Platform (UWP) - * .dll file;
- iOS - * .a file (file perpustakaan statis, yang sebenarnya adalah file gemuk, yang akan menyimpan semua arsitektur yang kita butuhkan);
- MacOS - * .dylib file (file perpustakaan dinamis)
Kemungkinan arsitektur pada platform yang berbeda
AndroidUWPiOS- armv7
- armv7s
- i386
- x86_64
- arm64
MacOSKita perlu mengumpulkan file asli untuk platform dan arsitektur yang kita butuhkan dalam mode rilis.
Buat dan siapkan file SDK asli
Platform Windows Universal (UWP)
Kami sedang membangun proyek C untuk dua arsitektur x86 dan x64. Setelah itu, kita akan memiliki dua file * .dll yang kita butuhkan.
Android
Untuk membuat file asli untuk proyek Android. Kita perlu membuat proyek Xamarin C ++. Tambahkan file C dan file header kami ke proyek Bersama. Setelah itu, Anda perlu merakit proyek dengan semua arsitektur yang diperlukan (lengan, arm64, x86, x64). Ini akan memberi kita * .so file untuk proyek Android.
iOS
Untuk membuat file asli untuk proyek iOS, kita bisa menggunakan proyek Xamarin C ++ yang sama yang kita gunakan untuk Android, tetapi ada nuansa. Kita perlu terhubung ke MacOS untuk membangun proyek C ++. Tetapi untuk ini kita perlu menginstal vcremote di macOS. Benar, setelah pembaruan terbaru itu tidak mungkin dilakukan sekarang. Mungkin nanti Microsoft akan memperhatikannya dan memperbaiki instalasinya, tetapi sekarang sayangnya tidak demikian.
Karena itu, kita harus pergi ke arah lain. Dalam Xcode, kita perlu membuat proyek Perpustakaan Cocos Touch Static untuk iOS.
Cara melakukan ini, bisa kita baca di sini . Kami menambahkan file kami dari C SDK ke proyek ini dan mengumpulkan proyek dua kali untuk mendapatkan set arsitektur yang kami butuhkan:
- untuk iphone simulator
- untuk iphone
Kemudian kita dapat memeriksa arsitektur mana yang termasuk dalam build kita dari perpustakaan statis menggunakan perintah terminal pada MacOS - "lipo". Misalnya, kita dapat melakukan panggilan seperti itu:
lipo -info /path_to_your_a_file/lib.a
Hasilnya harus seperti ini:
Architectures in the fat file: /path_to_your_a_file/lib.a are : armv7 armv7s i386 x86_64 arm6
Setelah kita menyiapkan file perpustakaan statis, kita dapat menggabungkannya menjadi satu file gemuk, dengan daftar semua arsitektur dalam satu file, sekali lagi menggunakan perintah terminal:
lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a
MacOS
Di MacOS, semuanya akan sangat sederhana. Kita perlu mengonversi file perpustakaan statis menjadi dinamis, sekali lagi menggunakan perintah terminal:
clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib
Dan itu saja. Kami akan mendapatkan file * .dylib yang kami butuhkan.
Paket nuget
Karena kami membuat paket nuget dan menambahkan logika spesifik untuk proyek Xamarin di dalamnya, kami perlu melakukan pembungkus untuk C SDK. Dalam C #, untuk menghubungkan metode C, kita perlu menggunakan atribut DllImport. Tapi di sini lagi ada nuansa. Kita perlu menggunakan const untuk jalur file C asli. Selain itu, setiap proyek akan memiliki jalurnya sendiri ke file tersebut, dan bahkan nama file itu sendiri akan berbeda. Karena itu, kami harus sedikit memperbaiki diri dan menulis pembungkus kami sendiri untuk ini.
Jadi, kelas utama kami yang menjelaskan metode file C.
public abstract class BaseLibraryClass { public abstract int Init (IntPtr value); }
Kemudian untuk setiap platform kita perlu mengimplementasikan kelas abstrak.
Android
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); }
Platform Windows Universal (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); }
Sekarang kita perlu membuat file enum dengan daftar platform / arsitektur:
public enum PlatformArchitecture { Undefined, X86, X64, Droid, Ios, Mac }
Dan sebuah pabrik untuk digunakan di dalam bungkus kami:
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 (); } } }
Kami juga membutuhkan metode Init untuk mengonfigurasi semua yang kami buat di dalam proyek Xamarin kami.
public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; } }
Menghubungkan perpustakaan yang dihasilkan ke proyek
Platform Windows Universal (UWP)
Kami menyalin file perpustakaan yang dihasilkan ke folder:
- lib / x86 / lib.dll
- lib / x64 / lib.dll
Dan kami mengatur arsitektur kami ketika aplikasi dimulai pada metode Init:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;
Android
Untuk proyek Android, kita perlu memperbaiki file * .csproj, menyimpan proyek dan menyalin file * .so ke folder. Dalam proyek Android, kami menunjukkan nama file yang dihasilkan, karena kami meresepkan path file dalam file * .csproj. Kita juga perlu mengingat hal-hal berikut saat menyalin file ke folder:
- armeabi - arm * .so file
- armeabi-v7a - arm * .so file
- arm64-v8a - arm64 * .so file
- x86 - x86 * .so file
- x64 - x64 * .so file
Perubahan untuk * .csproj file:
<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>
Dan instal arsitektur untuk paket nuget:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid;
iOS
Anda perlu menambahkan file * .a fat yang dihasilkan ke folder root proyek dan menginstal instruksi tambahan saat mengkompilasi proyek (properti iOS => iOS build => argumen mtouch tambahan). Instal instruksi berikut:
-gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a"
Juga, jangan lupa untuk menentukan Build Action sebagai None di properti file * .a.
Dan lagi instal arsitektur untuk paket nuget:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios;
MacOS
Tambahkan file * .dylib kami ke proyek Referensi Asli dan tentukan arsitektur yang diinginkan:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;
Setelah manipulasi ini, proyek untuk semua platform kami mengambil file asli yang dihasilkan dan kami dapat menggunakan semua fungsi dari SDK kami di dalam proyek.