Utiliser les bibliothèques natives de Scala via Java CPP + Java

introduction

Cible inexplorée Nous utilisons Qulacs, qui est un simulateur d'ordinateur quantique écrit principalement en C ++ pour le développement de l'ordinateur quantique gated 2018. Qulacs a déjà des liaisons Python, mais j'ai toujours voulu développer en Scala, qui est également un langage de programmation plus familier. J'ai donc utilisé Java CPP pour faire beaucoup de recherches pour appeler une bibliothèque native comme Qulacs de Scala via Java, et enfin le plug-in sbt. J'ai pu faire.

En fait, il existe un plug-in sbt dans l'organisation GitHub de JavaCPP, mais j'ai découvert que c'était pour faciliter l'utilisation d'OpenCV, etc. qui sont déjà disponibles dans JavaCPP de Scala, afin qu'une nouvelle bibliothèque native puisse être utilisée à partir de Scala. J'ai décidé d'en créer un nouveau cette fois car ce n'était pas quelque chose à faire. Cet article décrit comment utiliser ce plugin sbt et certaines implémentations internes. Si vous avez des questions ou des améliorations après avoir lu cet article, n'hésitez pas à nous le faire savoir dans les commentaires.

Présentation du plugin

Ce plug-in sbt fait trois choses:

  1. Créez une bibliothèque native comme «.so» (ou «.dylib»)
  2. Passez la bibliothèque et le fichier d'en-tête de (1) à JavaCPP pour créer un fichier pour JNI (Java Native Interface).
  3. Compilez (2) avec un système de traitement tel que g ++ pour créer une bibliothèque native utilisable depuis Java.

Tout d'abord, (1) semble être créé différemment selon la bibliothèque (en utilisant make, etc.), donc la commande OS est passée en tant que settingKey de sbt. Et (2) démarre également un nouveau processus Java à partir de sbt. Cela aurait dû être fait avec les modifications appropriées des options passées à la JVM, et aurait pu être réalisé avec les modifications appropriées de la commande run et de javaOptions de sbt, mais c'était un gros effort. Je l'ai fait parce qu'il n'y avait pas de changement. Ensuite, Java CPP fonctionnera (3) et deviendra une bibliothèque de liens dynamiques pouvant être appelée depuis Java.

Paramètres et utilisation

De là, j'expliquerai basé sur Sample Project. Maintenant, le code source C ++ et le fichier d'en-tête existent comme suit.

cpp_src/HelloWorld.hpp


class HelloWorld {
public:
    int printN(int n);
};

cpp_src/HelloWorld.cpp


#include <iostream>
#include <string>
#include "HelloWorld.hpp"

using namespace std;

int HelloWorld::printN(int n) {
    for (int i = 0; i < n; i++) {
        cout << "Hello World!\n";
    }

    return n;
}

Tout d'abord, écrivez les paramètres de compilation dans build.sbt.

build.sbt


includePath := (baseDirectory in Compile).value / "cpp_src"

libraryName := "libHelloWorld"

makeLibraryCommands := Seq(
  gppCompilerPath.value,
  "-I", includePath.value.toString,
  currentLibraryMeta.value.option,
  "-o",
  (libraryDestinationPath.value / s"${libraryName.value}.${currentLibraryMeta.value.extension}").toString,
  ((baseDirectory in Compile).value / "cpp_src" / "HelloWorld.cpp").toString
)

Ici, makeLibraryCommands est la commande du système d'exploitation utilisée pour créer libHelloWorld. Je vais expliquer dans l'ordre. Premièrement, gppCompilerPath utilise clang ++ par défaut. Ensuite, currentLibraryMeta stocke les options de traitement telles que clang ++ par le système d'exploitation, et la structure de donnéesDynamicLibraryMeta pour absorber l'extension de la bibliothèque native générée.

DynamicLibraryMeta.scala


sealed abstract class DynamicLibraryMeta(
  val option: String,
  val extension: String
)

object DynamicLibraryMeta {
  case object Mac extends DynamicLibraryMeta("-dynamiclib", "dylib")
  case object Linux extends DynamicLibraryMeta("-shared", "so")
}

Et currentLibraryMeta est automatiquement sélectionné dans la propriété Java System.getProperty (" os.name ") par défaut. Par défaut, libraryDestinationPath utilise également un répertoire dédié sous le répertoire target.

Ensuite, créez du code Java qui utilise la bibliothèque native.

src/main/java/javacpp/sbt/HelloWorld.java


package javacpp.sbt;

import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

@Platform(include = {"HelloWorld.hpp"}, link = "HelloWorld")
public class HelloWorld extends Pointer {
    static {
        Loader.load();
    }

    public HelloWorld() {
        allocate();
    }

