Android avec NDK prend en charge le code C / C ++ et iOS avec Objective-C ++ prend également en charge, alors comment puis-je écrire des applications avec du code C / C ++ natif partagé entre Android et iOS?
java
c++
java-native-interface
cross-platform
objective-c++
ademar111190
la source
la source
Réponses:
Mettre à jour.
Cette réponse est assez populaire même quatre ans après que je l'ai écrite, au cours de ces quatre années, beaucoup de choses ont changé, j'ai donc décidé de mettre à jour ma réponse pour mieux correspondre à notre réalité actuelle. L'idée de réponse ne change pas; la mise en œuvre a un peu changé. Mon anglais a également changé, il s'est beaucoup amélioré, donc la réponse est plus compréhensible pour tout le monde maintenant.
Veuillez jeter un œil au dépôt afin de pouvoir télécharger et exécuter le code que je montrerai ci-dessous.
La réponse
Avant d'afficher le code, veuillez en prendre beaucoup sur le diagramme suivant.
Chaque OS a son interface utilisateur et ses particularités, nous avons donc l'intention d'écrire un code spécifique à chaque plate-forme à cet égard. Dans d'autres mains, nous avons l'intention d'écrire tout le code logique, les règles métier et les choses qui peuvent être partagées en C ++, afin de pouvoir compiler le même code sur chaque plate-forme.
Dans le diagramme, vous pouvez voir la couche C ++ au niveau le plus bas. Tout le code partagé est dans ce segment. Le niveau le plus élevé est le code Obj-C / Java / Kotlin régulier, pas de nouvelles ici, la partie difficile est la couche intermédiaire.
La couche intermédiaire côté iOS est simple; il vous suffit de configurer votre projet pour construire en utilisant une variante d'Obj-c appelée Objective-C ++ et c'est tout, vous avez accès au code C ++.
La chose est devenue plus difficile du côté d'Android, les deux langages, Java et Kotlin, sous Android, fonctionnent sous une machine virtuelle Java. Donc, le seul moyen d'accéder au code C ++ est d'utiliser JNI , prenez le temps de lire les bases de JNI. Heureusement, l'EDI Android Studio actuel présente de vastes améliorations du côté JNI, et de nombreux problèmes vous sont signalés lorsque vous modifiez votre code.
Le code par étapes
Notre exemple est une application simple qui vous permet d'envoyer un texte au CPP, qui convertit ce texte en autre chose et le renvoie. L'idée est, iOS enverra "Obj-C" et Android enverra "Java" à partir de leurs langues respectives, et le code CPP créera un texte comme suit "cpp dit bonjour à << texte reçu >> ".
Code CPP partagé
Tout d'abord, nous allons créer le code CPP partagé, ce faisant, nous avons un simple fichier d'en-tête avec la déclaration de méthode qui reçoit le texte souhaité:
Et la mise en œuvre du RPC:
Unix
Un bonus intéressant est que nous pouvons également utiliser le même code pour Linux et Mac ainsi que pour d'autres systèmes Unix. Cette possibilité est particulièrement utile car nous pouvons tester notre code partagé plus rapidement, nous allons donc créer un Main.cpp comme suit pour l'exécuter depuis notre machine et voir si le code partagé fonctionne.
Pour créer le code, vous devez exécuter:
iOS
Il est temps de mettre en œuvre du côté mobile. Dans la mesure où iOS a une intégration simple, nous commençons par elle. Notre application iOS est une application Obj-c typique avec une seule différence; les fichiers sont
.mm
et non.m
. c'est-à-dire qu'il s'agit d'une application Obj-C ++, pas d'une application Obj-C.Pour une meilleure organisation, nous créons le CoreWrapper.mm comme suit:
Cette classe a la responsabilité de convertir les types et les appels CPP en types et appels Obj-C. Ce n'est pas obligatoire une fois que vous pouvez appeler le code CPP sur n'importe quel fichier que vous voulez sur Obj-C, mais cela aide à conserver l'organisation, et en dehors de vos fichiers wrapper, vous maintenez un code de style Obj-C complet, seul le fichier des wrappers devient CPP style .
Une fois votre wrapper connecté au code CPP, vous pouvez l'utiliser comme code Obj-C standard, par exemple ViewController "
Jetez un œil à l'apparence de l'application:
Android
Il est maintenant temps pour l'intégration Android. Android utilise Gradle comme système de construction et pour le code C / C ++, il utilise CMake. Donc, la première chose à faire est de configurer le CMake sur le fichier gradle:
Et la deuxième étape consiste à ajouter le fichier CMakeLists.txt:
Le fichier CMake est l'endroit où vous devez ajouter les fichiers CPP et les dossiers d'en-tête que vous utiliserez sur le projet, dans notre exemple, nous ajoutons le
CPP
dossier et les fichiers Core.h / .cpp. Pour en savoir plus sur la configuration C / C ++, veuillez le lire.Maintenant, le code principal fait partie de notre application, il est temps de créer le pont, pour rendre les choses plus simples et organisées, nous créons une classe spécifique nommée CoreWrapper pour être notre wrapper entre JVM et CPP:
Notez que cette classe a une
native
méthode et charge une bibliothèque native nomméenative-lib
. Cette bibliothèque est celle que nous créons, à la fin, le code CPP deviendra un objet partagé.so
Fichier intégré dans notre APK, et leloadLibrary
chargera. Enfin, lorsque vous appelez la méthode native, la JVM déléguera l'appel à la bibliothèque chargée.Maintenant, la partie la plus étrange de l'intégration Android est le JNI; Nous avons besoin d'un fichier cpp comme suit, dans notre cas "native-lib.cpp":
La première chose que vous remarquerez est que
extern "C"
cette partie est nécessaire pour que JNI fonctionne correctement avec notre code CPP et nos liens de méthode. Vous verrez également quelques symboles que JNI utilise pour fonctionner avec JVM commeJNIEXPORT
etJNICALL
. Pour que vous compreniez le sens de ces choses, il est nécessaire de prendre un temps et de le lire , pour les besoins de ce tutoriel, considérez simplement ces choses comme passe-partout.Une chose importante et généralement à l'origine de nombreux problèmes est le nom de la méthode; il doit suivre le modèle "Java_package_class_method". Actuellement, le studio Android a un excellent support pour cela afin qu'il puisse générer automatiquement ce passe-partout et vous montrer quand il est correct ou non nommé. Dans notre exemple, notre méthode s'appelle "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" c'est parce que "ademar.androidioscppexample" est notre package, nous remplaçons donc le "." par "_", CoreWrapper est la classe où nous lions la méthode native et "concatenateMyStringWithCppString" est le nom de la méthode lui-même.
Comme nous avons correctement déclaré la méthode, il est temps d'analyser les arguments, le premier paramètre est un pointeur de
JNIEnv
celui-ci est la façon dont nous avons accès aux trucs JNI, il est crucial de faire nos conversions comme vous le verrez bientôt. Le second est unjobject
c'est l'instance de l'objet que vous aviez utilisé pour appeler cette méthode. Vous pouvez le penser comme le java " this ", dans notre exemple, nous n'avons pas besoin de l'utiliser, mais nous devons toujours le déclarer. Après ce jobject, nous allons recevoir les arguments de la méthode. Parce que notre méthode n'a qu'un seul argument - une chaîne "myString", nous n'avons qu'une "jstring" avec le même nom. Notez également que notre type de retour est également un jstring. C'est parce que notre méthode Java renvoie une chaîne, pour plus d'informations sur les types Java / JNI, veuillez la lire.La dernière étape consiste à convertir les types JNI en types que nous utilisons côté CPP. Dans notre exemple, nous transformons le
jstring
en unconst char *
envoi converti en CPP, obtenons le résultat et reconvertissons enjstring
. Comme toutes les autres étapes sur JNI, ce n'est pas difficile; il n'est que passe-partout, tout le travail est fait par l'JNIEnv*
argument que nous recevons lorsque nous appelons leGetStringUTFChars
etNewStringUTF
. Après cela, notre code est prêt à fonctionner sur les appareils Android, jetons un coup d'œil.la source
L'approche décrite dans l'excellente réponse ci-dessus peut être complètement automatisée par Scapix Language Bridge qui génère du code wrapper à la volée directement à partir des en-têtes C ++. Voici un exemple :
Définissez votre classe en C ++:
Et appelez-le de Swift:
Et depuis Java:
la source