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

Thursday, March 1, 2012

How to Create a Debian Package

In my previous blog, I've described how to create an RPM package. This time, I'm going to show you how to create a Debian package. I'm going to use the same code that's used in RPM tutorial blog. Creating a Debian package can be pretty complicated if we have to do it manually. Luckily there are some helper scripts that can make our lives easier.
  1. Install devscripts and debhelper
  2. sudo apt-get install devscripts debhelper
  3. Create a simple hello.cpp
  4. #include <iostream>
    using namespace std;
    
    int main() {
        cout << "Hello World" << endl;
        return 0;
    }
    
  5. Create a Makefile
  6. CC = g++ 
    CCFLAGS = -g -Wall
    SRC = hello.cpp
    TARGET = hello
    
    .PHONY: clean install
    
    all: hello
    
    install: $(TARGET)
        if [ -d ${DESTDIR} ]; then rm -rf ${DESTDIR}; fi
        mkdir -p ${DESTDIR}
        cp $(TARGET) ${DESTDIR} 
    
    hello: 
        $(CC) $(CCFLAGS) -o $(TARGET) $(SRC)
    
    clean:
        rm -rf $(TARGET)
    
  7. In the directory where we created the above files, create a new debian directory
  8. mkdir debian
  9. Create a debian/changelog file
  10. dch --create -v 0.1 --package hello
    We can create this changelog file without using dch script, but the changelog file requires a particular format and the dch script can help to create a changelog template. The changelog file should look like this.
    hello (0.1) UNRELEASED; urgency=low
    
      * Initial release. (Closes: #123)
    
     -- foo <foo@bar.com>  Thu, 01 Mar 2012 14:16:42 +0800
    
  11. Create a debian/compat
  12. The content of this file is very simple. It just contains a number 8. This file is used to tell debhelper to use compatibility helper version 8. The file should look like this.
    8
  13. Create a debian/control file
  14. This control file is used to describe the source and binary package and other information. This control file also has a particular format. Take a note that there's a space after the Description field. This to tell that it's an extended description. The description and extended description should be different; otherwise lintian (a tool to check the correctness of files rquired to create Debian packages) will complain. The control file should look like this.
    Source: hello
    Maintainer: foo <foo@bar.com>
    Section: misc
    Priority: optional
    Standards-Version: 3.9.1
    Build-Depends: debhelper (>= 8)
    
    Package: hello
    Architecture: any
    Depends: ${shlibs:Depends}, ${misc:Depends}
    Description: say hello
     say hello world
    
  15. Create a debian/rules file
  16. This file is basically a Makefile.
    #!/usr/bin/make -f
    %:
        dh $@
    
    override_dh_auto_install:
        $(MAKE) DESTDIR=$$(pwd)/debian/hello/usr/bin install
    
    The override_dh_auto_install is required because in our example, we have a custom make install target.

  17. Create a debian/copyright file
  18. For this tutorial, just create an empty copyright file.

  19. To summarize, we should've already created all these files
  20. /home/foo/testdeb/hello/hello.cpp
                           /Makefile
                           /debian/control
                           /copyright
                           /changelog
                           /compat
                           /rules
    
  21. Create a Debian package
  22. debuild -uc -us
    -uc means don't sign the .changes file
    -us means don't sign the source package
    For more information about the options, read man dpkg-buildpackage.

    You should now see your source and Debian packages in /home/foo/testdeb

  23. Install the newly created Debian package
  24. sudo dpkg -i hello_0.1_i386.deb
  25. Test if hello program was successfully installed
  26. hello
    You should see "Hello World" output

  27. Remove the hello package
  28. sudo dpkg -r hello