Je vais vous présenter comment appeler du code C ++ à partir de Java en utilisant SWIG avec un exemple de code. Dans cet article, j'écrirai de Hello World à la définition de classe et à l'utilisation simple de STL.
Exemple de code: https://github.com/TkrUdagawa/swig_java_sample
Simplified Wrapper and Interface Generator est une abréviation de Simplified Wrapper et Interface Generator, un outil qui génère des wrappers qui peuvent appeler du code écrit en C / C ++ à partir d'autres langages. En plus de Java, les langages de script tels que Python, Ruby et PHP sont également pris en charge.
Page Officielle: http://www.swig.org/
La dernière version au moment de la rédaction est la 4.0.0, qui a été publiée à la fin du mois d'avril 2019. En regardant les notes de version, il semble que le conteneur C ++ 11 STL a été ajouté dans la version 4.0.0, il semble donc bon d'installer la version 4.0.0 même si c'est un peu gênant.
Téléchargement depuis la page de téléchargement du site Web officiel.
Extrayez le fichier téléchargé et installez-le avec configure, make
$ tar xvfz swig-4.0.0.tar.gz
$ cd swig-4.0.0
$ ./configure
$ make
$ make install
├── Main.java
├── hello.cpp
└── hello.i
Tout d'abord, préparez le code C ++ appelé depuis Java.
hello.cpp
#include <iostream>
void hello() {
std::cout << "Hello World!" << std::endl;
}
Ensuite, créez un fichier d'interface SWIG pour l'envelopper
hello.i
%module hello
%{
void hello(); <-①
%}
void hello(); <- ②
% module spécifie le nom du module à créer. Décrivez l'en-tête et la déclaration C / C ++ entre% {%}, puis écrivez ce qui est fourni comme interface lors de l'appel depuis un autre langage. J'ai écrit le même void hello () deux fois, mais si je n'ai pas écrit ①, cela a échoué avec une erreur non déclarée au moment de la compilation, et si j'ai supprimé ②, je n'ai pas trouvé la fonction lorsque Java a été exécuté.
Une fois que vous avez préparé cela, générez le code du wrapper avec la commande swig
.
$ swig -java -c++ -cppext cpp hello.i
La signification de l'argument est de spécifier la langue cible du code source généré par -java
, de permettre le traitement des fonctions C ++ avec -c ++
et d'étendre le code C ++ généré par -cppext cpp
. Il est défini sur «.cpp». Si rien n'est spécifié, l'extension sera cxx.
Lorsque cette commande est exécutée, les trois fichiers suivants sont générés.
Ensuite, compilez le code C ++ pour créer une bibliothèque partagée.
$ g++ -shared -o libhello.so -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -fPIC hello.cpp hello_wrap.cpp
Il est nécessaire de spécifier un chemin avec jni.h
, jni_md.h
, etc. dans le chemin d'inclusion.
Si vous ne le spécifiez pas, vous serez fâché qu'un fichier d'en-tête spécifique ne puisse pas être trouvé en raison d'une erreur de compilation, vous pouvez donc rechercher le fichier avec la commande Locate
etc. et l'ajouter au chemin d'inclusion à chaque fois.
Implémentez du côté Java qui charge la bibliothèque C ++.
Main.java
public class Main {
static {
System.loadLibrary("hello"); // <- C++Charger la bibliothèque
}
public static void main(String[] args) {
hello.hello();
}
}
Compilez et exécutez ceci avec le fichier généré par SWIG. Pour un exemple comme celui-ci, vous pouvez faire quelque chose comme javac * .java
sans penser à rien.
$ javac hello.java helloJNI.java Main.java
$ java Main
Hello World! <- C++Résultat d'exécution de la fonction
J'ai pu imprimer la chaîne en appelant la fonction hello côté C ++.
Créez une classe rectangulaire qui peut calculer la surface en définissant les longueurs verticale et horizontale. La procédure est fondamentalement la même que 1. Dossier à préparer cette fois
├── Main.java
├── square.cpp
├── square.hpp
└── square.i
Cette fois, je vais séparer l'en-tête et la source.
Implémentez la classe SquareC
comme une classe rectangulaire du côté C ++. Le nom du module et le nom de la classe définis dans le fichier d'interface ne doivent pas être dupliqués.
square.hpp
class SquareC {
public:
SquareC(double x, double y);
double area();
private:
double height_;
double width_;
};
square.cpp
#include "square.hpp"
SquareC::SquareC(double x, double y) : height_(x), width_(y) {
}
//Zone de retours
double SquareC::area() {
return height_ * width_;
}
Ensuite, préparez le fichier d'interface.
square.i
%module square
%{
#include "square.hpp" <- ①
%}
%include "square.hpp" <- ②
Comme je l'ai écrit en 1., (1) square.hpp est déclaré, et (2) square.hpp est lu pour la définition de l'interface.
Lorsque vous exécutez la commande swig, quatre fichiers sont générés cette fois. SquareC.java
, qui correspond à la classe définie en C ++, est un fichier qui n'était pas un exemple de Hello World.
$ swig -java -c++ -cppext cpp square.i
Puis compilez de la même manière
$ g++ -shared -o libsquare.so -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -fPIC square.cpp square_wrap.cpp
Chargez la bibliothèque carrée. La classe définie côté C ++ peut être utilisée de la même manière qu'une classe Java normale.
Main.java
public class Main {
static {
System.loadLibrary("square");
}
public static void main(String[] args) {
SquareC s = new SquareC(3.0, 4.0);
System.out.println(s.area());
}
}
Compiler et exécuter
$javac Main.java squareJNI.java SquareC.java square.java
$java Main
12.0
J'ai pu créer une instance de la classe SquareC et appeler sa méthode pour imprimer le résultat en Java.
Un exemple qui utilise std :: string. Faites un exemple en utilisant l'entrée / sortie de chaîne avec un simple setter et getter. Les quatre fichiers suivants sont préparés
├── Main.java
├── person.cpp
├── person.hpp
└── person.i
Créez une classe PersonC avec des membres de nom et d'âge.
person.hpp
#include <string>
class PersonC {
public:
PersonC(const std::string&, int);
const std::string& get_name() const;
void set_name(std::string&);
int get_age() const;
void set_age(int);
private:
std::string name_;
int age_;
};
person.cpp
#include <string>
#include "person.hpp"
PersonC::PersonC(const std::string& name, int age) : name_(name), age_(age) {}
const std::string& PersonC::get_name() const {
return name_;
}
void PersonC::set_name(std::string& name) {
name_ = name;
}
int PersonC::get_age() const {
return age_;
}
void PersonC::set_age(int age) {
age_ = age;
}
Ajoutez un nouveau % include <std_string.i>
au fichier d'interface. Il s'agit d'un fichier d'interface pour la gestion de std :: string
préparé par SWIG à l'avance.
person.i
%module person
%include <std_string.i>
%{
#include "person.hpp"
%}
%include "person.hpp"
Exécutez la commande swig comme précédemment.
$ swig -java -c++ -cppext cpp person.i
Les 5 fichiers suivants sont générés.
Un nouveau fichier appelé SWIGTYPE_p_std__string.java
qui encapsule std :: string a été créé.
Ensuite, créez une bibliothèque C ++.
g++ -shared -o libperson.so -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -fPIC person.cpp person_wrap.cpp
Vous pouvez écrire comme suit en utilisant la classe String sans aucune conscience particulière.
public class Main {
static {
System.loadLibrary("person");
}
public static void main(String[] args) {
String p_name = "Taro";
PersonC p = new PersonC(p_name, 30);
String n = p.get_name();
System.out.println(n);
}
}
$ javac *.java
$ java Main
Taro
Un exemple qui utilise std :: vector pour calculer le produit interne. Vous pouvez facilement utiliser le vecteur en Java en utilisant un modèle dans le fichier d'interface.
├── Main.java
├── inner.cpp
├── inner.hpp
└── inner.i
Du côté C ++, écrivez un programme qui calcule le produit interne des vecteurs doubles sans prêter une attention particulière à quoi que ce soit.
inner.hpp
#include<vector>
double inner_product(const std::vector<double>&, const std::vector<double>&);
inner.cpp
#include "inner.hpp"
double inner_product(const std::vector<double>& a,
const std::vector<double>& b) {
double ret_val = 0;
for (size_t i = 0; i < a.size(); ++i) {
ret_val += a[i] * b[i];
}
return ret_val;
}
Ensuite, écrivez le fichier d'interface.
%include <std_vector.i>
%{
#include "inner.hpp"
%}
%include "inner.hpp"
%template(DVec) std::vector<double>;
Inclut <std_vector.i>
pour utiliser l'interface vectorielle, et une nouvelle ligne appelée % template
apparaît. Comme son nom l'indique, il définit une interface pour encapsuler les modèles C ++, qui permet au langage cible de gérer les objets qui utilisent des modèles C ++. Dans cet exemple, std :: vector <double>
est accessible avec le type DVec
.
Lorsque vous exécutez la commande swig, le fichier suivant est généré.
La source Java correspondant à DVec a été générée. Ensuite, créez une bibliothèque partagée comme auparavant.
$ g++ -shared -o libinner.so -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -fPIC inner.cpp inner_wrap.cpp
Implémentez en utilisant <std :: vector>
défini comme DVec. Si vous regardez dans le DVec.java
généré, vous pouvez voir de quel type d'implémentation il s'agit en Java.
public class DVec extends java.util.AbstractList<Double> implements java.util.RandomAccess {
private transient long swigCPtr;
protected transient boolean swigCMemOwn;
(Ce qui suit est omis)
Vous pouvez voir qu'il est implémenté en héritant de AbstractList. Par conséquent, lors de l'utilisation à partir de Java Vous devez appeler la méthode AbstractList (add, etc.) au lieu de std :: vector.
Main.java
public class Main {
static {
System.loadLibrary("inner");
}
public static void main(String[] args) {
DVec a = new DVec();
a.add(1.0);
a.add(2.0);
a.add(3.0);
DVec b = new DVec();
b.add(3.0);
b.add(4.0);
b.add(5.0);
System.out.println(inner.inner_product(a, b));
}
}
Compilez et exécutez ceci.
$ javac *.java
$ java Main
26.0
Gérez les exemples où les modèles sont imbriqués. Si vous ne prévoyez pas d'utiliser le type intermédiaire du côté de la langue cible, vous ne pouvez écrire que le modèle le plus à l'extérieur dans le fichier d'interface.
Créez un exemple de création et d'affichage d'une matrice 3x3 aléatoire.
Nous allons implémenter la fonction create qui crée une matrice 3x3 et la fonction print_matrix qui l'affiche.
matrix.hpp
#include <vector>
std::vector<std::vector<double>> create();
void print_matrix(const std::vector<std::vector<double>>&);
matrix.cpp
#include <random>
#include <iostream>
#include "matrix.hpp"
std::vector<std::vector<double>> create() {
std::random_device rnd {};
std::vector<std::vector<double>> m {};
for (size_t i = 0; i < 3; ++i) {
std::vector<double> v {};
for (size_t j = 0; j < 3; ++j) {
v.push_back(rnd());
}
m.push_back(v);
}
return m;
}
void print_matrix(const std::vector<std::vector<double>>& m) {
std::cout << "[" << std::endl;
for (const auto& r : m) {
std::cout << " ";
for (const auto& e : r) {
std::cout << e << " " ;
}
std::cout << std::endl;
}
std::cout << "]" << std::endl;
}
Ensuite, créez un fichier d'interface.
matrix.i
%module matrix
%include <std_vector.i>
%{
#include "matrix.hpp"
%}
%include "matrix.hpp"
%template (DMatrix) std::vector<std::vector<double>>;
Écrivez uniquement DMatrix dans le fichier d'interface et essayez de ne rien écrire sur std :: vector
Si vous exécutez la commande swig dans cet état, les fichiers suivants seront créés.
Il semble qu'un fichier correspondant au vecteur double a également été créé.
Créez une bibliothèque partagée comme avant et terminez la préparation côté C ++.
$ g++ -shared -o libmatrix.so -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -fPIC matrix.cpp matrix_wrap.cpp
Implémentez le code côté Java. Créez une variable de la classe DMatrix et lisez la fonction ou accédez à l'élément.
public class Main {
static {
System.loadLibrary("matrix");
}
public static void main(String[] args) {
DMatrix m = matrix.create();
matrix.print_matrix(m);
System.out.println(m.get(0)); <-Essayez d'imprimer l'élément 0 sur m
System.out.println(m); <-Essayez d'imprimer m lui-même
}
}
Si vous faites cela, vous obtiendrez les résultats suivants.
$ javac *.java
$ java Main
[
1.99974e+09 2.96596e+08 1.57757e+09
1.71478e+09 6.51067e+08 2.89146e+09
1.63441e+09 9.24007e+08 2.31229e+09
]
SWIGTYPE_p_std__vectorT_double_t@27c170f0
[SWIGTYPE_p_std__vectorT_double_t@2626b418, SWIGTYPE_p_std__vectorT_double_t@5a07e868, SWIGTYPE_p_std__vectorT_double_t@76ed5528]
Le côté C ++ crée correctement des variables de type DMatrix et print_matrix fonctionne comme prévu. Du côté Java, par contre, seules les informations d'instance sont imprimées et le contenu du vecteur n'est pas imprimé.
SWIGTYPE_p_std__vectorT_double_t est une classe qui semble correspondre à DVec, mais si vous regardez le code source de celui-ci, vous pouvez voir que l'implémentation est clairement différente de celle vue dans l'exemple de 4.
SWIGTYPE_p_std__vectorT_double_t.java
public class SWIGTYPE_p_std__vectorT_double_t {
private transient long swigCPtr;
protected SWIGTYPE_p_std__vectorT_double_t(long cPtr, @SuppressWarnings("unused") boolean futureUse) {
swigCPtr = cPtr;
}
protected SWIGTYPE_p_std__vectorT_double_t() {
swigCPtr = 0;
}
protected static long getCPtr(SWIGTYPE_p_std__vectorT_double_t obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}
}
Vous pouvez voir que cette classe ne fournit qu'une variable membre appelée swigCPtr et son getter.
Si vous déclarez également DVec, qui est un élément de DMatrix, dans le fichier d'interface avec modèle, vous pourrez imprimer le contenu du vecteur depuis Java.
matrix.i
%module matrix
%include <std_vector.i>
%{
#include "matrix.hpp"
%}
%include "matrix.hpp"
%template (DMatrix) std::vector<std::vector<double>>;
%template (DVec) std::vector<double>;
$ java Main
java Main
[
3.37025e+09 3.25125e+09 1.91348e+08
2.32276e+09 2.57749e+09 3.0991e+09
1.9426e+09 2.75113e+09 4.03224e+09
]
[3.370253657E9, 3.251246011E9, 1.91347842E8]
[[3.370253657E9, 3.251246011E9, 1.91347842E8], [2.322762433E9, 2.577487148E9, 3.099102289E9], [1.942601516E9, 2.751128283E9, 4.032242749E9]]
Vous devriez pouvoir gérer les STL pris en charge par SWIG de la même manière.
Cela fait longtemps, donc c'est jusqu'ici.
Recommended Posts