Android com NDK tem suporte para código C / C ++ e iOS com Objective-C ++ também, então como posso escrever aplicativos com código C / C ++ nativo compartilhado entre Android e iOS?
java
c++
java-native-interface
cross-platform
objective-c++
ademar111190
fonte
fonte
Respostas:
Atualizar.
Esta resposta é bastante popular até quatro anos depois de escrevê-la, nesses quatro anos muitas coisas mudaram, então decidi atualizar minha resposta para se adequar melhor à nossa realidade atual. A ideia da resposta não muda; a implementação mudou um pouco. Meu inglês também mudou, melhorou muito, então a resposta é mais compreensível para todos agora.
Por favor, dê uma olhada no repo para que você possa baixar e executar o código que mostrarei abaixo.
A resposta
Antes de mostrar o código, analise muito o diagrama a seguir.
Cada SO possui sua IU e peculiaridades, portanto pretendemos escrever um código específico para cada plataforma a este respeito. Em outras mãos, todos os códigos lógicos, regras de negócios e coisas que podem ser compartilhadas pretendemos escrever usando C ++, para que possamos compilar o mesmo código para cada plataforma.
No diagrama, você pode ver a camada C ++ no nível mais baixo. Todo o código compartilhado está neste segmento. O nível mais alto é o código Obj-C / Java / Kotlin regular, nenhuma notícia aqui, a parte difícil é a camada intermediária.
A camada intermediária para o lado do iOS é simples; você só precisa configurar seu projeto para construir usando uma variante do Obj-c conhecida como Objective-C ++ e é tudo, você tem acesso ao código C ++.
A coisa ficou mais difícil no lado do Android, ambas as linguagens, Java e Kotlin, no Android, rodam em uma máquina virtual Java. Portanto, a única maneira de acessar o código C ++ é usando JNI , reserve um tempo para ler os fundamentos de JNI. Felizmente, o Android Studio IDE de hoje tem grandes melhorias no lado JNI, e muitos problemas são mostrados a você enquanto edita seu código.
O código por etapas
Nosso exemplo é um aplicativo simples que envia um texto para o CPP, ele converte esse texto em outra coisa e o retorna. A ideia é que o iOS enviará "Obj-C" e o Android enviará "Java" de suas respectivas línguas, e o código CPP criará um texto como "cpp diz olá para << texto recebido >> ".
Código CPP compartilhado
Em primeiro lugar, vamos criar o código CPP compartilhado, fazendo isso temos um arquivo de cabeçalho simples com a declaração do método que recebe o texto desejado:
E a implementação do CPP:
Unix
Um bônus interessante é que também podemos usar o mesmo código para Linux e Mac, bem como outros sistemas Unix. Essa possibilidade é especialmente útil porque podemos testar nosso código compartilhado mais rápido, então vamos criar um Main.cpp como segue para executá-lo em nossa máquina e ver se o código compartilhado está funcionando.
Para construir o código, você precisa executar:
iOS
É hora de implementar no lado móvel. Já que o iOS tem uma integração simples, estamos começando com ele. Nosso aplicativo iOS é um aplicativo Obj-c típico com apenas uma diferença; os arquivos são
.mm
e não.m
. ou seja, é um aplicativo Obj-C ++, não um aplicativo Obj-C.Para uma melhor organização, criamos o CoreWrapper.mm da seguinte forma:
Esta classe tem a responsabilidade de converter tipos e chamadas CPP em tipos e chamadas Obj-C. Não é obrigatório, uma vez que você pode chamar o código CPP em qualquer arquivo que desejar no Obj-C, mas ajuda a manter a organização, e fora de seus arquivos de invólucro, você mantém um código completo no estilo Obj-C, apenas o arquivo de invólucro torna-se no estilo CPP .
Depois que seu wrapper estiver conectado ao código CPP, você pode usá-lo como um código Obj-C padrão, por exemplo, ViewController "
Dê uma olhada na aparência do aplicativo:
Android
Agora é hora de integração com o Android. O Android usa o Gradle como sistema de compilação e, para o código C / C ++, usa o CMake. Portanto, a primeira coisa que precisamos fazer é configurar o CMake no arquivo gradle:
E a segunda etapa é adicionar o arquivo CMakeLists.txt:
O arquivo CMake é onde você precisa adicionar os arquivos CPP e pastas de cabeçalho que você usará no projeto, em nosso exemplo, estamos adicionando a
CPP
pasta e os arquivos Core.h / .cpp. Para saber mais sobre a configuração C / C ++, leia.Agora que o código principal é parte do nosso aplicativo, é hora de criar a ponte, para tornar as coisas mais simples e organizadas, criamos uma classe específica chamada CoreWrapper para ser nosso wrapper entre JVM e CPP:
Observe que esta classe possui um
native
método e carrega uma biblioteca nativa chamadanative-lib
. Esta biblioteca é a que criamos, no final, o código CPP se tornará um objeto compartilhado.so
File embed em nosso APK, e oloadLibrary
carregará. Finalmente, ao chamar o método nativo, a JVM delegará a chamada à biblioteca carregada.Agora, a parte mais estranha da integração do Android é o JNI; Precisamos de um arquivo cpp da seguinte forma, em nosso caso "native-lib.cpp":
A primeira coisa que você notará é que
extern "C"
esta parte é necessária para que o JNI funcione corretamente com nosso código CPP e ligações de método. Você também verá alguns símbolos que o JNI usa para trabalhar com JVM comoJNIEXPORT
eJNICALL
. Para você entender o significado dessas coisas, é necessário reservar um tempo e lê-lo , para os fins deste tutorial apenas considere essas coisas como clichês.Uma coisa significativa e geralmente a raiz de muitos problemas é o nome do método; ele precisa seguir o padrão "Java_package_class_method". Atualmente, o Android Studio tem um excelente suporte para ele, de modo que pode gerar esse boilerplate automaticamente e mostrar quando ele está correto ou não nomeado. Em nosso exemplo, nosso método é denominado "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" porque "ademar.androidioscppexample" é nosso pacote, então substituímos "." por "_", CoreWrapper é a classe onde estamos vinculando o método nativo e "concatenateMyStringWithCppString" é o próprio nome do método.
Como temos o método declarado corretamente é hora de analisar os argumentos, o primeiro parâmetro é um ponteiro
JNIEnv
dele é a forma como temos acesso ao material JNI, é fundamental que façamos nossas conversões como você verá em breve. O segundo éjobject
a instância do objeto que você usou para chamar esse método. Você pode pensar nisso como o java " this ", em nosso exemplo não precisamos usá-lo, mas ainda precisamos declará-lo. Após este jobject, iremos receber os argumentos do método. Como nosso método tem apenas um argumento - uma String "myString", temos apenas uma "jstring" com o mesmo nome. Observe também que nosso tipo de retorno também é jstring. É porque nosso método Java retorna uma String, para obter mais informações sobre os tipos Java / JNI, leia.A etapa final é converter os tipos JNI para os tipos que usamos no lado do CPP. Em nosso exemplo, estamos transformando o
jstring
em umconst char *
enviando-o convertido em CPP, obtendo o resultado e convertendo de volta parajstring
. Como todas as outras etapas do JNI, não é difícil; é apenas boilerplated, todo o trabalho é feito peloJNIEnv*
argumento que recebemos quando chamamos oGetStringUTFChars
eNewStringUTF
. Depois que nosso código estiver pronto para ser executado em dispositivos Android, vamos dar uma olhada.fonte
A abordagem descrita na excelente resposta acima pode ser completamente automatizada pelo Scapix Language Bridge, que gera o código do wrapper em tempo real diretamente dos cabeçalhos C ++. Aqui está um exemplo :
Defina sua classe em C ++:
E chame de Swift:
E de Java:
fonte