JNI Kickstart 小結 01

JNI 程序小結 01

Java + JNI 本地庫 異構程序構建示例。
下文 (增強版) : http://my.oschina.net/typhoon/blog/470904java


  1. 建立 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]$

    文件內容明細:
     
    1. 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();
      		}
      	}
      
      }
    2. 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.");
      	}
      
      }
    3. 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();
      
      }
    4. 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);
      
      }
  2. 將 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]$
  3. 爲將要調用本地庫的 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」,爲了下降記錯、輸錯文件名的概率,將類名、本地庫名、頭文件名統一規格,這是推薦的作法。

    文件內容明細:
     
    1. 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
    2. 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
  4. 建立 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 語言源代碼文件名統一規格,這是推薦的作法。

    文件內容明細:
     
    1. 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;
      };
    2. 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;
      };
  5. 將 C 語言源代碼文件編譯成本地庫文件 (本例中爲 *.so 共享對象文件):

    1. 一個定製的 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]$
    2. 編譯:
       
      [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」。
     
  6. 運行程序:

    1. 一個定製的 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]$
    2. 加載此 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]$
相關文章
相關標籤/搜索