Il n'y a pas si longtemps, j'avais un projet intéressant sur Xamarin Forms pour plusieurs plateformes:
Nous devions créer une bibliothèque pouvant se connecter à plusieurs de nos projets: Xamarin.Forms, Android en Java, Cordova, et également permettre aux développeurs tiers d'utiliser notre SDK dans leurs projets avec un effort d'intégration minimal.
L'équipe a décidé d'écrire une bibliothèque en C et de la connecter à nos projets selon les besoins. Cette solution nous a permis d'avoir une base de code pour le projet SDK et nous n'avons pas eu à dupliquer la bibliothèque séparément pour différentes plates-formes avec des problèmes possibles lors du portage du code et de la duplication des tests pour couvrir et vérifier le code.
Certes, au final, il s'est avéré assez difficile de «se faire des amis» une bibliothèque C avec différentes plates-formes sur la plate-forme Xamarin. Ce court article décrira comment nous avons réussi à le faire, et peut-être qu'il sera utile à quelqu'un et permettra de gagner du temps sur le projet.
Pour notre projet Xamarin, nous avons créé un autre package nuget, qui est un wrapper sur notre bibliothèque C et vous permet de faire toutes les modifications nécessaires en un seul endroit pour étendre le SDK, ainsi que le SDK lui-même d'une manière ou d'une autre.
Notre projet Xamarin comprend quatre plates-formes, chaque plate-forme a sa propre architecture et sur chaque plate-forme, nous devons construire une bibliothèque C dans son propre format de fichier natif.
Extensions de fichiers natifs
- Android - fichier * .so;
- Plate-forme Windows universelle (UWP) - fichier * .dll;
- iOS - fichier * .a (un fichier d'une bibliothèque statique, qui est en fait un gros fichier, qui stockera toutes les architectures dont nous avons besoin);
- MacOS - fichier * .dylib (fichier de bibliothèque dynamique)
Architectures possibles sur différentes plateformes
AndroidUWPiOS- armv7
- armv7s
- i386
- x86_64
- arm64
MacOSNous devons collecter des fichiers natifs pour les plates-formes et architectures dont nous avons besoin en mode release.
Créez et préparez des fichiers SDK natifs
Plateforme Windows universelle (UWP)
Nous construisons un projet C pour deux architectures x86 et x64. Après cela, nous aurons deux fichiers * .dll dont nous avons besoin.
Android
Pour créer des fichiers natifs pour un projet Android. Nous devons créer un projet Xamarin C ++. Ajoutez nos fichiers C et fichiers d'en-tête au projet partagé. Après cela, vous devez assembler un projet avec toutes les architectures nécessaires (arm, arm64, x86, x64). Cela nous donnera des fichiers * .so pour le projet Android.
iOS
Pour créer des fichiers natifs pour un projet iOS, nous pourrions utiliser le même projet Xamarin C ++ que nous avons utilisé pour Android, mais il y a une nuance. Nous devons nous connecter à MacOS pour créer un projet C ++. Mais pour cela, nous devons installer vcremote sur macOS. Certes, après les dernières mises à jour, il est tout simplement impossible de le faire maintenant. Peut-être que plus tard, Microsoft y fera attention et réparera son installation, mais maintenant ce n'est malheureusement pas le cas.
Pour cette raison, nous devons aller dans l'autre sens. Dans Xcode, nous devons créer un projet de bibliothèque statique Cocos Touch pour iOS.
Comment faire cela, nous pouvons lire ici . Nous ajoutons nos fichiers du SDK C à ce projet et assemblons le projet deux fois pour obtenir l'ensemble des architectures dont nous avons besoin:
- pour simulateur d'iphone
- pour iphone
Ensuite, nous pouvons vérifier quelles architectures sont incluses dans nos versions de la bibliothèque statique en utilisant la commande de terminal sur MacOS - «lipo». Par exemple, nous pouvons faire un tel appel:
lipo -info /path_to_your_a_file/lib.a
Le résultat devrait être comme ceci:
Architectures in the fat file: /path_to_your_a_file/lib.a are : armv7 armv7s i386 x86_64 arm6
Après avoir préparé les fichiers de bibliothèque statiques, nous pouvons les combiner en un seul fichier gras, avec une liste de toutes les architectures dans un fichier, en utilisant à nouveau la commande de terminal:
lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a
MacOS
Sous MacOS, tout sera extrêmement simple. Nous devons convertir le fichier de bibliothèque statique en fichier dynamique, en utilisant à nouveau la commande terminal:
clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib
Et c'est tout. Nous obtiendrons le fichier * .dylib dont nous avons besoin.
Forfait Nuget
Depuis que nous avons créé un package nuget et ajouté une logique spécifique pour le projet Xamarin, nous devions créer un wrapper pour le SDK C. En C #, pour connecter les méthodes C, nous devons utiliser l'attribut DllImport. Mais là encore, il y a une nuance. Nous devons utiliser const pour le chemin du fichier C natif. De plus, chaque projet aura son propre chemin d'accès au fichier, et même le nom du fichier lui-même sera différent. Pour cette raison, nous avons dû nous affiner un peu et écrire nos propres emballages pour cela.
Donc, notre classe principale qui décrit les méthodes du fichier C.
public abstract class BaseLibraryClass { public abstract int Init (IntPtr value); }
Ensuite, pour chaque plate-forme, nous devons implémenter une classe abstraite.
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); }
Plateforme Windows universelle (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); }
Maintenant, nous devons créer un fichier enum avec une liste de plates-formes / architectures:
public enum PlatformArchitecture { Undefined, X86, X64, Droid, Ios, Mac }
Et une usine à utiliser à l'intérieur de notre emballage:
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 (); } } }
Nous avons également besoin de la méthode Init pour configurer tout ce que nous avons créé dans nos projets Xamarin.
public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; } }
Connexion de bibliothèques générées à des projets
Plateforme Windows universelle (UWP)
Nous copions les fichiers de bibliothèque générés dans des dossiers:
- lib / x86 / lib.dll
- lib / x64 / lib.dll
Et nous configurons notre architecture lorsque l'application démarre dans la méthode Init:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;
Android
Pour un projet Android, nous devons corriger le fichier * .csproj, enregistrer le projet et copier les fichiers * .so dans des dossiers. Dans le projet Android, nous indiquons le nom du fichier généré, car nous prescrivons les chemins d'accès au fichier * .csproj. Nous devons également nous souvenir des éléments suivants lors de la copie de fichiers dans des dossiers:
- armeabi - fichier arm * .so
- armeabi-v7a - fichier arm * .so
- arm64-v8a - fichier arm64 * .so
- Fichier x86 - x86 * .so
- Fichier x64 - x64 * .so
Modifications pour le fichier * .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>
Et installez l'architecture du paquet nuget:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid;
iOS
Vous devez ajouter le fichier fat * .a généré dans le dossier racine du projet et installer des instructions supplémentaires lors de la compilation du projet (propriétés iOS => build iOS => arguments mtouch supplémentaires). Installez les instructions suivantes:
-gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a"
N'oubliez pas non plus de spécifier l'action de génération sur Aucun dans les propriétés du fichier * .a.
Et installez à nouveau l'architecture du package nuget:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios;
MacOS
Ajoutez notre fichier * .dylib au projet Native References et prescrivez l'architecture souhaitée:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;
Après ces manipulations, les projets de toutes nos plateformes ont récupéré les fichiers natifs générés et nous avons pu utiliser toutes les fonctions de notre KFOR à l'intérieur du projet.