Friday, March 9, 2012

Getting Started with JNA

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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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_ */
1
2
3
4
5
6
7
8
9
10
11
12
#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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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_ */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
    }
}

No comments:

Post a Comment