JNA (Java Native Access) is an awesome library similar to Python's ctypes and .NET's P/Invoke. It allows you to access native C libraries without writing any JNI code (actually JNA will write the JNI code for you). The only drawback is that it can't access C++ code without writing any C glue-code. In this blog, I'm going to show you how to get started with JNA, including how to access C++ code.
Let's start with a simple example to access C stdlib.
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.PointerType;
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary) Native.loadLibrary(
(Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
void printf(String format, Object... args);
void system(String command);
FILE fopen(String path, String mode);
int fputs(String content, FILE fp);
String fgets(Memory memory, int size, FILE fp);
int fclose(FILE fp);
public class FILE extends PointerType {
}
}
import com.sun.jna.Memory;
import test.CLibrary.FILE;
public class Main {
public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello JNA\n");
CLibrary.INSTANCE.system("ls -lrt");
writeFile("test.txt");
readFile("test.txt");
}
private static void writeFile(String path) {
System.out.println("Writing " + path);
FILE fp = CLibrary.INSTANCE.fopen(path, "w");
if (fp != null) {
CLibrary.INSTANCE.fputs("Hello World", fp);
CLibrary.INSTANCE.fclose(fp);
}
}
private static void readFile(String path) {
System.out.println("Reading " + path);
FILE fp = CLibrary.INSTANCE.fopen(path, "r");
if (fp != null) {
int size = 256;
Memory buffer = new Memory(size);
CLibrary.INSTANCE.fgets(buffer, size, fp);
System.out.println(buffer.getString(0));
CLibrary.INSTANCE.fclose(fp);
}
}
}
One important thing to note here is that fgets takes char* as a buffer. The JNA equivalent of this is com.sun.jna.Memory.
Now let's create a simple C++ application.
#ifndef HELLO_H_
#define HELLO_H_
#include <iostream>
#include <string>
using namespace std;
struct HelloRequest {
string name;
string message;
};
struct HelloResponse {
string message;
};
class Hello {
public:
void sayHello(const string& name) const;
struct HelloResponse hello(const struct HelloRequest& request);
};
#endif /* HELLO_H_ */
#include "Hello.h"
void Hello::sayHello(const string& name) const {
cout << "Hello, " << name << endl;
}
struct HelloResponse Hello::hello(const struct HelloRequest& req) {
HelloResponse res;
res.message = req.message + ", " + req.name;
return res;
}
To access C++ libraries from JNA, we need to write C glue-code like this.
#ifndef CHELLO_H_
#define CHELLO_H_
extern "C" {
struct CHelloRequest {
const char* name;
const char* message;
};
struct CHelloResponse {
const char* message;
};
void sayHello(const char* name);
struct CHelloResponse hello(const struct CHelloRequest& request);
}
#endif /* CHELLO_H_ */
#include "CHello.h"
#include "Hello.h"
void sayHello(const char* name) {
Hello h;
h.sayHello(string(name));
}
struct CHelloResponse hello(const struct CHelloRequest& request) {
HelloRequest req;
req.name = string(request.name);
req.message = string(request.message);
Hello h;
HelloResponse res = h.hello(req);
CHelloResponse response;
response.message = res.message.c_str();
return response;
}
Now compile our C/C++ source code and create a shared library. Let's call it libhello.so.
Create a new Java class to access hello library.
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
public interface HelloLibrary extends Library {
HelloLibrary INSTANCE = (HelloLibrary) Native.loadLibrary("hello",
HelloLibrary.class);
void sayHello(String name);
CHelloResponse.ByValue hello(CHelloRequest request);
public class CHelloRequest extends Structure {
public String name;
public String message;
}
public class CHelloResponse extends Structure {
public static class ByValue extends CHelloResponse implements Structure.ByValue {
public String message;
}
}
}
Take a note that hello() function returns CHelloResponse struct by value, therefore we need to implement Structure.ByValue.
Now modify our Main class to access hello library and don't forget to set LD_LIBRARY_PATH (for Linux) and PATH (for Windows) to point to the directory where the hello library is located.
import test.HelloLibrary.CHelloRequest;
import test.HelloLibrary.CHelloResponse;
public class Main {
public static void main(String[] args) {
HelloLibrary.INSTANCE.sayHello("Foo");
CHelloRequest req = new CHelloRequest();
req.name = "Bar";
req.message = "Bye";
CHelloResponse.ByValue res = HelloLibrary.INSTANCE.hello(req);
System.out.println(res.message);
}
}