El trabajo de Xamarin con el SDK C

No hace mucho tiempo tuve un proyecto interesante sobre Xamarin Forms para varias plataformas:

  • Android
  • iOS
  • UWP
  • MacOS

Necesitábamos crear una biblioteca que pudiera conectarse a varios de nuestros proyectos: Xamarin.Forms, Android en Java, Cordova, y también permitir que los desarrolladores externos utilicen nuestro SDK en sus proyectos con un mínimo esfuerzo de integración.

El equipo decidió escribir una biblioteca en C y conectarla a nuestros proyectos según sea necesario. Esta solución nos permitió tener una base de código para el proyecto SDK y no tuvimos que duplicar la biblioteca por separado para diferentes plataformas con posibles problemas al portar código y duplicar pruebas para cubrir y verificar el código.

Es cierto que al final resultó ser bastante difícil "hacer amigos" una biblioteca C con diferentes plataformas en la plataforma Xamarin. Este breve artículo describirá cómo logramos hacer esto, y tal vez sea útil para alguien y ahorre tiempo en el proyecto.

Para nuestro proyecto Xamarin, creamos otro paquete nuget, que es un contenedor sobre nuestra biblioteca C y le permite realizar todos los cambios necesarios en un lugar para expandir el SDK, así como expandir el SDK de alguna manera.

Nuestro proyecto Xamarin incluye cuatro plataformas, cada plataforma tiene su propia arquitectura y en cada plataforma necesitamos construir una biblioteca C en su propio formato de archivo nativo.

Extensiones de archivo nativas


  • Android - archivo * .so;
  • Plataforma universal de Windows (UWP): archivo * .dll;
  • iOS - * .a archivo (un archivo de una biblioteca estática, que de hecho es un archivo grueso, que almacenará todas las arquitecturas que necesitamos);
  • MacOS - archivo * .dylib (archivo de biblioteca dinámica)

Posibles arquitecturas en diferentes plataformas.


Android

  • brazo
  • arm64
  • x86
  • x64

UWP

  • x86
  • x64

iOS

  • armv7
  • armv7s
  • i386
  • x86_64
  • arm64

MacOS
  • x86_64

Necesitamos recopilar archivos nativos para las plataformas y arquitecturas que necesitamos en el modo de lanzamiento.

Construye y prepara archivos SDK nativos


Plataforma universal de Windows (UWP)


Estamos construyendo un proyecto en C para dos arquitecturas x86 y x64. Después de eso, tendremos dos archivos * .dll que necesitamos.

Android


Para crear archivos nativos para un proyecto de Android. Necesitamos crear un proyecto Xamarin C ++. Agregue nuestros archivos C y archivos de encabezado al proyecto compartido. Después de eso, debe ensamblar un proyecto con todas las arquitecturas necesarias (arm, arm64, x86, x64). Esto nos dará archivos * .so para el proyecto de Android.

iOS


Para crear archivos nativos para un proyecto iOS, podríamos usar el mismo proyecto Xamarin C ++ que usamos para Android, pero hay un matiz. Necesitamos conectarnos a MacOS para construir un proyecto C ++. Pero para esto necesitamos instalar vcremote en macOS. Es cierto que después de las últimas actualizaciones es simplemente imposible hacerlo ahora. Quizás más tarde Microsoft le preste atención y arregle su instalación, pero ahora desafortunadamente este no es el caso.

Debido a esto, tenemos que ir hacia otro lado. En Xcode, necesitamos crear un proyecto de biblioteca estática Cocos Touch para iOS. Cómo hacer esto, podemos leer aquí . Agregamos nuestros archivos del SDK de C a este proyecto y lo ensamblamos dos veces para obtener el conjunto de arquitecturas que necesitamos:

  • para simulador de iphone
  • para iphone

Luego podemos verificar qué arquitecturas están incluidas en nuestras compilaciones de la biblioteca estática usando el comando de terminal en MacOS - "lipo". Por ejemplo, podemos hacer tal llamada:

lipo -info /path_to_your_a_file/lib.a 

El resultado debería ser así:

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

Después de que hayamos preparado los archivos de la biblioteca estática, podemos combinarlos en un archivo grueso, con una lista de todas las arquitecturas en un archivo, nuevamente usando el comando de terminal:

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

MacOS


En MacOS, todo será extremadamente simple. Necesitamos convertir el archivo de biblioteca estática a dinámico, nuevamente usando el comando terminal:

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

Y eso es todo. Obtendremos el archivo * .dylib que necesitamos.

Paquete Nuget


Como creamos un paquete nuget y agregamos una lógica específica para el proyecto Xamarin en él, necesitábamos hacer un contenedor para el SDK de C. En C #, para conectar los métodos de C, necesitamos usar el atributo DllImport. Pero aquí de nuevo hay un matiz. Necesitamos usar const para la ruta del archivo C nativo. Además, cada proyecto tendrá su propia ruta al archivo, e incluso el nombre del archivo será diferente. Debido a esto, tuvimos que refinarnos un poco y escribir nuestros propios envoltorios para esto.

Entonces, nuestra clase principal que describe los métodos de archivo C.

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

Luego, para cada plataforma necesitamos implementar una clase abstracta.

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); } 

Plataforma universal de 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); } 

Ahora necesitamos hacer un archivo enum con una lista de plataformas / arquitecturas:

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

Y una fábrica para usar dentro de nuestra envoltura:

 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 (); } } } 

También necesitamos el método Init para configurar todo lo que creamos dentro de nuestros proyectos de Xamarin.

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

Conectando bibliotecas generadas a proyectos


Plataforma universal de Windows (UWP)


Copiamos los archivos de biblioteca generados a carpetas:

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

Y configuramos nuestra arquitectura cuando la aplicación se inicia en el método Init:

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

Android


Para un proyecto de Android, necesitamos arreglar el archivo * .csproj, guardar el proyecto y copiar archivos * .so a carpetas. En el proyecto de Android, indicamos el nombre del archivo generado, ya que prescribimos las rutas de los archivos en el archivo * .csproj. También debemos recordar lo siguiente al copiar archivos a carpetas:

  • armeabi - arm * .so file
  • armeabi-v7a - archivo arm * .so
  • arm64-v8a - archivo arm64 * .so
  • Archivo x86 - x86 * .so
  • x64 - x64 * .so archivo

Cambios para el archivo * .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> 

E instale la arquitectura para el paquete nuget:

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

iOS


Debe agregar el archivo * .a fat generado a la carpeta raíz del proyecto e instalar instrucciones adicionales al compilar el proyecto (propiedades de iOS => compilación de iOS => argumentos adicionales de mtouch). Instale las siguientes instrucciones:

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

Además, no olvide especificar la Acción de compilación como Ninguna en las propiedades del archivo * .a.

Y nuevamente instale la arquitectura para el paquete nuget:

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

MacOS


Agregue nuestro archivo * .dylib al proyecto Native References y prescriba la arquitectura deseada:

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

Después de estas manipulaciones, los proyectos para todas nuestras plataformas recogieron los archivos nativos generados y pudimos usar todas las funciones de nuestro KFOR dentro del proyecto.

Source: https://habr.com/ru/post/es435210/


All Articles