Sunday, September 28, 2008

Going Native: Structures and Callbacks

This is a continuation of a simple overview of the Java Native Access (JNA) library. In the first part, I looked at the basics of mapping native library functions, with simple types.

JNA provides two key elements for the mapping Java to C libraries, namely structures and callbacks. Structures are automatically mapped from the native values and may be used as a type-safe pointer to a instance of the structure. Callbacks are provided via an extension of an interface, and the appropriate method is defined on the new interface.

Structures

Mapping native structs into Java classes is fairly simple via the Structure class. This class may be extended, and and any fields may be declared as public members of the new class.

For example, let's take the following C struct

struct {
char* name;
int count;
char* desc;
} record;

and map it into Java

public class Record extends Structure {
public String name;
public int count;
public String desc;
}


Once we have our structure defined, we can now create mappings for functions which operate on the record struct. In C:

int save(record* record);

and in our Java library interface:

public int save(Record record);


What if we want to pass this struct into a function to be populated? For example,

int load(record* record);

// and it's usage:
record r;
load(&r);
printf("record found: %s", r.name);

in which we load the record from a data store. We need a way, in Java, to pass this object to the native code, by reference. Fortunately, JNA provides a simple way of doing this.

public class Record extends Structure {
public class ByReference extends Record implements Structure.ByReference {}
...
}

// and it's usage:
Record.ByReference record = new Record.ByReference();
MyLibrary.INSTANCE.load(record);
System.out.println("record found: " + record.name);

This tag interface provides a very simple way of giving us this functionality.

Callbacks

Callbacks are provided by the Callback interface. By extending this interface we can add a method which will be used by the C library as the call back method.

Let say we have a method in our C library which will notify the application if a record has been deleted by another process. It provides us with a method to add a callback,

int register_delete_monitor( void* fn);

and the callback expected takes a parameter of a record.

In our Java library, we would create a Callback for this method:

interface DeletedRecordCallback extends Callback {
void callback (Record r);
}
...
// and in our Library Interface:
int register_delete_monitor (DeletedRecordCallback drc);

Now we can pass in an implementation of our DeletedRecordCallback and have it registered as a callback in the C library.

There are one caveat with the use of callbacks in JNA. The callback must not be garbage collected unless it's been unregistered. This is especially true for long lived callbacks. A call to a garbage-collected callback will crash the vm. This can be simply avoided by hanging on to the instance of the callback - a singleton will work quite well in this context. Otherwise, it is important to have the callback instance deregister itself in it's finalize method.

Overall Impressions

Coupling the power of JNA's type mappings, with its structure mapping and callback access, it is a very useful library for taking the difficulty out of mapping native code to java. It makes rapid development with existing native libraries much, much easier. Java-to-native mapping has never been easier.

0 comments: