Lier le code Java et C ++ avec SWIG

À propos de cet article

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

Qu'est-ce que SWIG

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/

Environnement d'exécution à portée de main

Installation SWIG

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écharger

Téléchargement depuis la page de téléchargement du site Web officiel.

Installation

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

échantillon

  1. Hello world Tout d'abord, l'exemple le plus simple. Créez un exemple qui appelle une fonction définie en C ++ à partir de Java. Les trois fichiers suivants sont préparés par vous-même
├── Main.java
├── hello.cpp
└── hello.i

Préparation côté C ++

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.

Préparation côté Java

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 ++.

2. Utiliser la classe

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.

Préparation côté C ++

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

Préparation côté Java

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.

3. utilisez une chaîne

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

Préparation côté C ++

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

Préparation côté Java

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

4. Utiliser le vecteur

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

Préparation côté C ++

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

Préparation côté Java

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

5. Utilisez un vecteur vectoriel

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.

Préparation côté C ++

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

Préparation côté Java

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

Lier le code Java et C ++ avec SWIG
Crypter avec Java et décrypter avec C #
Essayez d'intégrer Ruby et Java avec Dapr
Résolution avec Ruby, Perl et Java AtCoder ABC 128 C
Histoire de remplacement C # et Java
Kotlin post- et pré-incrémentation et surcharge des opérateurs (comparaison avec C, Java, C ++)
Exemple de code pour analyser la date et l'heure avec SimpleDateFormat de Java
Résolution avec Ruby, Perl et Java AtCoder ABC 129 C (Partie 1)
Utiliser java avec MSYS et Cygwin
Traçage distribué avec OpenCensus et Java
Installez Java et Tomcat avec Ansible
Coder Java depuis Emacs avec Eclim
Utilisez JDBC avec Java et Scala.
Hello World avec Docker et langage C
Nouvelles fonctionnalités de Java 9 et exemple de code
Construire Java avec Mac vs Code
Exécuter du code Java packagé avec des commandes
Suivez le lien avec Selenium (Java)
Sortie PDF et TIFF avec Java 8
Préparer l'environnement de développement Java avec VS Code
Surveillez les applications Java avec jolokia et hawtio
noyau java: compilateur HotSpot et tas C
Essayons WebSocket avec Java et javascript!
[Java] Lecture et écriture de fichiers avec OpenCSV
[JaCoCo (Java Code Coverage)] Utilisation avec NetBeans
La direction de Java dans "C ++ Design and Evolution"
Exécutez du code Java à partir de cpp sur cocos2dx
Logique du numéro de page et code de référence (java)
De Java à C et de C à Java dans Android Studio
Résolution avec Ruby, Perl et Java AtCoder ABC 129 C (Partie 2) Méthode de planification dynamique
Comment créer une application avec un mécanisme de plug-in [C # et Java]
Découvrez .NET 5 avec Docker et Visual Studio Code
Exécuter du code Java de manière scriptée
CONSEILS relatifs au code Java
Analyse de code statique par Checkstyle avec Java + Gradle
J'ai essayé d'implémenter Ruby avec Ruby (et C) (j'ai joué avec intégré)
Exemple de code Java 02
Exemple de code Java 03
Créez et testez des applications Java + Gradle avec Wercker
J'ai aussi essayé Web Assembly avec Nim et C
JSON avec Java et Jackson Part 2 XSS mesures
Différences dans l'écriture des classes Java, C # et Javascript
Exemple de code Java 04
Couverture de code Java plus confortable avec Jacoco 0.8.0
En utilisant Gradle avec VSCode, compilez Java → exécutez
Résumer les différences entre l'écriture C # et Java
Java et JavaScript
Briser de force le problème C "* 3 ou / 2" de [AtCoder Problem-ABC100] avec Java [Code]
XXE et Java
Préparer un environnement de scraping avec Docker et Java
Exemple de code Java 01
KMS) Chiffrement d'enveloppe avec décryptage openssl et java
Code de caractère Java
Essayez de déboguer un programme Java avec VS Code
Crypter / décrypter avec AES256 en PHP et Java
[Java] Convertir et importer des valeurs de fichier avec OpenCSV
[Review] Lecture et écriture de fichiers avec java (JDK6)
Créer un environnement de développement Java avec VS Code