一、Windows版本
1、建立工作目錄JNIDemo。
2、編寫包含本地方法的Java類JNIDemo.java。
首先為Java的package在工作目錄下建立資料夾階層test/jni/demo,在該子目錄下建立Java源檔案。
package test.jni.demo; </p><p>public class JNIDemo{ </p><p> //Dynamic library initialization<br /> static {<br /> try{<br /> System.loadLibrary("jnidemo");<br /> }catch(SecurityException e){<br /> e.printStackTrace();<br /> throw new RuntimeException("A security manager exists and its checkLink method doesn't allow loading of the specified dynamic library jnidemo.dll");<br /> }catch(UnsatisfiedLinkError e){<br /> e.printStackTrace();<br /> throw new RuntimeException("jnidemo.dll does not exist");<br /> }<br /> } </p><p> //Native method,input a String object,show the message and return a String object<br /> public static native String showMessage(String msg); </p><p> //Main method,just for test<br /> public static void main(String []args){<br /> String msg = JNIDemo.showMessage("Hello JNI");<br /> System.out.println(msg);<br /> }<br />}<br />
3、設定環境變數,為編譯Java源檔案和產生C代碼標頭檔做準備,前提是要實現設定好JAVA_HOME和CLASSPATH環境變數,否則相應指定需要的絕對路徑。
set PATH=%JAVA_HOME%/bin;%PATH%<br />set CLASSPATH=.;%CLASSPATH%
4、編譯Java源檔案。
- javac test/jni/demo/JNIDemo.java
5、產生C標頭檔。
- javah -jni -o jnidemo.h test.jni.demo.JNIDemo
6、此時工作目錄下將產生jnidemo.h檔案,建立jnidemo.c檔案實現該本地介面。
#include <windows.h><br />#include "jnidemo.h" </p><p>JNIEXPORT jstring JNICALL Java_test_jni_demo_JNIDemo_showMessage(JNIEnv *env,<br /> jclass classObject,<br /> jstring valueObject){<br /> jclass clsString;<br /> jstring strEncode;<br /> jmethodID mid;<br /> jbyteArray barr;<br /> jsize alen;<br /> jbyte *ba;<br /> char *rtn = NULL;<br /> char msg[1024];<br /> clsString = (*env)->FindClass(env,"java/lang/String");<br /> strEncode = (*env)->NewStringUTF(env,"UTF-8");//Support for multibyte character language<br /> mid = (*env)->GetMethodID(env,clsString, "getBytes", "(Ljava/lang/String;)[B");<br /> barr = (jbyteArray)(*env)->CallObjectMethod(env,valueObject,mid,strEncode);<br /> alen = (*env)->GetArrayLength(env,barr);<br /> ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);<br /> if(alen > 0){<br /> rtn = (char*)malloc(alen+1);<br /> memcpy(rtn,ba,alen);<br /> rtn[alen]=0;<br /> }<br /> (*env)->ReleaseByteArrayElements(env,barr,ba,0);<br /> MessageBox(NULL,(LPCTSTR)rtn,"JNIDemo",MB_OK);//Call Win32 API<br /> memset(msg,'/0',sizeof(msg));<br /> sprintf(msg,"JNI method receive the message : %s/n",rtn);//Risky code<br /> if(rtn){<br /> free(rtn);<br /> }<br /> mid = (*env)->GetMethodID(env,clsString,"<init>","([BLjava/lang/String;)V");<br /> strEncode = (*env)->NewStringUTF(env,"GBK");//Support for chinese<br /> alen = strlen(msg);<br /> barr = (*env)->NewByteArray(env,alen);<br /> (*env)->SetByteArrayRegion(env,barr,0,alen,(jbyte*)msg);<br /> return (jstring)(*env)->NewObject(env,clsString,mid,barr,strEncode);<br />}<br />
7、編譯動態庫,由於不想啟動msdv,所以使用namke和cl.exe、link.exe解決它。
- F:/workshop/Test/JNIDemo>"D:/Program Files/Microsoft Visual Studio/VC98/Bin/VCVARS32.BAT"
設定環境變數後編寫makefile,內容如下。
CC=cl.exe<br />LD=link.exe </p><p>CFLAGS=/nologo /MT /W3 /Gi /GX /O2 /I "D:/Program Files/Java/jdk1.6.0_07/include/win32" /I "D:/Program Files/Java/jdk1.6.0_07/include" /I "." /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "JNIDEMO_EXPORTS" /Fd"jnidemo.pdb" /FD /c<br />LDFLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:no /pdb:"jnidemo.pdb" /machine:I386 /out:"jnidemo.dll" /implib:"jnidemo.lib" /libpath:"D:/Program Files/Java/jdk1.6.0_07/lib" </p><p>SOURCES=jnidemo.c </p><p>OBJECTS=*.obj </p><p>all:<br /> $(CC) $(CFLAGS) $(SOURCES)<br /> $(LD) $(LDFLAGS) $(OBJECTS) </p><p>javah:<br /> javac test/jni/demo/JNIDemo.java<br /> javah -jni -o jnidemo.h test.jni.demo.JNIDemo </p><p>run:<br /> java test.jni.demo.JNIDemo </p><p>clean:<br /> del /F jnidemo.h<br /> del /F test/jni/demo/*.class<br /> del /F *.dll<br /> del /F *.obj<br /> del /F *.idb<br /> del /F *.exp<br /> del /F *.lib<br />
之後順序執行nmake javah、nmake all、nmake run即可見到jni介面執行效果,首先訊息框顯示java輸入的參數,點擊確定後控制台列印jni方法返回的字串。
二、Linux版本
由於實現方式類似,僅編譯工具和使用的API不同,過程不再贅述,僅給是檔案內容、命令和運行效果。
1、建立工作目錄。
[tbicl@vipcms test]$ mkdir JNIDemo<br />[tbicl@vipcms test]$ cd JNIDemo/<br />[tbicl@vipcms JNIDemo]$ mkdir test<br />[tbicl@vipcms JNIDemo]$ cd test/<br />[tbicl@vipcms test]$ mkdir jni<br />[tbicl@vipcms test]$ cd jni/<br />[tbicl@vipcms jni]$ mkdir demo<br />[tbicl@vipcms jni]$ cd demo/<br />[tbicl@vipcms demo]$ vim JNIDemo.java<br />
2、Java源檔案和windows版的一致,僅修改提示資訊。
package test.jni.demo; </p><p>public class JNIDemo{ </p><p> //Dynamic library initialization<br /> static {<br /> try{<br /> System.loadLibrary("jnidemo");<br /> }catch(SecurityException e){<br /> e.printStackTrace();<br /> throw new RuntimeException("A security manager exists and its checkLink method doesn't allow loading of the specified dynamic lib<br />rary libjnidemo.so");<br /> }catch(UnsatisfiedLinkError e){<br /> e.printStackTrace();<br /> throw new RuntimeException("libjnidemo.so does not exist");<br /> }<br /> } </p><p> //Native method,input a String object,show the message and return a String object<br /> public static native String showMessage(String msg); </p><p> //Main method,just for test<br /> public static void main(String []args){<br /> String msg = JNIDemo.showMessage("Hello JNI");<br /> System.out.println(msg);<br /> }<br />}<br />
3、直接編寫makefile搞定剩下的事。
CC=cc -g </p><p>LIBS= -L${JAVA_HOME}/lib </p><p>CFLAGS= -Wl,-soname,libjnidemo.so -fPIC -shared<br />SOURCES=jnidemo.c </p><p>all:<br /> $(CC) $(CFLAGS) $(LIBS) -o libjnidemo.so $(SOURCES) </p><p>javah:<br /> javac -cp . test/jni/demo/JNIDemo.java<br /> javah -classpath . -jni -o jnidemo.h test.jni.demo.JNIDemo </p><p>run:<br /> export LD_LIBRARY_PATH=.:${LD_LIBRARY_PATH}<br /> java test.jni.demo.JNIDemo </p><p>clean:<br /> rm -rf ../*.so<br /> rm -rf ../jnidemo.h<br /> rm -rf test/jni/demo/*.class<br />
4、產生標頭檔。
[tbicl@vipcms JNIDemo]$ make javah<br />javac -cp . test/jni/demo/JNIDemo.java<br />javah -classpath . -jni -o jnidemo.h test.jni.demo.JNIDemo<br />[tbicl@vipcms JNIDemo]$ ll<br />total 12<br />-rw-rw-r-- 1 tbicl tbicl 490 Aug 20 23:38 jnidemo.h<br />-rw-rw-r-- 1 tbicl tbicl 424 Aug 20 23:38 makefile<br />drwxrwxr-x 3 tbicl tbicl 4096 Aug 20 23:23 test<br />
5、修改jnidemo.c。
#include "jnidemo.h" </p><p>JNIEXPORT jstring JNICALL Java_test_jni_demo_JNIDemo_showMessage(JNIEnv *env,<br /> jclass classObject,<br /> jstring valueObject){<br /> jclass clsString;<br /> jstring strEncode;<br /> jmethodID mid;<br /> jbyteArray barr;<br /> jsize alen;<br /> jbyte *ba;<br /> char *rtn = NULL;<br /> char msg[1024];<br /> clsString = (*env)->FindClass(env,"java/lang/String");<br /> strEncode = (*env)->NewStringUTF(env,"UTF-8");//Support for multibyte character language<br /> mid = (*env)->GetMethodID(env,clsString, "getBytes", "(Ljava/lang/String;)[B");<br /> barr = (jbyteArray)(*env)->CallObjectMethod(env,valueObject,mid,strEncode);<br /> alen = (*env)->GetArrayLength(env,barr);<br /> ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);<br /> if(alen > 0){<br /> rtn = (char*)malloc(alen+1);<br /> memcpy(rtn,ba,alen);<br /> rtn[alen]=0;<br /> }<br /> (*env)->ReleaseByteArrayElements(env,barr,ba,0);<br /> //MessageBox(NULL,(LPCTSTR)rtn,"JNIDemo",MB_OK);//Call Win32 API<br /> fprintf(stdout,"%s/n",rtn);<br /> memset(msg,'/0',sizeof(msg));<br /> sprintf(msg,"JNI method receive the message : %s/n",rtn);//Risky code<br /> if(rtn){<br /> free(rtn);<br /> }<br /> mid = (*env)->GetMethodID(env,clsString,"<init>","([BLjava/lang/String;)V");<br /> strEncode = (*env)->NewStringUTF(env,"GBK");//Support for chinese<br /> alen = strlen(msg);<br /> barr = (*env)->NewByteArray(env,alen);<br /> (*env)->SetByteArrayRegion(env,barr,0,alen,(jbyte*)msg);<br /> return (jstring)(*env)->NewObject(env,clsString,mid,barr,strEncode);<br />}<br />
6、編譯並運行。
三、總結
別人說jni是雙刃劍,使好了很有用處,否則帶來一堆煩惱,正如c代碼中標註的危險代碼部分,如果Java方法輸入的參數足夠大,導致msg數組無法容納,sprintf將引發問題,由於通常都是通過jvm啟動並載入動態庫本地方法,本地方法和jvm使用同一地址空間,本地方法crash將導致jvm奔潰直接退出,如果這種情況放生諸如WebLogic等追求很大穩定性的中介軟體之上,後果可想而知,所以這洪水能迴避就盡量迴避,除非本地介面簡單得出錯的幾率都沒有,又或者編寫者非常神仙能把握好它。