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.
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);
    }
}

No comments:

Post a Comment