BOOSTED JNI
Table des matières
 Présentation et/ou rappel de JNI
 Wrapper C++
 Objectif (quoi)
 Aperçu du code (comment)
 En para...
Durant la présentation…
S’il vous plait
Évitez de poser des questions
…sauf si vous en avez
JNI (Java to Native Interface)
Java
C, C++,
C#,Fortran,
Haskell
(n’importe quel
language qui
supporte la C
calling
convent...
Exemple (Java)
class JniTest
{
/**
Implemented in c++
*/
native boolean nativeMethod(String s);
/**
Called from c++
*/
voi...
Exemple (C++)
JNIEXPORT jboolean JNICALL Java_JniTest_nativeMethod
(JNIEnv* env, jobject obj, jstring s)
{
std::cout << en...
Solutions existantes
 Générateurs de code
 GIWS
 JACE
 Codemesh JunC++ion
 Wrapper + générateur
 android-cpp-sdk
 A...
L’objectif
 Sans générateur de code
 Sans fichier de configuration
 Sans code d’initialisation
 Sans gras trans
myClas...
Plan de match
1. Génération de la signature JNI (“(IF)V”)
2. Interface d’invocation (invoke())
3. Invocation (env->CallSta...
Génération de la signature
 Projeter (mapper) un type en string
jint -> "I"
(type) (string)
 Décomposition du function t...
Projeter un type en string
 Solution: TypeTrait
template<class T>
struct JniTypeTrait;
template<> struct JniTypeTrait<jfl...
Décomposition du fonction type
Des meta-fonctions ??? Une liste de types ???
typedef boost::mpl::at_c<ParameterTypes, 0>::...
Template Metaprogramming
 Effectuer des calculs par à la compilation
 Les calculs se basent sur des types et des
constan...
Exemple: Suite de Fibonacci
template<int n> struct Fibo {
enum {value = Fibo<n-2>::value + Fibo<n-1>::value};
};
template<...
Boost.MPL
// Example #1: vector and push_back
typedef vector<float,double,long double> floats;
typedef push_back<floats,in...
Function Type to JNI Signature
std::string buf;
buf += "(";
buf += ")";
buf += JniTypeTrait<ReturnType>::signature();
[&] ...
Plan de match
2. Interface d’invocation (invoke())
Invocation (interface)
 Rappel de l’enjeux:Vérification compile-time
du nombre et du type des arguments
obj.invoke<void(j...
Invocation (interface)
template<class F>
typename JniMethod<F>::ReturnType invoke(
const char* methodName
{
// ...
}
templ...
Question quiz
 Sachant que boost::mpl::at_c<Seq, n> n’est
valide que pour des valeurs de n comprise entre 0
et boost::mpl...
Réponse: SFINAE
 Substitution Failure Is Not An Error
If an invalid argument or return type is formed during the instanti...
SFINAE (application)
template<class T>
std::size_t size_in_bits(T t,
typename std::enable_if<std::is_arithmetic<T>>::type*...
Plan de match
3. Invocation (env->CallStaticVoidMethod())
Invocation (implementation)
OK, mais ce n’est pas tout!
CallIntMethod()
Et si ReturnType vaut autre
chose que jint ??
auto...
Invocation (variations sur ReturnType)
typedef JniTypeTrait<typename JniMethod<F>::ReturnType> Trait;
((*env_).*Trait::cal...
Invocation (cas de ReturnType=void)
Il reste un problème:
auto r = env_->CallVoidMethod(object_, method, p1);
>> ERROR! au...
Invocation (cas de ReturnType=void)
template<class ReturnType, class ParameterTypes>
struct Invoker
{
ReturnType operator(...
Invocation (paramètres)
template<class... P>
P... params
params...
Variadic templates ?
Variadic templates
template<class Head, class... Tail>
void print(const Head& head, Tail... tail)
{
std::cout << head << "...
Voilà!
 Autres fonctionnalités:
 Gestion des références (locales vs globales)
 Gestion des exceptions
 Scope de foncti...
Questions?
Prochain SlideShare
Chargement dans…5
×

Boosted Java to Native Interface (JNI)

171 vues

Publié le

Récemment, j’ai eu l’occasion de travailler sur un wrapper JNI que l’on utilise dans le projet Lithium pour appeler du Java à partir du C++. Non seulement est-ce que j’ai été satisfait du résultat que ça a donné, mais j’ai eu un plaisir fou à le réaliser. Aujourd’hui, je veux partager ce thrill avec vous.

Publié dans : Logiciels
0 commentaire
0 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Aucun téléchargement
Vues
Nombre de vues
171
Sur SlideShare
0
Issues des intégrations
0
Intégrations
3
Actions
Partages
0
Téléchargements
1
Commentaires
0
J’aime
0
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive
  • Récemment, j’ai eu l’occasion de travailler sur un wrapper JNI que l’on utilise dans le projet Lithium pour appeler du Java à partir du C++. Non seulement est-ce que j’ai été satisfait du résultat que ça a donné, mais j’ai eu un plaisir fou à le réaliser. Aujourd’hui, je veux partager ce thrill avec vous.
  • Cette présentation n’est pas une présentation exhaustive du wrapper JNI. Il s’agit plutôt d’un survol partiel avec emphase sur le “comment ça marche” et un prétexte pour présenter certaines technique moderne de programmation c++ qui sont inconnue à plusieurs.
  • SVP éviter les questions du genre “pourquoi ne pas faire ceci au lieu de cela ?”, qui pourrait faire diverger rapidement la présentation déjà chargée. Je suis disponible pour en parler par après. Tous les commentaires et questions sont d’ailleurs les bienvenus.
  • JNI permet d’appeler du code natif à partir du Java et appeler du code Java à partir du code C++.

    Le premier cas d’utilisation est plus fréquent et plus simple. Il permet à des organisations de rentabiliser un code base natif et l’intégrant à une application moderne Java.

    Le second cas est plus rare (parce qu’en général non requis dans le premier cas d’utilisation) et aussi plus compliqué à cause de l’aspect “managed” de la JVM.
  • La fonction “nativeMethod” est implémentée par du code natif.

    La fonction “javaMethod” est appelée par le code natif.
  • La signature de la fonction est générée par l’outil “javah” qui est inclus avec le JRE.
    Il ne reste plus qu’à remplir le corps de la fonction et compiler. Exemples typiques:
    Accéder le tableau de caractères d’une string Java
    Charger une classe par son nom
    Récupérer la méthode à appeler, identifiée par son nom et par sa signature
    Appeler la méthode
    Mais il y a des bugs dans cet exemple:
    Leak de mémoire de la string UTF8
    Erreur dans la signature
    Erreur dans le nombre et le type des arguments
    Mauvaise fonction d’appel (static + return type)
    Initialization de l’environnement JNI pour les threads
    Passage illégal de l’environnement et des références entre threads
    Gestion d’erreurs
  • La majorité des solutions existantes comporte un générateur de code. C’est bien, mais c’est lourd, surtout dans un petit projet si l’on a qu’un ou deux appels à faire (on se connait: en tant que développeur, on va le faire manuellement plutôt que sortir la grosse machine.)
  • Voici l’objectif:
    Syntaxe succinte, claire et simple
    Pur C++
    Sure (vérification statique) + exception en cas d’erreur runtime

    Sondage:
    Piece of cake ?
    Possible, mais ne sait pas trop comment ?
    What?

    Si vous avez répondu b), cette présentation est pour vous!!
  • Pour cette présentation, je me limite seulement à l’aspect “appel d’une méthode Java en C++”. Il y a d’autres aspects, mais il faut faire des choix.
  • On commence en douceur…

  • On définit une classe “JniTypeTrait” pour laquelle on fournit une spécialisation pour int, float, void, etc.

    La fonction membre signature() permet d’obtenir la string correspondant au type.

    Simple, non ?
  • Ensuite, il faut extraire les types de la signature de la fonction

    Boost.FunctionTypes offre des méta-fonctions qui permettent d’extraire le ReturnType et le type des arguments sont la forme d’une liste de type.

    Boost.MPL permet ensuite d’extraire le type de chaque paramètre à partir de la liste avec la méta-fonction at_c<>
  • Le TMP a été “découvert accidentellement” durant la standardisation du c++. On réalise alors que non seulement on peut faire plein de chose avec les templates, mais qu’on peut faire n’importe quel calcul! (Turing complete-ness).

    Comme le langage n’a pas été concu pour ça, la syntaxe est plus bizarre, mais on s’habitue.

    Il y a 10 ans, les compilateurs suffoquaient à compiler des meta-programme moindrement complexe. Aujourd’hui, ce n’est plus un problème et l’évolution du langage fait de plus en plus de place à la métaprogrammation.
  • Tout le monde connait la suite de Fibonacci, alors on va l’implémenter dans un métaprogramme!

    Cette façon de faire dite “pattern matching” est typique des langages fonctionnels.

    La limitation: les arguments et le résultats sont des constantes à la compilation.
  • Boost.MPL fournit un algorithme, for_each, qui, comme son pendant STL, invoque un functor pour chaque élément d’une séquence. À la différence que la séquence en question est, dans le cas mpl::for_each, une séquence de types.

    Convertir un type en string? Nous avons déjà vu ça! JniTypeTrait ! Trop facile…
  • Troisième et dernière étape de notre périple…
  • Voici la solution pour ReturnType=int. Rien de compliqué

    La fonction checkException() vérifie qui une exception Java a été lancée et convertie celle-ci en exception C++. On se rappele que JNI est une API C et donc ne supporte pas les exceptions nativement. Les exceptions Java sont donc émulées par des attributs de JNIEnv que le programmeur se doit de vérifier.
  • On ramène la class de Trait définit plus tôt et on y ajoute une fonction qui permet d’obtenir la fonction JNI à appeler selon le type de valeur de retour. La fonction est retournée sous la forme d’un pointeur vers une fonction membre (dont un typedef est déclaré pour fin de lisibilité).
  • Impossible, même pour auto, de déclarer une variable de type void (qui, par définition, signifie absence de type)
  • Dans un premier temps, on refactor le corps de la fonction dans une classe “invoker”
    NB: cette définition est complète: il manque le constructeur ainsi que les membres

    Dans un 2e temps, on crée une spécialisation partielle pour ReturnType=void

    Pas de spécialisation partielle des function templates
  • Ici, on peut se permettre d’accepter n’importe quoi parce que la validation a été faite au niveau de invoke()

    Alors, on utilise simplement un variadic template
  • Voici un exemple type d’utilisation de variadic templates

    L’équivalent de la fonction print en Python.

    Il ne faut pas confondre les variadic templates (…) et les VARARGS (…). Les derniers ne sont pas sécuritaires et un héritage du langage C. Les variadic templates, en revanches, sont sécuritaires parce qu’ils n’existent qu’à la compilation.

    À noter:
    Le template parameter pack n’est pas un type! Il n’est pas manipulable au runtime. En fait, comme les templates, il n’existent carrément plus une fois le programme compilé…
    Il est possible d’inclure une expression abitrairement complexe dans l’expansion du pack.
  • Boosted Java to Native Interface (JNI)

    1. 1. BOOSTED JNI
    2. 2. Table des matières  Présentation et/ou rappel de JNI  Wrapper C++  Objectif (quoi)  Aperçu du code (comment)  En parallèle  TypeTrait  Template Specialization (full, partial)  Template meta-programming  SFINAE  Variadic templates
    3. 3. Durant la présentation… S’il vous plait Évitez de poser des questions …sauf si vous en avez
    4. 4. JNI (Java to Native Interface) Java C, C++, C#,Fortran, Haskell (n’importe quel language qui supporte la C calling convention)
    5. 5. Exemple (Java) class JniTest { /** Implemented in c++ */ native boolean nativeMethod(String s); /** Called from c++ */ void javaMethod(int i, float f) { System.out.println("Hello from native!"); } static void main(String args) { new JniTest().nativeMethod("Ni!"); } static { System.loadLibrary("MyNativeLibrary"); } }
    6. 6. Exemple (C++) JNIEXPORT jboolean JNICALL Java_JniTest_nativeMethod (JNIEnv* env, jobject obj, jstring s) { std::cout << env->GetStringUTFChars(s,0) << std::endl; jclass clazz = env->FindClass("JniTest"); jmethodID m = env->GetMethodID(clazz, “javaMethod", "(ID)V"); std::thread([=] { env->CallStaticObjectMethod(clazz, m, 42, "1.0", 3.14); }).detach(); return JNI_TRUE; } Voyez-vous un bug ? Et pourtant, ce code compile sans erreur et sans warning!
    7. 7. Solutions existantes  Générateurs de code  GIWS  JACE  Codemesh JunC++ion  Wrapper + générateur  android-cpp-sdk  Autres (appel de code natif à partir de Java)  SWIG  JNIWrapper
    8. 8. L’objectif  Sans générateur de code  Sans fichier de configuration  Sans code d’initialisation  Sans gras trans myClass.invoke<void(jint, jfloat)>("javaMethod", 42, 1.0); Type de la function Sert à générer la signature JNI ET à valider la liste des arguments Les arguments passés à la fonction Le nombre et les types sont validés à la compilation Le nom de la méthode Java Lance une exception si elle n’existe pas
    9. 9. Plan de match 1. Génération de la signature JNI (“(IF)V”) 2. Interface d’invocation (invoke()) 3. Invocation (env->CallStaticVoidMethod())
    10. 10. Génération de la signature  Projeter (mapper) un type en string jint -> "I" (type) (string)  Décomposition du function type void(jint,jfloat) -> Ret = void P1 = jint P2 = jfloat  Bâtir la signature void(jint, jfloat) -> "(IF)V"
    11. 11. Projeter un type en string  Solution: TypeTrait template<class T> struct JniTypeTrait; template<> struct JniTypeTrait<jfloat> { static const char* signature() {return "F";} }; template<> struct JniTypeTrait<jint> { static const char* signature() {return "I";} }; // Exemple const char* signature_int = JniTypeTrait<jint>::signature(); template<> struct JniTypeTrait<void> { static const char* signature() {return "V";} }; // …ainsi de suite pour les autres types
    12. 12. Décomposition du fonction type Des meta-fonctions ??? Une liste de types ??? typedef boost::mpl::at_c<ParameterTypes, 0>::type P1; typedef boost::mpl::at_c<ParameterTypes, 1>::type P2; typedef boost::function_types::parameter_types<F>::type ParameterTypes; typedef boost::function_types::result_type<F>::type ReturnType; typedef void(F)(jint, jfloat);
    13. 13. Template Metaprogramming  Effectuer des calculs par à la compilation  Les calculs se basent sur des types et des constantes  Le résultat est une constante ou du code  Turing Complete!  Possible d’effectuer n’importe quelle computation  Pas de variable!  Pas de loops. Seulement de la récursion.  Familiarité avec le paradigme fonctionnel
    14. 14. Exemple: Suite de Fibonacci template<int n> struct Fibo { enum {value = Fibo<n-2>::value + Fibo<n-1>::value}; }; template<> struct Fibo<0> { enum {value = 1}; }; template<> struct Fibo<1> { enum {value = 1}; }; // Exemples d’utilisation BOOST_STATIC_ASSERT(Fibo<7>::value == 21); std::cout << Fibo<12>::value << std::endl; int array[Fibo<5>::value];
    15. 15. Boost.MPL // Example #1: vector and push_back typedef vector<float,double,long double> floats; typedef push_back<floats,int>::type types; BOOST_MPL_ASSERT(( is_same< at_c<types,3>::type, int > )); // Example #2: transform typedef vector<char, short, int, long, float, double> types; typedef vector<char*,short*,int*,long*,float*,double*> pointers; typedef transform< types,boost::add_pointer<_1> >::type result; BOOST_MPL_ASSERT(( equal<result, pointers> )); // Example #3: find_if typedef vector<char,int,unsigned,long,unsigned long> types; typedef find_if<types, is_same<_1, unsigned> >::type iter; BOOST_MPL_ASSERT(( is_same< deref<iter>::type, unsigned > )); BOOST_MPL_ASSERT_RELATION( iter::pos::value, ==, 2 );
    16. 16. Function Type to JNI Signature std::string buf; buf += "("; buf += ")"; buf += JniTypeTrait<ReturnType>::signature(); [&] (auto t) { buf += JniTypeTrait<decltype(t)>::signature(); } boost::mpl::for_each<ParameterTypes>( ); NB: Les polymorphic lambdas (avec auto) ont été introduits dans C++14 (qui est actuellement en cours de standardisation). Ils sont disponibles dansVS2013 CTP, GCC 4.9 et Clang 3.4
    17. 17. Plan de match 2. Interface d’invocation (invoke())
    18. 18. Invocation (interface)  Rappel de l’enjeux:Vérification compile-time du nombre et du type des arguments obj.invoke<void(jint, jfloat)>("func", 1); // Error! obj.invoke<void(jint, jfloat)>("func", 1, 2.0, 3); // Error! obj.invoke<void(jint, jfloat)>("func", "1", "2.0"); // Error! obj.invoke<void(jint, jfloat)>("func", 1, 1.0); // OK
    19. 19. Invocation (interface) template<class F> typename JniMethod<F>::ReturnType invoke( const char* methodName { // ... } template<class F> typename JniMethod<F>::ReturnType invoke( const char* methodName, typename boost::mpl::at_c<typename JniMethod<F>::ParameterTypes, 0>::type p1) { // ... } template<class F> typename JniMethod<F>::ReturnType invoke( const char* methodName, typename boost::mpl::at_c<typename JniMethod<F>::ParameterTypes, 0>::type p1, typename boost::mpl::at_c<typename JniMethod<F>::ParameterTypes, 1>::type p2) { // ... }
    20. 20. Question quiz  Sachant que boost::mpl::at_c<Seq, n> n’est valide que pour des valeurs de n comprise entre 0 et boost::mpl::size<Seq>::value,  Comment se peut-il y avoir des overloads de invoke() pour chaque nombre d’arguments sans erreur de compilation ? typedef boost::mpl::vector<int> TypeList; typedef boost::mpl::at_c<TypeList, 2>::type foo; // Error!
    21. 21. Réponse: SFINAE  Substitution Failure Is Not An Error If an invalid argument or return type is formed during the instantiation of a function template, the instantiation is removed from the overload resolution set instead of causing a compilation error. http://boost.org/doc/libs/1_55_0/libs/utility/enable_if.html  Exemple: int negate(int i) { return -i; } template<class F> typename F::result_type negate(const F& f) { return -f(); }
    22. 22. SFINAE (application) template<class T> std::size_t size_in_bits(T t, typename std::enable_if<std::is_arithmetic<T>>::type* dummy = 0) { return 8 * sizeof(T); } template<class T> std::size_t size_in_bits(T t) { return 8 * sizeof(T); } size_in_bits(42); // 32. OK size_in_bits(std::string(“yo”)); // 256. OK, mais...?!? size_in_bits(42); // 32. OK size_in_bits(std::string(“yo”)); // ERROR. Unable to instantiate template...
    23. 23. Plan de match 3. Invocation (env->CallStaticVoidMethod())
    24. 24. Invocation (implementation) OK, mais ce n’est pas tout! CallIntMethod() Et si ReturnType vaut autre chose que jint ?? auto r = env_->CallIntMethod(object_, method, p1); env_.checkException(); return r;
    25. 25. Invocation (variations sur ReturnType) typedef JniTypeTrait<typename JniMethod<F>::ReturnType> Trait; ((*env_).*Trait::callFunction()) // pointer-to-member-function type (to simplify callFunction() declaration) typedef jint (JNIEnv::*CallFunctionType)(jobject, jmethodID, ...); // return the JNIEnv member function to call to return jint static CallFunctionType callFunction() {return &JNIEnv::CallIntMethod;}
    26. 26. Invocation (cas de ReturnType=void) Il reste un problème: auto r = env_->CallVoidMethod(object_, method, p1); >> ERROR! auto cannot deduce type from void
    27. 27. Invocation (cas de ReturnType=void) template<class ReturnType, class ParameterTypes> struct Invoker { ReturnType operator()() { auto r = ((*env_).*CallFunc_)(object_, method_); env_.checkException(); return r; } }; template<class ParameterTypes> struct Invoker<void, ParameterTypes> { void operator()() { env_->CallVoidMethod(object_, method_); env_.checkException(); } }; Et les paramètres dans tout ça ?
    28. 28. Invocation (paramètres) template<class... P> P... params params... Variadic templates ?
    29. 29. Variadic templates template<class Head, class... Tail> void print(const Head& head, Tail... tail) { std::cout << head << " "; print(tail...); } template<class Head> void print(const Head& head) { std::cout << head << std::endl; } // Exemple print(2, "abc", 3.1415); // resultat: // 2 abc 3.1415 Template parameter pack expansion
    30. 30. Voilà!  Autres fonctionnalités:  Gestion des références (locales vs globales)  Gestion des exceptions  Scope de fonction et de thread  Facilitateur pour les Strings  Surveillez les commits dans Lithium (git@git.innobec.com:innobec/lithium.git) obj.invoke<void(jint, jfloat)>("javaMethod", 42, 1.0);
    31. 31. Questions?

    ×