I will introduce how to call C ++ code from Java using SWIG with sample code. In this article, I will write from hello world to class definition and simple STL usage.
Sample code: https://github.com/TkrUdagawa/swig_java_sample
An abbreviation for Simplified Wrapper and Interface Generator, a tool that generates wrappers that can call code written in C / C ++ from other languages. In addition to Java, scripting languages such as Python, Ruby, and PHP are also supported.
Official page: http://www.swig.org/
The latest version at the time of writing is 4.0.0, which was released at the end of April 2019. Looking at the release notes, it seems that C ++ 11 STL container is added in 4.0.0, so it seems good to install 4.0.0 even if it is a little troublesome.
Download from Download page of official website.
Extract the downloaded file and install it with 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
First, prepare the C ++ code that is called from Java.
hello.cpp
#include <iostream>
void hello() {
std::cout << "Hello World!" << std::endl;
}
Then create a SWIG interface file to wrap it
hello.i
%module hello
%{
void hello(); <-①
%}
void hello(); <- ②
In% module, specify the name of the module to be created. Describe the C / C ++ header and declaration between% {%}, and finally write what is provided as an interface when calling from another language. I wrote the same void hello () twice, but if I didn't write ①, it failed with an undeclared error at compile time, and if I deleted ②, I couldn't find the function when Java was executed.
Once you have prepared this, generate the wrapper code with the swig
command.
$ swig -java -c++ -cppext cpp hello.i
The meaning of the argument is to specify the target language of the source code generated by -java
, to enable processing of C ++ functions with-c ++
, and to extend the C ++ code generated by -cppext cpp
. It is set to .cpp
. If nothing is specified, the extension will be cxx.
When this command is executed, the following three files are generated.
Then compile the C ++ code to create a shared library.
$ 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
It is necessary to specify a path with jni.h
, jni_md.h
, etc. in the include path.
If you do not specify it, you will be angry that a specific header file cannot be found due to a compile error, so you can search for the file with the locate
command etc. and add it to the include path each time.
Implement on the Java side that loads the C ++ library.
Main.java
public class Main {
static {
System.loadLibrary("hello"); // <- C++Loading the library
}
public static void main(String[] args) {
hello.hello();
}
}
Compile and run this with the SWIG-generated files. In this case, you can do something like javac * .java
without thinking about anything.
$ javac hello.java helloJNI.java Main.java
$ java Main
Hello World! <- C++Function execution result of
I was able to print the string by calling the hello function on the C ++ side.
Create a rectangle class that can calculate the area by setting the length and width. The procedure is basically the same as 1. File to prepare this time
├── Main.java
├── square.cpp
├── square.hpp
└── square.i
This time I will separate the header and the source.
Implement the SquareC
class as a rectangular class on the C ++ side. The module name and class name defined in the interface file must not be duplicated.
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) {
}
//Returns the area
double SquareC::area() {
return height_ * width_;
}
Next, prepare the interface file.
square.i
%module square
%{
#include "square.hpp" <- ①
%}
%include "square.hpp" <- ②
As I wrote in 1., (1) square.hpp is declared, and (2) square.hpp is read for interface definition.
Executing the swig command will generate four files this time. SquareC.java
, which corresponds to the class defined in C ++, is a file that was not an example of Hello World.
$ swig -java -c++ -cppext cpp square.i
Then compile in the same way
$ 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
Load the square library. The class defined on the C ++ side can be used in the same way as a normal Java class.
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());
}
}
Compile and run
$javac Main.java squareJNI.java SquareC.java square.java
$java Main
12.0
I was able to create an instance of the SquareC class and call its method to print the result in Java.
Sample using std :: string. Make an example using string I / O with a simple setter and getter. The following four files are prepared
├── Main.java
├── person.cpp
├── person.hpp
└── person.i
Create a PersonC class with name and age members.
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;
}
Add a new % include <std_string.i>
to the interface file. This is an interface file for handling std :: string
prepared by SWIG in advance.
person.i
%module person
%include <std_string.i>
%{
#include "person.hpp"
%}
%include "person.hpp"
Execute the swig command as before.
$ swig -java -c++ -cppext cpp person.i
The following 5 files are generated.
A new file called SWIGTYPE_p_std__string.java
that wraps std :: string has been created.
Next, create a C ++ library.
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
You can write as follows using String class without any particular consciousness.
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
A sample that calculates the inner product using std :: vector. If you use template in the interface file, you can easily use vector in Java.
├── Main.java
├── inner.cpp
├── inner.hpp
└── inner.i
On the C ++ side, write a program that calculates the inner product of double vectors without paying special attention to anything.
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;
}
Next, write the interface file.
%include <std_vector.i>
%{
#include "inner.hpp"
%}
%include "inner.hpp"
%template(DVec) std::vector<double>;
Includes <std_vector.i>
to use the vector interface, and a new line called % template
appears. As the name implies, it defines an interface for wrapping C ++ templates, which makes it possible to handle objects using C ++ templates even in the target language. In this example, std :: vector <double>
can be accessed with the type DVec
.
When you execute the swig command, the following file is generated.
Java source corresponding to DVec has been generated. Then, create a shared library as before.
$ 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
Implement using <std :: vector>
defined as DVec. If you look at the generated DVec.java
, you can see what kind of implementation it is in Java.
public class DVec extends java.util.AbstractList<Double> implements java.util.RandomAccess {
private transient long swigCPtr;
protected transient boolean swigCMemOwn;
(The following is omitted)
You can see that it is implemented by inheriting AbstractList. Therefore, when using from Java You need to call the AbstractList method (such as add) instead of 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));
}
}
Compile and run this.
$ javac *.java
$ java Main
26.0
Handle examples where templates are nested. If you do not plan to use the intermediate type on the target language side, you can write only the outermost template in the interface file.
Create an example of creating and displaying a random 3x3 matrix.
We will implement the create function that creates a 3x3 matrix and the print_matrix function that displays it.
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;
}
Next, create an interface file.
matrix.i
%module matrix
%include <std_vector.i>
%{
#include "matrix.hpp"
%}
%include "matrix.hpp"
%template (DMatrix) std::vector<std::vector<double>>;
Write only DMatrix in the interface file and try not to write anything about std :: vector
If you execute the swig command in this state, the following files will be created.
It looks like a file corresponding to vector double has also been created.
Create a shared library as before and finish the preparation on the C ++ side.
$ 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
Implement the code on the Java side. Create a variable of DMatrix class and read the function or access the element.
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)); <-Try to print element 0 of m
System.out.println(m); <-Try to print m itself
}
}
If you do this, you will get the following results.
$ 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]
The C ++ side creates variables of type DMatrix properly, and print_matrix works as expected. On the Java side, on the other hand, only the instance information is printed, and the contents of the vector are not printed.
SWIGTYPE_p_std__vectorT_double_t is a class that seems to correspond to DVec, but if you look at the source code of it, you can see that the implementation is clearly different from the one seen in the sample of 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;
}
}
You can see that this class only provides a member variable called swigCPtr and its getter.
If you declare DVec, which is an element of DMatrix, in the interface file with template, you can print the contents of the vector from 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]]
STL supported by SWIG should be treated in the same way.
It's been long, so it's up to here.
Recommended Posts