Há pouco tempo, eu tinha um projeto interessante no Xamarin Forms para várias plataformas:
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
AndroidUWPiOS- armv7
- armv7s
- i386
- x86_64
- arm64
MacOSPrecisamos 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.