    public native void allocate();

    public native int printN(int n);
}

Revenez à build.sbt et indiquez la bibliothèque native et le chemin de classe Java correspondant.

build.sbt


nativeJavaClassPath := "javacpp.sbt.*"

enablePlugins(SbtJavaCPP4S)

Vous pouvez également utiliser des caractères génériques comme celui-ci. De plus, avec ce plug-in activé, le travail dans build.sbt est terminé.

Enfin, créez le code d'appel de pièce Java à partir de Scala.

src/main/scala/javacpp/HelloWorld.scala


object HelloWorld {
  def main(args: Array[String]): Unit = {
    val instance = new HelloWorld()
    instance.printN(5)
  }
}

Après cela, si vous sbt run ceci, ce sera comme suit.

$ sbt run
[info] Loading settings for project example from build.sbt ...
[info] Set current project to example (in build file:/Users/yyu/Desktop/javacpp-sbt/example/)
[info] Compiling 1 Scala source to /Users/yyu/Desktop/javacpp-sbt/example/target/scala-2.12/classes ...
[info] Success!
[info] running (fork) org.bytedeco.javacpp.tools.Builder -cp /Users/yyu/Desktop/javacpp-sbt/example/target/scala-2.12/classes:/Users/yyu/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/bytedeco/javacpp/1.5.1/javacpp-1.5.1.jar:/Users/yyu/.sbt/boot/scala-2.12.10/lib/scala-library.jar -Dplatform.compiler=clang++ -Dplatform.includepath=/Users/yyu/Desktop/javacpp-sbt/example/cpp_src -Dplatform.linkpath=/Users/yyu/Desktop/javacpp-sbt/example/target/libjni -d /Users/yyu/Desktop/javacpp-sbt/example/target/libjni javacpp.sbt.*mple / Compile / generateJNILibrary 0s
[info] Generating /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jnijavacpp.cpp
[info] Generating /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jniHelloWorld.cpp
[info] Compiling /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/libjniHelloWorld.dylib
[info] clang++ -I/Users/yyu/Desktop/javacpp-sbt/example/cpp_src -I/Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home/include/darwin /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jniHelloWorld.cpp /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jnijavacpp.cpp -march=x86-64 -m64 -O3 -Wl,-rpath,@loader_path/. -Wall -fPIC -dynamiclib -undefined dynamic_lookup -o libjniHelloWorld.dylib -L/Users/yyu/Desktop/javacpp-sbt/example/target/libjni -Wl,-rpath,/Users/yyu/Desktop/javacpp-sbt/example/target/libjni -lHelloWorld -framework JavaVM
[info] Deleting /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jniHelloWorld.cpp
[info] Deleting /Users/yyu/Desktop/javacpp-sbt/example/target/libjni/jnijavacpp.cpp
[info] running (fork) javacpp.HelloWorld
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[success] Total time: 6 s, completed 2019/11/09 4:57:50

Cela fonctionne bien comme ça.

en conclusion

Le petit exemple a fonctionné, donc maintenant je veux pouvoir travailler avec de grandes bibliothèques C ++ comme Qulacs. Ce développement demande beaucoup d'efforts pour la quantité de code. Après tout, j'ai eu beaucoup de problèmes, comme ne pas bien connaître l'erreur native et ne pas connaître les liens dynamiques. En passant, je pense que la complexité des liens dynamiques est l'une des raisons pour lesquelles le langage Go est un seul binaire.

Recommended Posts

Utiliser les bibliothèques natives de Scala via Java CPP + Java
Utilisez Matplotlib depuis Java ou Scala avec Matplotlib4j
Utilisez JDBC avec Java et Scala.
Utiliser Chrome Headless de Selenium / Java
Appelez l'API Java de TensorFlow depuis Scala
Utiliser le type de données PostgreSQL (jsonb) à partir de Java
Bibliothèques Java
Essayez d'appeler Nim depuis Java via JNI
Exécutez du code Java à partir de cpp sur cocos2dx
Exécuter Rust depuis Java avec JNA (Java Native Access)
Créer Scala Seq à partir de Java, faire de Scala Seq une liste Java
Obtenez unixtime (secondes) de ZonedDateTime dans Scala / Java
[Java] Utilisez Collectors.collectingAndThen
Comment écrire Scala du point de vue de Java
Comportement lors de l'appel des méthodes d'argument de longueur variable Java à partir de Scala / Kotlin / Java
6 fonctionnalités que j'ai manquées après mon retour de Scala à Java
J'ai essayé d'appeler du code natif Java / Objective-C depuis Flutter
JAWJAW est pratique si vous utilisez WordNet à partir de Java
Verrou pessimiste sur NW ABAP depuis Java via JCo