O trabalho de Xamarin com o C SDK

Há pouco tempo, eu tinha um projeto interessante no Xamarin Forms para várias plataformas:

  • Android
  • iOS
  • UWP
  • MacOS

Precisávamos criar uma biblioteca que pudesse se conectar a vários de nossos projetos: Xamarin.Forms, Android em Java, Cordova e também permitir que desenvolvedores de terceiros usassem nosso SDK em seus projetos com o mínimo esforço de integração.

A equipe decidiu escrever uma biblioteca em C e conectá-la aos nossos projetos, conforme necessário. Essa solução nos permitiu ter uma base de código para o projeto SDK e não tivemos que duplicar a biblioteca separadamente para diferentes plataformas com possíveis problemas ao portar código e duplicar testes para cobrir e verificar o código.

É verdade que, no final, acabou sendo bastante difícil "fazer amigos" uma biblioteca C com plataformas diferentes na plataforma Xamarin. Este pequeno artigo descreverá como conseguimos fazer isso, e talvez seja útil para alguém e economize tempo no projeto.

Para o nosso projeto Xamarin, criamos outro pacote de nuget, que é um invólucro da nossa biblioteca C e permite que você faça todas as alterações necessárias em um único local para expandir o SDK, bem como expandir o próprio SDK de alguma maneira.

Nosso projeto Xamarin inclui quatro plataformas, cada plataforma tem sua própria arquitetura e, em cada plataforma, precisamos criar uma biblioteca C em seu próprio formato de arquivo nativo.

Extensões de arquivo nativas


  • Android - arquivo * .so;
  • Plataforma Universal do Windows (UWP) - arquivo * .dll;
  • arquivo iOS - * .a (um arquivo de biblioteca estática, que na verdade é um arquivo gordo, que armazena todas as arquiteturas necessárias);
  • MacOS - arquivo * .dylib (arquivo de biblioteca dinâmica)

Arquiteturas possíveis em diferentes plataformas


Android

  • braço
  • arm64
  • x86
  • x64

UWP

  • x86
  • x64

iOS

  • armv7
  • armv7s
  • i386
  • x86_64
  • arm64

MacOS
  • x86_64

Precisamos coletar arquivos nativos para as plataformas e arquiteturas necessárias no modo de lançamento.

Construir e preparar arquivos SDK nativos


Plataforma Universal do Windows (UWP)


Estamos construindo um projeto C para duas arquiteturas x86 e x64. Depois disso, teremos dois arquivos * .dll que precisamos.

Android


Para criar arquivos nativos para um projeto Android. Precisamos criar um projeto Xamarin C ++. Adicione nossos arquivos C e arquivos de cabeçalho ao projeto compartilhado. Depois disso, você precisa montar um projeto com todas as arquiteturas necessárias (arm, arm64, x86, x64). Isso nos fornecerá arquivos * .so para o projeto Android.

iOS


Para criar arquivos nativos para um projeto iOS, poderíamos usar o mesmo projeto Xamarin C ++ que usamos para Android, mas há uma nuance. Precisamos nos conectar ao MacOS para criar um projeto C ++. Mas, para isso, precisamos instalar o vcremote no macOS. É verdade que, após as atualizações mais recentes, é simplesmente impossível fazer isso agora. Talvez mais tarde a Microsoft preste atenção e corrija sua instalação, mas agora infelizmente não é esse o caso.

Por isso, temos que seguir o outro caminho. No Xcode, precisamos criar um projeto Cocos Touch Static Library para iOS. Como fazer isso, podemos ler aqui . Adicionamos nossos arquivos do C SDK a este projeto e montamos o projeto duas vezes para obter o conjunto de arquiteturas de que precisamos:

  • para simulador de iphone
  • para iphone

Em seguida, podemos verificar quais arquiteturas estão incluídas em nossas construções da biblioteca estática usando o comando terminal no MacOS - “lipo”. Por exemplo, podemos fazer uma ligação:

lipo -info /path_to_your_a_file/lib.a 

O resultado deve ser assim:

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

Depois de preparar os arquivos da biblioteca estática, podemos combiná-los em um arquivo fat, com uma lista de todas as arquiteturas em um arquivo, novamente usando o comando terminal:

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

MacOS


No MacOS, tudo será extremamente simples. Precisamos converter o arquivo de biblioteca estática em dinâmico, novamente usando o comando terminal:

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

E isso é tudo. Obteremos o arquivo * .dylib necessário.

Pacote de pepitas


Como criamos um pacote nuget e adicionamos lógica específica ao projeto Xamarin, precisamos criar um wrapper para o C SDK. Em C #, para conectar métodos C, precisamos usar o atributo DllImport. Mas aqui novamente há uma nuance. Precisamos usar const para o caminho do arquivo C nativo. Além disso, cada projeto terá seu próprio caminho para o arquivo e até o nome do arquivo será diferente. Por causa disso, tivemos que nos refinar um pouco e escrever nossos próprios invólucros para isso.

Portanto, nossa classe principal que descreve métodos de arquivo C.

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

Então, para cada plataforma, precisamos implementar uma classe abstrata.

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

Agora precisamos criar um arquivo enum com uma lista de plataformas / arquiteturas:

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

E uma fábrica para uso dentro do nosso invólucro:

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

Também precisamos do método Init para configurar tudo o que criamos em nossos projetos Xamarin.

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

Conectando Bibliotecas Geradas a Projetos


Plataforma Universal do Windows (UWP)


Copiamos os arquivos de biblioteca gerados para pastas:

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

E configuramos nossa arquitetura quando o aplicativo inicia no método Init:

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

Android


Para um projeto Android, precisamos corrigir o arquivo * .csproj, salvar o projeto e copiar arquivos * .so em pastas. No projeto Android, indicamos o nome do arquivo gerado, pois prescrevemos os caminhos do arquivo no arquivo * .csproj. Também precisamos lembrar o seguinte ao copiar arquivos para pastas:

  • armeabi - arquivo arm * .so
  • armeabi-v7a - arquivo arm * .so
  • arm64-v8a - arquivo arm64 * .so
  • x86 - x86 * .so arquivo
  • x64 - x64 * .so arquivo

Alterações no arquivo * .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 a arquitetura para o pacote nuget:

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

iOS


É necessário adicionar o arquivo fat * .a gerado à pasta raiz do projeto e instalar instruções adicionais ao compilar o projeto (propriedades do iOS => iOS build => argumentos adicionais do mtouch). Instale as seguintes instruções:

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

Além disso, não esqueça de especificar a Ação de Compilação como Nenhuma nas propriedades do arquivo * .a.

E, novamente, instale a arquitetura do pacote nuget:

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

MacOS


Inclua nosso arquivo * .dylib no projeto Native References e prescreva a arquitetura desejada:

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

Após essas manipulações, os projetos de todas as nossas plataformas capturaram os arquivos nativos gerados e pudemos usar todas as funções do nosso KFOR dentro do projeto.

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


All Articles