10 January 2008

JAVA Native Interface (JNI): notebook

The JAVA Native Interface (JNI)allows Java code running in the Java virtual machine to call and (be called) some methods from libraries written in C/C++. I've learned this technology by trying to use the CURSES library (an API allowing the programmer to write text user interfaces in a terminal).

We starts by creating a java class: NCurses.java


1 /* A JNI test */
2 public class NCurses
3 {
4 public NCurses() {}
5 /** initializes the ncurses library */
6 public native static int install();
7 /** disposes the ncurses library */
8 public native static int uninstall();
9 /** set character on screen */
10 public native static void setCharAt(int x,int y,int ch);
11 /** update the screen */
12 public native static void refresh();
13 /** get the number of rows */
14 public native int getRowCount();
15 /** get the number of columns */
16 public native int getColumnCount();
17 /** inverse video */
18 public native int standout();
19 /** normal video */
20 public native int standend();
21
22 /** draw a string at the given position */
23 public void drawString(String s,int x, int y)
24 {
25 int maxx= getColumnCount();
26 for(int i=0;i< s.length() && i+x < maxx;++i)
27 {
28 if(i+3< s.length() && s.substring(i,i+3).equals("<b>"))
29 {
30 standout(); i+=2; continue;
31 }
32 else if(i+4< s.length() && s.substring(i,i+4).equals("</b>"))
33 {
34 standend(); i+=3; continue;
35 }
36 setCharAt(i+x,y,s.charAt(i));
37 }
38 }
39
40 public static void main(String[] args)
41 {
42 //load the C library
43 System.loadLibrary("NCurses");
44 //init curses
45 NCurses.install();
46
47 NCurses sample = new NCurses();
48
49 //draw a box
50 for(int i=1;i+1< sample.getRowCount();++i)
51 {
52 sample.setCharAt(1,i,'*');
53 sample.setCharAt(sample.getColumnCount()-2,i,'*');
54 }
55 for(int i=1;i+1< sample.getColumnCount();++i)
56 {
57 sample.setCharAt(i,1,'*');
58 sample.setCharAt(i,sample.getRowCount()-2,'*');
59 }
60 /* draw some strings */
61 int n=0;
62 for(int i=2;i+3< sample.getRowCount();i+=3)
63 {
64 sample.drawString("<b>Menu \t"+(++n)+"</b>: Hello Menu",5,i);
65 }
66 /* update the screen */
67 sample.refresh();
68 /* wait 10 sec */
69 try{ Thread.sleep(10000); } catch(Exception err) {}
70 //dispose ncurses
71 NCurses.uninstall();
72 }
73 }


This class contains some native methods that are not implemented in this class but they will be loading (line 43 by calling System.loadLibrary)

We can compile this class:
javac NCurses.java


We can run JAVAH on this class. javah will generate the C header file that are needed to implement our native methods.

javah NCurses


This generates the following file: NCurses.h


1 /* DO NOT EDIT THIS FILE - it is machine generated */
2 #include <jni.h>
3 /* Header for class NCurses */
4
5 #ifndef _Included_NCurses
6 #define _Included_NCurses
7 #ifdef __cplusplus
8 extern "C" {
9 #endif
10 /*
11 * Class: NCurses
12 * Method: install
13 * Signature: ()I
14 */
15 JNIEXPORT jint JNICALL Java_NCurses_install
16 (JNIEnv *, jclass);
17
18 /*
19 * Class: NCurses
20 * Method: uninstall
21 * Signature: ()I
22 */
23 JNIEXPORT jint JNICALL Java_NCurses_uninstall
24 (JNIEnv *, jclass);
25
26 /*
27 * Class: NCurses
28 * Method: setCharAt
29 * Signature: (III)V
30 */
31 JNIEXPORT void JNICALL Java_NCurses_setCharAt
32 (JNIEnv *, jclass, jint, jint, jint);
33
34 /*
35 * Class: NCurses
36 * Method: refresh
37 * Signature: ()V
38 */
39 JNIEXPORT void JNICALL Java_NCurses_refresh
40 (JNIEnv *, jclass);
41
42 /*
43 * Class: NCurses
44 * Method: getRowCount
45 * Signature: ()I
46 */
47 JNIEXPORT jint JNICALL Java_NCurses_getRowCount
48 (JNIEnv *, jobject);
49
50 /*
51 * Class: NCurses
52 * Method: getColumnCount
53 * Signature: ()I
54 */
55 JNIEXPORT jint JNICALL Java_NCurses_getColumnCount
56 (JNIEnv *, jobject);
57
58 /*
59 * Class: NCurses
60 * Method: standout
61 * Signature: ()I
62 */
63 JNIEXPORT jint JNICALL Java_NCurses_standout
64 (JNIEnv *, jobject);
65
66 /*
67 * Class: NCurses
68 * Method: standend
69 * Signature: ()I
70 */
71 JNIEXPORT jint JNICALL Java_NCurses_standend
72 (JNIEnv *, jobject);
73
74 #ifdef __cplusplus
75 }
76 #endif
77 #endif


