Vor nicht allzu langer Zeit hatte ich ein interessantes Projekt zu Xamarin Forms für mehrere Plattformen:
Wir mussten eine Bibliothek erstellen, die eine Verbindung zu mehreren unserer Projekte herstellen konnte: Xamarin.Forms, Android in Java, Cordova, und es Drittentwicklern ermöglichen, unser SDK in ihren Projekten mit minimalem Integrationsaufwand zu verwenden.
Das Team hat beschlossen, eine Bibliothek in C zu schreiben und sie nach Bedarf mit unseren Projekten zu verbinden. Diese Lösung ermöglichte es uns, eine Codebasis für das SDK-Projekt zu haben, und wir mussten die Bibliothek nicht separat für verschiedene Plattformen duplizieren, mit möglichen Problemen beim Portieren von Code und beim Duplizieren von Tests, um den Code abzudecken und zu überprüfen.
Am Ende stellte sich heraus, dass es ziemlich schwierig war, eine C-Bibliothek mit verschiedenen Plattformen auf der Xamarin-Plattform zu „finden“. In diesem kurzen Artikel wird beschrieben, wie wir dies geschafft haben. Vielleicht ist er für jemanden nützlich und spart Zeit für das Projekt.
Für unser Xamarin-Projekt haben wir ein weiteres Nuget-Paket erstellt, das einen Wrapper über unsere C-Bibliothek darstellt und es Ihnen ermöglicht, alle erforderlichen Änderungen an einem Ort vorzunehmen, um das SDK zu erweitern, sowie das SDK selbst auf irgendeine Weise zu erweitern.
Unser Xamarin-Projekt umfasst vier Plattformen, jede Plattform hat ihre eigene Architektur und auf jeder Plattform müssen wir eine C-Bibliothek in ihrem eigenen nativen Dateiformat erstellen.
Native Dateierweiterungen
- Android - * .so Datei;
- Universal Windows Platform (UWP) - * .dll-Datei;
- iOS - * .a Datei (eine statische Bibliotheksdatei, die in der Tat eine fette Datei ist, in der alle benötigten Architekturen gespeichert werden);
- MacOS - * .dylib-Datei (dynamische Bibliotheksdatei)
Mögliche Architekturen auf verschiedenen Plattformen
AndroidUWPiOS- armv7
- armv7s
- i386
- x86_64
- arm64
MacOSWir müssen native Dateien für die Plattformen und Architekturen sammeln, die wir im Release-Modus benötigen.
Erstellen und Vorbereiten nativer SDK-Dateien
Universelle Windows-Plattform (UWP)
Wir erstellen ein C-Projekt für zwei x86- und x64-Architekturen. Danach haben wir zwei * .dll-Dateien, die wir brauchen.
Android
So erstellen Sie native Dateien für ein Android-Projekt. Wir müssen ein Xamarin C ++ - Projekt erstellen. Fügen Sie unsere C-Dateien und Header-Dateien zum freigegebenen Projekt hinzu. Danach müssen Sie ein Projekt mit allen erforderlichen Architekturen (arm, arm64, x86, x64) zusammenstellen. Dadurch erhalten wir * .so-Dateien für das Android-Projekt.
iOS
Um native Dateien für ein iOS-Projekt zu erstellen, können wir dasselbe Xamarin C ++ - Projekt verwenden, das wir für Android verwendet haben, aber es gibt eine Nuance. Wir müssen eine Verbindung zu MacOS herstellen, um ein C ++ - Projekt zu erstellen. Dafür müssen wir vcremote unter macOS installieren. Richtig, nach den letzten Updates ist dies jetzt einfach unmöglich. Vielleicht wird Microsoft später darauf achten und die Installation reparieren, aber jetzt ist dies leider nicht der Fall.
Aus diesem Grund müssen wir den anderen Weg gehen. In Xcode müssen wir ein Cocos Touch Static Library-Projekt für iOS erstellen.
Wie das geht, lesen wir hier . Wir fügen unsere Dateien aus dem C SDK zu diesem Projekt hinzu und stellen das Projekt zweimal zusammen, um die benötigten Architekturen zu erhalten:
- für iPhone Simulator
- für iPhone
Anschließend können wir mit dem Terminalbefehl unter MacOS - „lipo“ - überprüfen, welche Architekturen in unseren Builds der statischen Bibliothek enthalten sind. Zum Beispiel können wir einen solchen Anruf tätigen:
lipo -info /path_to_your_a_file/lib.a
Das Ergebnis sollte folgendermaßen aussehen:
Architectures in the fat file: /path_to_your_a_file/lib.a are : armv7 armv7s i386 x86_64 arm6
Nachdem wir die statischen Bibliotheksdateien vorbereitet haben, können wir sie mit dem Befehl terminal zu einer fetten Datei mit einer Liste aller Architekturen in einer Datei kombinieren:
lipo -create lib_iphone.a lib_iphone_simulator.a -output lib.a
MacOS
Unter MacOS wird alles extrem einfach sein. Wir müssen die statische Bibliotheksdatei wieder mit dem Befehl terminal in eine dynamische konvertieren:
clang -fpic -shared -Wl, -all_load lib.a -o lib.dylib
Und alle. Wir erhalten die * .dylib-Datei, die wir benötigen.
Nuget-Paket
Da wir ein Nuget-Paket erstellt und eine spezifische Logik für das Xamarin-Projekt hinzugefügt haben, mussten wir einen Wrapper für das C SDK erstellen. In C # müssen wir das DllImport-Attribut verwenden, um C-Methoden zu verbinden. Aber auch hier gibt es eine Nuance. Wir müssen const für den Pfad der nativen C-Datei verwenden. Darüber hinaus hat jedes Projekt seinen eigenen Pfad zur Datei, und selbst der Name der Datei selbst ist unterschiedlich. Aus diesem Grund mussten wir uns ein wenig verfeinern und unsere eigenen Wrapper dafür schreiben.
Also unsere Hauptklasse, die C-Dateimethoden beschreibt.
public abstract class BaseLibraryClass { public abstract int Init (IntPtr value); }
Dann müssen wir für jede Plattform eine abstrakte Klasse implementieren.
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); }
Universelle Windows-Plattform (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); }
Jetzt müssen wir eine Aufzählungsdatei mit einer Liste von Plattformen / Architekturen erstellen:
public enum PlatformArchitecture { Undefined, X86, X64, Droid, Ios, Mac }
Und eine Fabrik zur Verwendung in unserer Verpackung:
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 (); } } }
Wir benötigen auch die Init-Methode, um alles zu konfigurieren, was wir in unseren Xamarin-Projekten erstellt haben.
public static class Init { public static PlatformArchitecture PlatformArchitecture { get; set; } }
Generierte Bibliotheken mit Projekten verbinden
Universelle Windows-Plattform (UWP)
Wir kopieren die generierten Bibliotheksdateien in Ordner:
- lib / x86 / lib.dll
- lib / x64 / lib.dll
Und wir richten unsere Architektur ein, wenn die Anwendung mit der Init-Methode gestartet wird:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.X64;
Android
Für ein Android-Projekt müssen wir die * .csproj-Datei reparieren, das Projekt speichern und * .so-Dateien in Ordner kopieren. Im Android-Projekt geben wir den Namen der generierten Datei an, da wir die Dateipfade in der * .csproj-Datei vorschreiben. Beim Kopieren von Dateien in Ordner müssen wir außerdem Folgendes beachten:
- armeabi - arm * .so Datei
- armeabi-v7a - arm * .so Datei
- arm64-v8a - arm64 * .so-Datei
- x86 - x86 * .so-Datei
- x64 - x64 * .so Datei
Änderungen für * .csproj-Datei:
<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>
Und installieren Sie die Architektur für das Nuget-Paket:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Droid;
iOS
Sie müssen die generierte * .a-Fat-Datei zum Stammordner des Projekts hinzufügen und beim Kompilieren des Projekts zusätzliche Anweisungen installieren (iOS-Eigenschaften => iOS-Build => Zusätzliche mtouch-Argumente). Installieren Sie die folgenden Anweisungen:
-gcc_flags "-L${ProjectDir} -llib -force_load ${ProjectDir}/lib.a"
Vergessen Sie auch nicht, die Build-Aktion in den Eigenschaften der Datei * .a als Keine anzugeben.
Installieren Sie erneut die Architektur für das Nuget-Paket:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Ios;
MacOS
Fügen Sie unsere * .dylib-Datei zum Native References-Projekt hinzu und schreiben Sie die gewünschte Architektur vor:
Wrapper.Init.PlatformArchitecture = Wrapper.Enums.PlatformArchitecture.Mac;
Nach diesen Manipulationen haben Projekte für alle unsere Plattformen die generierten nativen Dateien aufgenommen und wir konnten alle Funktionen unserer KFOR innerhalb des Projekts verwenden.