Monday, July 18, 2011

JNI Example with Class Instantiation (C++)

Awhile back I had to create a JNI wrapper for a C++ class that uses a proprietary core C API. Rewriting everything including the core API in Java would be safer and perform better but in this case not duplicating code was more important. I couldn't find a clear code example containing everything that was needed to 1) make the JNI work at all and 2) wrap a class and call its non-static member function, so I'm putting this here in case it might help someone else.

There are two source files you'll need to write:
  1. A Java class that declares native methods, loads the library built using the JNI, and calls native methods.
  2. JNI code that implements the native methods declared in the Java class and uses the C++ class you're wrapping. You'll build a library with this.
The JNI code file will need to #include a file you'll generate using javah, the C Header and Stub File Generator. The javah tool will create the JNI function prototypes that match the native methods you declared in the Java class.

Here is an example, A.java:

package com.company.product.component;
import java.util.Properties;

public class A {
    private long ptr_;

    private native long createA(long value);
    private native void destroyA(long ptr);
    private native long getResult(long ptr, byte[] input, long length);

    static {
        System.loadLibrary("AJni");
    }
    
    public A(long value) {
        ptr_ = createA(value);
    }
    
    /**
     * Destroy the A instance created by the JNI.
     * This must be called when finished using the A.
     */
    public void destroy() {
        destroyA(ptr_);
    }

    /**
     * Do something.
     *
     * @param input The input array
     * @param length The length of the input array to process
     * @return The result
     */
    public long getResult(byte[] input, long length) {
        return getResult(ptr_, input, length);
    }
}

There are three native methods declared in A.java: one that will create an instance of the C++ class, one that calls a member function of the class, and one that destroys the instance. There is a member variable, ptr_, which stores the pointer to the A instance that's created outside the JVM. You need this to call the member function, getResult(). You also need it to destroy the A instance when you're done. Once you have your Java class written you'll need to:
  1. Use javac to compile A.java into A.class.
  2. Run javah on the Java class like this: javah com.company.product.component.A
    to generate com_company_product_component_A.h which you'll include in the JNI code.
This is what com_company_product_component_A.h looks like:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_company_product_component_A */

#ifndef _Included_com_company_product_component_A
#define _Included_com_company_product_component_A
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_company_product_component_A
 * Method:    createA
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_company_product_component_A_createA
  (JNIEnv *, jobject, jlong);

/*
 * Class:     com_company_product_component_A
 * Method:    destroyA
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_company_product_component_A_destroyA
  (JNIEnv *, jobject, jlong);

/*
 * Class:     com_company_product_component_A
 * Method:    getResult
 * Signature: (J[BJ)J
 */
JNIEXPORT jlong JNICALL Java_com_company_product_component_A_getResult
  (JNIEnv *, jobject, jlong, jbyteArray, jlong);

#ifdef __cplusplus
}
#endif
#endif

The header contains function prototypes that you'll need to provide implementations for in a C++ source file.

Here is the example JNI code, A.cc:

#include "com_company_product_component_A.h"
#include <A.hh>

JNIEXPORT jlong JNICALL Java_com_company_product_component_createA
    (JNIEnv *env, jobject obj, jlong value)
{
    return reinterpret_cast<jlong>(new A(value));
}

JNIEXPORT void JNICALL Java_com_company_product_component_destroyA
    (JNIEnv *env, jobject obj, jlong ptr)
{
    A *a = reinterpret_cast<A*>(ptr);
    if(a) {
        delete a;
    }
}

JNIEXPORT jlong JNICALL Java_com_company_product_component_getResult
    (JNIEnv *env, jobject obj, jlong ptr, jbyteArray input, jlong length)
{
    jbyte *inputArray = env->GetByteArrayElements(input, NULL);
    jlong result = reinterpret_cast<A*>(ptr)->
        getResult(reinterpret_cast<const char*>(inputArray), length);
    env->ReleaseByteArrayElements(input, inputArray, 0);
    return result;
}

The code includes A.hh, the header for the C++ class you're wrapping. All the use of the C++ class is here. Then, A.cc needs to be compiled and linked with the library you're wrapping into a new library, for example libAJni.so, that gets loaded by the Java code using System.load() as you saw in A.java earlier. That's pretty much it. You can now use the Java class A to interact with the C++ library libAJni.so which interacts with the C++ library you wanted to wrap in the first place.
A.class --> libAJni.so --> libA.so

Friday, July 01, 2011

Alternation without Capture/Extraction/Selection

This drove me crazy for longer than I wanted (since I would want that for varying amounts of time) so I will note it here for other frustrated people to find.

I had a regular expression from which I wanted to capture part of it in $1. I also had an alternation in it that needed grouping with parentheses. It kept capturing the alteration in $1 when I didn't care or want to capture that at all.

This is an example of what I had at first that didn't do what I wanted:

$string =~ /bytes\s+=\s+\d+\.?\d*(K|M)?\s+\(\s*(\d+\.?\d*)\%/;

The above code was giving me either 'K' or 'M' in $1 if either were there instead of what I wanted which was the second grouping (\d+\.?\d*). I just needed to know how to stop the capture since stuff like K|M? and K?|M? without parentheses didn't work right either.

After a lot of online searching using probably the wrong query terms, I found the concept of "non-capturing groupings" which are apparently denoted by (?:regex).

Changed my code to this to finally get what I wanted:

$string =~ /bytes\s+=\s+\d+\.?\d*(?:K|M)?\s+\(\s*(\d+\.?\d*)\%/;

This way the 'K' or 'M' isn't captured and I get the second grouping (\d+\.?\d*) stored in $1.