I've implemented the C methods in the following file: NCurses.c

1 #include "NCurses.h"
2 #include <string.h>
3 #include <curses.h>
4 #include <signal.h>
5
6 JNIEXPORT jint JNICALL Java_NCurses_install(JNIEnv *env, jclass clazz)
7 {
8 //(void) signal(SIGINT, finish); /* arrange interrupts to terminate */
9
10 (void) initscr(); /* initialize the curses library */
11 keypad(stdscr, TRUE); /* enable keyboard mapping */
12 (void) nonl(); /* tell curses not to do NL->CR/NL on output */
13 (void) cbreak(); /* take input chars one at a time, no wait for \n */
14 (void) noecho(); /* don't echo input */
15 clear();
16 move(0,0);
17 refresh();
18 return 0;
19 }
20
21 JNIEXPORT jint JNICALL Java_NCurses_uninstall(JNIEnv *env, jclass clazz)
22 {
23 endwin();
24 return 0;
25 }
26
27 JNIEXPORT jint JNICALL Java_NCurses_getRowCount(JNIEnv *env, jobject obj)
28 {
29 int h, w;
30 getmaxyx(stdscr, h, w);
31 return h;
32 }
33
34
35 JNIEXPORT jint JNICALL Java_NCurses_getColumnCount(JNIEnv *env, jobject obj)
36 {
37 int h, w;
38 getmaxyx(stdscr, h, w);
39 return w;
40 }
41
42 /*
43 * Class: NCurses
44 * Method: setCharAt
45 * Signature: (III)V
46 */
47 JNIEXPORT void JNICALL Java_NCurses_setCharAt
48 (JNIEnv *env, jclass object, jint x, jint y, jint chr)
49 {
50 mvaddch(y,x,chr);
51 }
52
53 /*
54 * Class: NCurses
55 * Method: refresh
56 * Signature: ()V
57 */
58 JNIEXPORT void JNICALL Java_NCurses_refresh
59 (JNIEnv *env, jclass object)
60 {
61 refresh();
62 }
63
64
65 /*
66 * Class: NCurses
67 * Method: standout
68 * Signature: ()I
69 */
70 JNIEXPORT jint JNICALL Java_NCurses_standout
71 (JNIEnv *env, jobject object)
72 {
73 standout();
74 }
75
76 /*
77 * Class: NCurses
78 * Method: standend
79 * Signature: ()I
80 */
81 JNIEXPORT jint JNICALL Java_NCurses_standend
82 (JNIEnv * env, jobject object)
83 {
84 standend();
85 }


before compiling the C source, I set the variable LD_LIBRARY_PATH. For my machine it was:
export LD_LIBRARY_PATH=${PWD}:/usr/local/lib:/usr/lib


We can now compile NCurses.c and create the C library. You may need to add the path the java C headers which are contained in ${JAVA_HOME}/include/ and ${JAVA_HOME}/include/{your-machine}

gcc -fpic -c NCurses.c -o NCurses.o
gcc -shared -o libNCurses.so NCurses.o -lcurses


Let's run our java program:

java NCurses


The result looks like this:

*********************************************
* Menu 1 : Hello Menu *
* *
* *
* Menu 2 : Hello Menu *
* *
* *
* Menu 3 : Hello Menu *
* *
* *
* Menu 4 : Hello Menu *
* *
* *
* Menu 5 : Hello Menu *
* *
* *
* Menu 6 : Hello Menu *
* *
* *
* *
*********************************************


That's it.
Pierre

4 comments:

Egon Willighagen said...

Excellent! Now we can finally drop Swing and SWT ;)

Pierre Lindenbaum said...

:-))

shj said...

Thank you for this - a few copy and pastes and I could use curses from Java - neat :-)

Have you done further development on this? If you want to talk about future development, please drop me a mail. I'm using Gmail, username is stighj

Cheers and thanks again.

Pierre Lindenbaum said...

@shj you can use http://sourceforge.net/projects/javacurses/