Table des matières
Développement multiplateforme mobile et Web avec C ++ expliqué.
Partie 1: Configuration du projet
Code
Le code est dans le part1_setup branche du référentiel.
Bien sûr, nous supposons que vous avez téléchargé et installé les outils, vous avez besoin XCode sur un Mac (j’utilise Appcode mais ce n’est pas gratuit), Android Studio avec Android NDK et Emscripten. Peut-être aussi un IDE pour C ++ comme CLion ou QtCreator (ils soutiennent tous les deux les projets CMake).
J’ai écrit ce tutoriel sur Linux et MacOS, donc les chemins de fichiers et les séparateurs sont de type UNIX, je ne sais pas si cela fonctionne sous Windows, tout commentaire est le bienvenu.
.
├── android
│ ├── app
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── cpp
│ │ │ ├── CMakeLists.txt
│ │ │ ├── common_src -> ../../../../../common/src
│ │ │ ├── deps -> ../../../../../deps
│ │ │ └── native-lib.cpp
│ │ ├── java
│ │ │ └── org
│ │ │ └── example
│ │ │ └── xptuto
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher_round.xml
│ │ │ └── ic_launcher.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ...
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── CMakeLists.txt
├── common
│ └── src
│ └── CMakeLists.txt
├── deps
│ ├── CMakeLists.txt
│ └── djinni
│ ├── CMakeLists.txt
│ ├── LICENSE
│ ├── README.md
│ ├── src
│ │ ...
│ │ └── support
│ │ ├── sbt
│ │ ├── sbt-launch.jar
│ │ ├── sbt.resolvers.properties
│ │ └── sbt.security.policy
│ └── support-lib
│ ├── djinni_common.hpp
│ ├── jni
│ │ ├── djinni_main.cpp
│ │ ├── djinni_support.cpp
│ │ ├── djinni_support.hpp
│ │ └── Marshal.hpp
│ ├── proxy_cache_impl.hpp
│ └── proxy_cache_interface.hpp
├── ios
│ ├── xptuto
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.mm
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── main.mm
│ │ ├── SceneDelegate.h
│ │ ├── SceneDelegate.mm
│ │ ├── ViewController.h
│ │ └── ViewController.mm
│ └── xptuto.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
| ...
├── README.md
├── run_djinni.py
└── web
├── CMakeLists.txt
├── index.html
└── web_glue.cpp
Nous pouvons commencer en créant un projet dans XCode, un autre dans Android studio et un autre pour le web. Ces trois projets sont contenus dans un dossier de niveau supérieur où nous mettons une configuration CMake de niveau supérieur, le code source commun et les dépendances communes.
Notre projet Xcode (dans le sous-dossier ios) est un projet iOS simple, les seules modifications que nous avons apportées ici sont que nous avons défini la norme C ++ (Apple l’appelle «dialecte») sur C ++ 17. Après cela, nous avons renommé tous les .m fichiers vers .mm ils sont donc compilés en Objective-C ++. Il n’y a rien d’autre à configurer maintenant car nous utiliserons directement le code C ++. La seule chose à faire lorsque nous commençons à ajouter du code C ++ est d’ajouter les fichiers à notre cible XCode – à notre application, à savoir. Xcode utilise son propre système de construction qui n’est pas intégré à CMake. Pour cette partie, nous n’avons pas de code C ++, nous pouvons donc nous arrêter ici.
Notre projet Android est plus complexe à mettre en place car Android nécessite JNI pour que Java puisse parler au C ++, nous devons donc ajouter nos bits JNI générés, Java et C ++ à notre build.
Nous devons l’ajouter à notre build.gradle
sourceSets {
main {
java.srcDirs = ['src/main/gen_java', 'src/main/java']
}
}
Ensembles de sources Java dans gradle, répertoire C ++ dans CMake. Nous devons également ajouter les dépendances C ++ à la build déjà: la bibliothèque de support Djinni est utilisée pour implémenter les liaisons JNI. Le répertoire source commun C ++ sera ajouté plus tard car il est vide maintenant.
Android CMakeLists.txt doit inclure les deux sous-dossiers nécessaires: common_src et deps:
# when we have some sources
#add_subdirectory(common_src)
add_subdirectory(deps)
Ceux-ci sont lien symbolique aux dossiers réels au-dessus du projet, de cette façon, le projet Android semble autonome. Les liens symboliques sont bien dans git, je ne sais pas comment ils fonctionnent sur Windows.
Pour Android, nous devons également configurer le générateur JNI, nous utilisons Djinni de Dropbox (et nous l’utilisons _only_ pour Java), le script run_djinni.py a une configuration comme le nom du package, le nom de l’espace de noms… Ceux-ci peuvent être configurés ad hoc.
# Generate
djinni_cmd = ("{base_dir}/deps/djinni/src/run-assume-built "
"--cpp-out {temp_out}/cpp "
"--cpp-namespace {cpp_namespace} "
"--java-out {temp_out}/java "
"--java-package {java_package} "
"--jni-out {temp_out}/jni "
"--idl {in_file} ")
Notre projet Web est assez simple jusqu’à présent, mais nous devons d’abord configurer emscripten. Pour cela, vous pouvez suivre les explications ici: https://emscripten.org/docs/getting_started/downloads.html
Comme Android, la construction se fait avec CMake, donc il créera et liera la source commune lorsque nous la construirons. Nous pouvons coder à partir de l’IDE, nous avons juste besoin de lui dire de sélectionner la chaîne d’outils emscripten, lors du chargement de cmake: ajouter -DCMAKE_TOOLCHAIN_FILE =
Apparemment, QtCreator a également un certain support https://doc.qt.io/qtcreator/creator-setup-webassembly.html mais je n’ai pas passé de temps à l’essayer.
Le code CMake de niveau supérieur ressemble à ceci jusqu’à présent:
cmake_minimum_required(VERSION 3.4.1)
project(xptuto)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)if(EMSCRIPTEN)
include_directories("${EMSCRIPTEN_PREFIX}/system/include")
add_subdirectory(web)
endif(EMSCRIPTEN)# when we have sources
# add_subdirectory(common/src)
Enfin, nous avons également un projet C ++ simple, à construire à partir du répertoire de niveau supérieur, qui pour l’instant ne fera que construire le code commun dans une bibliothèque partagée sur notre ordinateur. Nous l’utiliserons plus tard pour les tests unitaires. Le projet C ++ de niveau supérieur peut être chargé dans n’importe quel IDE qui prend en charge CMake (CLion, QtCreator…). J’utilise CLion car je suis habitué aux produits JetBrains mais il est payant.
Pour vous développer, vous pouvez basculer entre les IDE, selon la plateforme que vous souhaitez cibler ce jour-là.
Sur iOS, si vous ajoutez un fichier C ++ à partir de XCode ou AppCode, il sera ajouté au projet, vous pouvez donc simplement exécuter le projet et les nouveaux fichiers seront compilés. Si vous ajoutez ces fichiers ailleurs, vous aurez besoin pour les ajouter manuellement au projet XCode (clic droit sur common_src, ajouter des fichiers au projet et sélectionner vos nouveaux fichiers). Le débogage se fait comme d’habitude depuis XCode ou Appcode. Le débogueur comprend les objets Objective-C et les objets C ++ sans différence, les exceptions aussi, tout est dans le même contexte.
Les exceptions C ++ sont… des exceptions C ++ et doivent être interceptées avec essayez {} catch {}, pas avec @try {} @catch {}
Vous pouvez le voir sur cette capture d’écran, soi est un UIViewController (une classe Objective-C) utilisateur est un xptuto :: Utilisateur une classe C ++.
Sur Android, vous devrez synchroniser CMake en exécutant Build -> Actualiser les projets C ++, une fois actualisé, l’exécution du projet sur votre cible inclura les nouveaux fichiers, vous devez exécuter cette étape chaque fois qu’un nouveau fichier d’implémentation C ++ est ajouté (un .cpp), pas lorsqu’il est modifié. Les nouvelles liaisons Java JNI sont vues automatiquement par gradle lorsque vous exécutez à nouveau.
Le débogage est un peu plus délicat: Android Studio génère deux débogueurs, un pour Java et un autre pour C ++, ils ont de petites différences d’interface utilisateur et sont côte à côte dans la fenêtre de débogage, vous ne pouvez pas explorer le code Java en code C ++ (étape, par exemple ), vous avez besoin de points d’arrêt aux deux extrémités. Ce n’est pas super pratique mais fonctionne assez bien. Le débogage de Java avec du code natif JNI dans IntelliJ se fait de la même manière, je crois.
Djinni fera exploser les exceptions, mais quand elles franchiront les frontières Java / C ++, elles seront transformées en RuntimeException de base ou en djinni :: jni_exception (selon la direction dans laquelle nous allons) et la pile est perdue. Ainsi, dans votre pile d’exceptions, vous verrez le dernier cadre Java avant le C ++ ou le dernier cadre C ++ avant le Java.
Nous pouvons voir ici les deux onglets du débogueur. Nous voyons maintenant la partie Java.
Sur le Web, vous devez synchroniser CMake, à partir de votre répertoire de construction, exécutez cmake -DCMAKE_TOOLCHAIN_FILE =
Pour fonctionner sur le Web, vous devez démarrer le serveur http python (ou un autre serveur mais notre exemple l’utilise) et charger localhost: 8080 dans votre navigateur.
python2 -m SimpleHTTPServer 8080
Le débogage peut être effectué dans Chrome mais les valeurs des variables ne sont pas visibles, les points d’arrêt fonctionnent. Les exceptions bouillonnent dans Javascript et la pile n’est pas perdue, vous pouvez donc en déduire d’où provient votre exception en lisant les noms des cadres.
N’ajoutez jamais manuellement des fichiers aux CMakeLists ici, ils sont chargés avec des GLOBs (* récursivement). C’est juste la façon dont cet exemple de projet a été configuré.
Nous avons maintenant 3 projets que nous pouvons exécuter et déboguer, qui voient tous notre code commun (qui n’est pas encore là). Nous avons peut-être économisé du temps ou non, mais nous n’avons certainement pas perdu de temps à créer 3 projets avec 3 programmeurs différents.
Nous pouvons maintenant passer à la partie intéressante de la transmission de données.
Suivant: transmettre les données d’avant en arrière ⇨