JNI 程序小结 01
Java + JNI 本地库 异构程序构建示例。
下文 (加强版) : http://my.oschina.net/typhoon/blog/470904
-
创建 Java 源代码文件:
[typhoon@TFW-CENT6-LT sandbox]$ mkdir -p src/tfw/rsch/jni
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt src
[typhoon@TFW-CENT6-LT sandbox]$ vi src/tfw/rsch/jni/Main.java
[typhoon@TFW-CENT6-LT sandbox]$ vi src/tfw/rsch/jni/JniLoader.java
[typhoon@TFW-CENT6-LT sandbox]$ vi src/tfw/rsch/jni/JniCall.java
[typhoon@TFW-CENT6-LT sandbox]$ vi src/tfw/rsch/jni/JniCall_02.java
[typhoon@TFW-CENT6-LT sandbox]$ ls src/tfw/rsch/jni
JniCall_02.java JniCall.java JniLoader.java Main.java
[typhoon@TFW-CENT6-LT sandbox]$
文件内容明细:
- src/tfw/rsch/jni/Main.java
/** * ...<br /> */ package tfw.rsch.jni; import tfw.base.util.array.ArrayToolE; import tfw.base.util.text.TextToolE; /** * 启动类,启动整个程序。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-23_00-06 */ public class Main { /** * 主函数,启动加载和测试 JNI 的用例。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-09_15-54 * @param str1dCmdArgs * - 字符串型数组,来自命令行的参数。<br /> */ public static void main(String[] str1dCmdArgs) { new Main().test(str1dCmdArgs); } /** * JNI 加载与调用测试。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-23_00-05 * @param str1dArgs * 来自命令行的参数,字符串型数组。<br /> */ private void test(String[] str1dArgs) { try { // [S] 对数组型参数做预处理,把数组元素组合进单一的字符串。 String strArgsText = ArrayToolE.arrayForConsole(str1dArgs); if (null == strArgsText) { strArgsText = ""; } else if ("null".equals(strArgsText)) { strArgsText = null; } // [E] 对数组型参数做预处理,把数组元素组合进单一的字符串。 // 测试用例 01 :加载本地库。 new JniLoader(); // 测试用例 01 :调用本地函数 (本地库中的函数)。 JniCall jc = new JniCall(); jc.println(); jc.nativePrintln(); // [S] 测试用例 02 :加载本地库、调用本地函数。 JniCall_02 jc_02 = new JniCall_02(); { { String strMethodHead = TextToolE.concat("\tjc_02.javaManipulate(", ((null == strArgsText) ? "null" : ("\"" + strArgsText + "\"")) + ")\n\t{"); System.out.println(strMethodHead); String strRst = jc_02.javaManipulate(strArgsText); String strMethodTail = TextToolE.concat("\t}\n\tGot Return Value: ", ((null == strRst) ? "null" : ("\"" + strRst + "\""))); System.out.println(strMethodTail); } System.out.println(); { String strMethodHead = TextToolE.concat("\tjc_02.nativeManipulate(", ((null == strArgsText) ? "null" : ("\"" + strArgsText + "\"")) + ")\n\t{"); System.out.println(strMethodHead); // 此处:调用本地函数! String strOut = jc_02.nativeManipulate(strArgsText); String strMethodTail = TextToolE.concat("\t}\n\tGot Return Value: ", ((null == strOut) ? "null" : ("\"" + strOut + "\""))); System.out.println(strMethodTail); } } // [E] 测试用例 02 :加载本地库、调用本地函数。 } catch (Throwable t) { t.printStackTrace(); } } }
- src/tfw/rsch/jni/JniLoader.java
/** * ...<br /> */ package tfw.rsch.jni; /** * 测试用例 01 :<br /> * 这个类在初始化时加载测试用例 01 的本地库 (*.so 共享模块 或 *.dll 动态链接库 * 之类)。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-22_23-12 */ public class JniLoader { /** * 静态代码块,确保在类初始化时即自动执行共享库加载函数。<br /> */ static { loadNativeLibrary(); } /** * 测试用例 01 :<br /> * 本地库 (*.so 共享模块 或 *.dll 动态链接库 之类) 加载函数。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-22_21-37 */ private static void loadNativeLibrary() { // 将从系统属性“java.library.path”中搜索本地库。 String strJavaLibraryPath = System.getProperty("java.library.path"); // 将根据本地库的名称“JniCall_name1”加载相应的库文件 // “libJniCall_name1.so”(或“JniCall_name1.dll”之类)。 String strNativeLibraryName = "JniCall_name1"; System.out.println("java.library.path=" + strJavaLibraryPath); System.out.println("Loading \"" + strNativeLibraryName + "\"..."); // 此处:加载本地库! System.loadLibrary(strNativeLibraryName); System.out.println("Loaded."); } }
- src/tfw/rsch/jni/JniCall.java
/** * ...<br /> */ package tfw.rsch.jni; /** * 测试用例 01 :<br /> * 这个类提供一个测试用例 01 本地库中函数 (本地函数) 的入口,供 java 程序调用。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-22_23-32 */ public class JniCall { /** * 测试用例 01 :一个 java 函数。向标准输出打印一条信息。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-11_14-09 */ public void println() { System.out.println("\tJava:\tWorks!"); } /** * 测试用例 01 :本地库中函数 (本地函数) 的入口。<br /> * 预计相应的本地函数也将向标准输出打印一条信息,类似于上述的 java 函数。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-11_14-09 */ public native void nativePrintln(); }
- src/tfw/rsch/jni/JniCall_02.java
/** * ...<br /> */ package tfw.rsch.jni; import tfw.base.util.misc.MiscToolE; import tfw.base.util.text.TextToolE; /** * 测试用例 02 :这个类<br /> * * 在初始化时加载测试用例 01 的本地库 (*.so 共享模块 或 *.dll 动态链接库 之类);<br /> * * 提供一个测试用例 02 本地库中函数 (本地函数) 的入口,供 java 程序调用。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-22_23-56 */ public class JniCall_02 { /** * 静态代码块,确保在类初始化时即自动执行共享库加载函数。<br /> */ static { loadNativeLibrary(); } /** * 测试用例 02 :<br /> * 本地库 (*.so 共享模块 或 *.dll 动态链接库 之类) 加载函数。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-22_23-15 */ private static void loadNativeLibrary() { // 将从系统属性“java.library.path”中搜索本地库。 String strJavaLibraryPath = System.getProperty("java.library.path"); // 将根据本地库的名称“JniCall_02”加载相应的库文件“libJniCall_02.so” // (或“JniCall_02.dll”之类)。 String strNativeLibraryName = "JniCall_02"; System.out.println("java.library.path=" + strJavaLibraryPath); System.out.println("Loading \"" + strNativeLibraryName + "\"..."); // 此处:加载本地库! System.loadLibrary(strNativeLibraryName); System.out.println("Loaded."); } /** * 测试用例 02 :一个 java 函数。<br /> * 将函数入口处传入的字符串参数打印至标准输出; * 之后从标准输入处读取一个字符串,并将其返回。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-11_14-10 * @param strText * - 传入的参数,字符串。<br /> * @return 一个收自标准输入的字符串,<strong>有可能为 null 。</strong><br /> */ public String javaManipulate(String strText) { // 将传入的字符串参数打印至标准输出。 String strReceiveText = TextToolE.concat("\t\tJava:\n\n\t\tArgument Received:\n\t\t\t", (null == strText) ? "null" : ("\"" + strText + "\"")); System.out.println(strReceiveText); // [S] 从标准输入处接收一个字符串,并打印至标准输出。 System.out .print("\t\tUser Input (\"null\" would be considered as null):\n\t\t\t"); String strUserInput = MiscToolE.getline(System.in); System.out.println("\t\tUser Input Received:\n\t\t\t" + ((null == strUserInput) ? "null" : ("\"" + strUserInput + "\""))); // [E] 从标准输入处接收一个字符串,并打印至标准输出。 // [S] 把将要返回的值打印至标准输出。 String strReturnValue = "null".equals(strUserInput) ? null : strUserInput; System.out.println("\t\tReturns:\n\t\t\t" + ((null == strReturnValue) ? "null" : ("\"" + strReturnValue + "\""))); // [E] 把将要返回的值打印至标准输出。 // 返回。 return strReturnValue; } /** * 测试用例 02 :本地库中函数 (本地函数) 的入口。<br /> * 预计相应的本地函数也将把函数入口处传入的字符串参数打印至标准输出、 * 之后从标准输入处读取一个字符串,并将其返回。<br /> * * @author Typhoon.Free.Wolf * @version 2015-04-11_14-10 * @param strText * - 传入的参数,字符串。<br /> * @return 一个实际上返回自本地函数的字符串,<strong>有可能为 null 。</strong><br /> */ public native String nativeManipulate(String strText); }
- src/tfw/rsch/jni/Main.java
-
将 Java 源代码文件编译成二进制程序:
[typhoon@TFW-CENT6-LT sandbox]$ cp -a ../lib . # ← 上述源代码引用的工具类在这个文件夹里。
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt lib src
[typhoon@TFW-CENT6-LT sandbox]$ ls lib
tfw-base.aij.jar tfw-base.v2.2.8_2014-12-22_22-00.longest_night.jre150.aij.jar
# ↑ ↖_ 工具类“ArrayToolE”、“MiscToolE”和“TextToolE”所在。
# `- 指向“tfw-base.v2.2.8_2014-12-22_22-00.longest_night.jre150.aij.jar”的符号链接。
[typhoon@TFW-CENT6-LT sandbox]$ mkdir -p classes
[typhoon@TFW-CENT6-LT sandbox]$ javac -classpath lib/tfw-base.aij.jar -d classes src/tfw/rsch/jni/*.java
[typhoon@TFW-CENT6-LT sandbox]$ ls classes/tfw/rsch/jni
JniCall_02.class JniCall.class JniLoader.class Main.class
[typhoon@TFW-CENT6-LT sandbox]$ -
为将要调用本地库的 Java 类生成 JNI 头文件:
[typhoon@TFW-CENT6-LT sandbox]$ mkdir c_include
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt c_include classes lib src
[typhoon@TFW-CENT6-LT sandbox]$ javah -classpath classes -o c_include/JniCall_name2.h tfw.rsch.jni.JniCall
[typhoon@TFW-CENT6-LT sandbox]$ javah -classpath classes -o c_include/JniCall_02.h tfw.rsch.jni.JniCall_02
[typhoon@TFW-CENT6-LT sandbox]$ ls c_include
JniCall_02.h JniCall_name2.h
[typhoon@TFW-CENT6-LT sandbox]$
头文件的文件名和将要使用的本地库文件名没有关系;在不违反操作系统命名规则、不违反编译器限制的情况下可以随意命名。
用例 01 ,类“tfw.rsch.jni.JniCall”将使用本地库“JniCall_name1”,但头文件命名为“JniCall_name2.h”并不会导致错误。
用例 02 ,类“tfw.rsch.jni.JniCall_02”,为了降低记错、输错文件名的几率,将类名、本地库名、头文件名统一规格,这是推荐的做法。
文件内容明细:
- c_include/JniCall_name2.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class tfw_rsch_jni_JniCall */ #ifndef _Included_tfw_rsch_jni_JniCall #define _Included_tfw_rsch_jni_JniCall #ifdef __cplusplus extern "C" { #endif /* * Class: tfw_rsch_jni_JniCall * Method: nativePrintln * Signature: ()V */ JNIEXPORT void JNICALL Java_tfw_rsch_jni_JniCall_nativePrintln (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
- c_include/JniCall_02.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class tfw_rsch_jni_JniCall_02 */ #ifndef _Included_tfw_rsch_jni_JniCall_02 #define _Included_tfw_rsch_jni_JniCall_02 #ifdef __cplusplus extern "C" { #endif /* * Class: tfw_rsch_jni_JniCall_02 * Method: nativeManipulate * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_tfw_rsch_jni_JniCall_102_nativeManipulate (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
- c_include/JniCall_name2.h
-
创建 C 语言源代码文件:
[typhoon@TFW-CENT6-LT sandbox]$ mkdir c_src
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt c_include classes c_src lib src
[typhoon@TFW-CENT6-LT sandbox]$ vi c_src/JniCall_name3.c
[typhoon@TFW-CENT6-LT sandbox]$ vi c_src/JniCall_02.c
[typhoon@TFW-CENT6-LT sandbox]$ ls c_src
JniCall_02.c JniCall_name3.c
[typhoon@TFW-CENT6-LT sandbox]$
C 语言源代码文件的文件名和将要调用的本地库文件名没有关系;在不违反操作系统命名规则、不违反编译器限制的情况下可以随意命名。
用例 01 ,类名、本地库名、头文件名、C 语言源代码文件名互相全无关系并不会导致错误。
用例 02 ,为了降低记错、输错文件名的几率,将类名、本地库名、头文件名、C 语言源代码文件名统一规格,这是推荐的做法。
文件内容明细:
- c_src/JniCall_name3.c
#include <jni.h> #include <stdio.h> #include <JniCall_name2.h> JNIEXPORT void JNICALL Java_tfw_rsch_jni_JniCall_nativePrintln (JNIEnv *env, jobject obj) { printf("\tJNI:\tWorks!\n"); return; };
- c_src/JniCall_02.c
#include <jni.h> #include <stdio.h> #include <JniCall_02.h> JNIEXPORT jstring JNICALL Java_tfw_rsch_jni_JniCall_102_nativeManipulate (JNIEnv *jniEnv, jobject jobj, jstring jstr) { // 将传入的参数打印至标准输出。 printf("\t\tJNI:\n\n\t\tArgument Received:\n\t\t\t"); printf((NULL == jstr) ? "%s\n" : "\"%s\"\n", jstr); // 将传入的字符串参数转换成 C 语言字符串,再打印至标准输出。 const char *strConverted = (NULL == jstr) ? NULL : (*jniEnv)->GetStringUTFChars(jniEnv, jstr, 0); printf("\t\tConverted:\n\t\t\t"); printf((NULL == strConverted) ? "%s\n" : "\"%s\"\n", strConverted); // [S] 从标准输入处接收一个字符串,并打印至标准输出。 printf("\t\tUser Input (\"NULL\" would be considered as NULL):\n\t\t\t"); char *ch1dUserInput; gets(ch1dUserInput); printf("\t\tUser Input Received:\n\t\t\t"); printf((0 == strcmp(ch1dUserInput, "NULL")) ? "%s\n" : "\"%s\"\n", ch1dUserInput); // [E] 从标准输入处接收一个字符串,并打印至标准输出。 // 把接收到、将要返回的字符串打印至标准输出。 printf("\t\tReturn Value:\n\t\t\t"); char *strReturnValue = (0 == strcmp(ch1dUserInput, "NULL")) ? NULL : ch1dUserInput; printf((NULL == strReturnValue) ? "%s\n" : "\"%s\"\n", strReturnValue); // 把将要返回的字符串转换成 java 字符串,并打印至标准输出。 jstring jstrReturnValue = (*jniEnv)->NewStringUTF(jniEnv, strReturnValue); printf("\t\tConverted:\n\t\t\t"); printf((NULL == jstrReturnValue) ? "%s\n" : "\"%s\"\n", jstrReturnValue); // 返回。 return jstrReturnValue; };
- c_src/JniCall_name3.c
-
将 C 语言源代码文件编译成本地库文件 (本例中为 *.so 共享对象文件):
- 一个定制的 shell 脚本能减少很多手工输入,降低出错的几率:
[typhoon@TFW-CENT6-LT sandbox]$ vi jnicc.sh
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt c_include classes c_src jnicc.sh lib src
[typhoon@TFW-CENT6-LT sandbox]$ cat jnicc.sh
#!/bin/sh
# date;
echo $0; echo $1; echo $2;
# gcc $1 -shared -I c_include -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -o $2;
# gcc 4.4.7 20120313 of CentOS 6.6 requires "-fPIC".
gcc $1 -fPIC -shared -I c_include -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -o $2;
echo $?;
# date;
[typhoon@TFW-CENT6-LT sandbox]$ chmod 700 jnicc.sh
[typhoon@TFW-CENT6-LT sandbox]$ - 编译:
[typhoon@localhost sandbox]$ ./jnicc.sh c_src/JniCall_name3.c lib/libJniCall_name1.so
./jnicc.sh
c_src/JniCall_name3.c
lib/libJniCall_name1.so
0
[typhoon@localhost sandbox]$ ./jnicc.sh c_src/JniCall_02.c lib/libJniCall_02.so
./jnicc.sh
c_src/JniCall_02.c
lib/libJniCall_02.so
0
[typhoon@localhost sandbox]$ ls lib
libJniCall_02.so
libJniCall_name1.so
tfw-base.aij.jar
tfw-base.v2.2.8_2014-12-22_22-00.longest_night.jre150.aij.jar
[typhoon@TFW-CENT6-LT sandbox]$
用例 01 ,类“tfw.rsch.jni.JniCall”将使用本地库“JniCall_name1”,编译出的库文件名称必须为“libJniCall_name1.so”。
用例 02 ,类“tfw.rsch.jni.JniCall_02”将使用本地库“JniCall_02”,编译出的库文件名称必须为“libJniCall_02.so”。
- 一个定制的 shell 脚本能减少很多手工输入,降低出错的几率:
-
运行程序:
- 一个定制的 alias 设置能减少很多手工输入,降低出错的几率:
[typhoon@TFW-CENT6-LT sandbox]$ vi jrun.alias
[typhoon@TFW-CENT6-LT sandbox]$ ls
0_guide.txt c_include classes c_src jnicc.sh jrun.alias lib src
[typhoon@TFW-CENT6-LT sandbox]$ cat jrun.alias
alias jrun='java -classpath classes:lib/tfw-base.aij.jar -Djava.library.path=lib';
[typhoon@TFW-CENT6-LT sandbox]$ - 加载此 alias 设置,并运行此程序:
[typhoon@TFW-CENT6-LT sandbox]$ source jrun.alias
[typhoon@TFW-CENT6-LT sandbox]$ alias
……
alias jrun='java -classpath classes:lib/tfw-base.aij.jar -Djava.library.path=lib'
……
[typhoon@localhost sandbox]$ jrun tfw.rsch.jni.Main 一二三四五 上山打老虎
java.library.path=lib
Loading "JniCall_name1"...
Loaded.
Java: Works!
JNI: Works!
java.library.path=lib
Loading "JniCall_02"...
Loaded.
jc_02.javaManipulate("一二三四五, 上山打老虎")
{
Java:
Argument Received:
"一二三四五, 上山打老虎"
User Input ("null" would be considered as null):
老虎没打到,打到小松鼠……
User Input Received:
"老虎没打到,打到小松鼠……"
Returns:
"老虎没打到,打到小松鼠……"
}
Got Return Value: "老虎没打到,打到小松鼠……"
jc_02.nativeManipulate("一二三四五, 上山打老虎")
{
JNI:
Argument Received:
"h#��"
Converted:
"一二三四五, 上山打老虎"
User Input ("NULL" would be considered as NULL):
松鼠有几只?一二三四五!
User Input Received:
"松鼠有几只?一二三四五!"
Return Value:
"松鼠有几只?一二三四五!"
Converted:
"�I��"
}
Got Return Value: "松鼠有几只?一二三四五!"
[typhoon@localhost sandbox]$
- 一个定制的 alias 设置能减少很多手工输入,降低出错的几率: