Android con NDK tiene soporte para código C / C ++ e iOS con Objective-C ++ también tiene soporte, entonces, ¿cómo puedo escribir aplicaciones con código nativo C / C ++ compartido entre Android e iOS?
java
c++
java-native-interface
cross-platform
objective-c++
ademar111190
fuente
fuente
Respuestas:
Actualizar.
Esta respuesta es bastante popular incluso cuatro años después de que la escribí, en estos cuatro años muchas cosas han cambiado, así que decidí actualizar mi respuesta para que se ajuste mejor a nuestra realidad actual. La idea de respuesta no cambia; la implementación ha cambiado un poco. Mi inglés también ha cambiado, ha mejorado mucho, por lo que ahora la respuesta es más comprensible para todos.
Eche un vistazo al repositorio para que pueda descargar y ejecutar el código que mostraré a continuación.
La respuesta
Antes de mostrar el código, consulte el siguiente diagrama.
Cada SO tiene su UI y peculiaridades, por lo que pretendemos escribir código específico para cada plataforma en este sentido. En otras manos, todo el código lógico, las reglas comerciales y las cosas que se pueden compartir, pretendemos escribir usando C ++, para que podamos compilar el mismo código para cada plataforma.
En el diagrama, puede ver la capa C ++ en el nivel más bajo. Todo el código compartido está en este segmento. El nivel más alto es el código regular Obj-C / Java / Kotlin, no hay noticias aquí, la parte difícil es la capa intermedia.
La capa intermedia del lado de iOS es simple; solo necesita configurar su proyecto para construir usando una variante de Obj-c conocida como Objective-C ++ y es todo, tiene acceso al código C ++.
La cosa se volvió más difícil en el lado de Android, ambos lenguajes, Java y Kotlin, en Android, se ejecutan bajo una Máquina Virtual Java. Entonces, la única forma de acceder al código C ++ es usando JNI , tómese el tiempo para leer los conceptos básicos de JNI. Afortunadamente, el IDE de Android Studio de hoy tiene grandes mejoras en el lado de JNI, y se le muestran muchos problemas mientras edita su código.
El código por pasos
Nuestra muestra es una aplicación simple que envía un texto a CPP, y convierte ese texto en otra cosa y lo devuelve. La idea es que iOS enviará "Obj-C" y Android enviará "Java" desde sus respectivos idiomas, y el código CPP creará un texto como "cpp saluda a << texto recibido >> ".
Código CPP compartido
En primer lugar vamos a crear el código CPP compartido, para ello tenemos un archivo de encabezado simple con la declaración del método que recibe el texto deseado:
Y la implementación de CPP:
Unix
Una ventaja interesante es que también podemos usar el mismo código para Linux y Mac, así como para otros sistemas Unix. Esta posibilidad es especialmente útil porque podemos probar nuestro código compartido más rápido, por lo que vamos a crear un Main.cpp como sigue para ejecutarlo desde nuestra máquina y ver si el código compartido está funcionando.
Para compilar el código, debe ejecutar:
iOS
Es hora de implementar en el lado móvil. En la medida en que iOS tiene una integración simple, estamos comenzando con ella. Nuestra aplicación para iOS es una aplicación típica de Obj-c con una sola diferencia; los archivos son
.mm
y no.m
. es decir, es una aplicación Obj-C ++, no una aplicación Obj-C.Para una mejor organización, creamos CoreWrapper.mm de la siguiente manera:
Esta clase tiene la responsabilidad de convertir tipos y llamadas CPP a tipos y llamadas Obj-C. No es obligatorio una vez que puede llamar al código CPP en cualquier archivo que desee en Obj-C, pero ayuda a mantener la organización, y fuera de sus archivos de envoltura, mantiene un código completo con estilo de Obj-C, solo el archivo de envolturas se convierte en estilo CPP .
Una vez que su contenedor está conectado al código CPP, puede usarlo como un código Obj-C estándar, por ejemplo, ViewController "
Eche un vistazo a cómo se ve la aplicación:
Androide
Ahora es el momento de la integración con Android. Android usa Gradle como sistema de compilación, y para el código C / C ++ usa CMake. Entonces, lo primero que debemos hacer es configurar el archivo CMake en gradle:
Y el segundo paso es agregar el archivo CMakeLists.txt:
El archivo CMake es donde debe agregar los archivos CPP y las carpetas de encabezado que usará en el proyecto; en nuestro ejemplo, estamos agregando la
CPP
carpeta y los archivos Core.h / .cpp. Para saber más sobre la configuración de C / C ++, léalo.Ahora que el código central es parte de nuestra aplicación, es hora de crear el puente, para hacer las cosas más simples y organizadas, creamos una clase específica llamada CoreWrapper para que sea nuestra envoltura entre JVM y CPP:
Tenga en cuenta que esta clase tiene un
native
método y carga una biblioteca nativa llamadanative-lib
. Esta biblioteca es la que creamos, al final, el código CPP se convertirá en un objeto compartido.so
Archivo incrustado en nuestro APK, yloadLibrary
lo cargará. Finalmente, cuando llame al método nativo, la JVM delegará la llamada a la biblioteca cargada.Ahora, la parte más extraña de la integración de Android es el JNI; Necesitamos un archivo cpp como sigue, en nuestro caso "native-lib.cpp":
Lo primero que notará es que
extern "C"
esta parte es necesaria para que JNI funcione correctamente con nuestro código CPP y enlaces de métodos. También verá algunos símbolos que JNI usa para trabajar con JVM comoJNIEXPORT
yJNICALL
. Para que comprenda el significado de esas cosas, es necesario tomarse un tiempo y leerlo , para los propósitos de este tutorial, simplemente considere estas cosas como un texto estándar.Una cosa importante y generalmente la raíz de muchos problemas es el nombre del método; debe seguir el patrón "Java_package_class_method". Actualmente, Android Studio tiene un excelente soporte para él, por lo que puede generar este modelo automáticamente y mostrarle cuándo es correcto o no tiene nombre. En nuestro ejemplo, nuestro método se llama "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" es porque "ademar.androidioscppexample" es nuestro paquete, así que reemplazamos el "." por "_", CoreWrapper es la clase donde estamos vinculando el método nativo y "concatenateMyStringWithCppString" es el nombre del método en sí.
Como tenemos el método correctamente declarado, es hora de analizar los argumentos, el primer parámetro es un puntero de
JNIEnv
la forma en que tenemos acceso a las cosas de JNI, es crucial que hagamos nuestras conversiones como verá pronto. El segundo es unajobject
instancia del objeto que había utilizado para llamar a este método. Puede pensarlo como el java " this ", en nuestro ejemplo no necesitamos usarlo, pero aún debemos declararlo. Después de este trabajo, vamos a recibir los argumentos del método. Debido a que nuestro método tiene solo un argumento, una cadena "myString", solo tenemos una "jstring" con el mismo nombre. También observe que nuestro tipo de retorno es también jstring. Es porque nuestro método Java devuelve una cadena, para obtener más información sobre los tipos de Java / JNI, léalo.El paso final es convertir los tipos JNI a los tipos que usamos en el lado de CPP. En nuestro ejemplo, estamos transformando el
jstring
en unconst char *
envío convertido a CPP, obteniendo el resultado y volviendo a convertir ajstring
. Como todos los demás pasos de JNI, no es difícil; solo está repetido, todo el trabajo lo realiza elJNIEnv*
argumento que recibimos cuando llamamos alGetStringUTFChars
yNewStringUTF
. Después de que nuestro código esté listo para ejecutarse en dispositivos Android, echemos un vistazo.fuente
El enfoque descrito en la excelente respuesta anterior puede ser completamente automatizado por Scapix Language Bridge, que genera código de envoltura sobre la marcha directamente desde los encabezados de C ++. He aquí un ejemplo :
Defina su clase en C ++:
Y llámalo desde Swift:
Y desde Java:
fuente