1. 程式人生 > >Mac及Android環境下的JNI學習

Mac及Android環境下的JNI學習

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

Mac及Android環境下的JNI學習

4 人閱讀   發表回覆

簡介

JNI就是Java Native Interface, 也可以理解為一般指令碼語言的C API, 一般情況下這種API的學習都是一種痛苦的精力, 從來如此, 沒有太多技術含量, 就是一堆晦澀難以理解的程式設計模型, 程式設計介面, 充斥著各種從當前語言到C語言的型別轉換. 基本的含義就是用C語言的思維去表示當前的語言, 這個問題在Lua語言中到達了極致. 不管是多麼為了效率, 一個純堆疊操作的程式設計介面都像組合語言一樣難以使用.
因為最近又開始做Android遊戲了, 用的是cocos2d-x, JNI是難以避免了, 以前的使用都是照貓畫虎似的寫幾個函式呼叫介面, 總感覺有問題, 今天好好的學習學習吧.
與一般關於JNI文章稍微有些不一樣的是, 本文會更多的關注於Android相關的問題.

本文使用的環境是: 
Mac OS X 10.8.3
java version “1.6.0_43″
Java(TM) SE Runtime Environment (build 1.6.0_43-b01-447-11M4203)
Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01-447, mixed mode)
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Android SDK API 17
Android ndk r8d

從Java中呼叫C/C++庫

從Java中呼叫C/C++庫的典型使用場景就是在Android中Load自己寫的遊戲庫, 然後執行. 雖然有cocos2d-x引擎的時候你幾乎不用關心這個.

Hello World

從R&D開始, Hello World就成了一開始必用的例子了, 學習JNI也從這個開始吧.
首先構建一個最簡單的Java class:

// HelloWorld.javaimport java.lang.System.*;public class HelloWorld {  public native void SayHelloWorld();  public static void main(String[] args) {    System.loadLibrary("helloworld");    HelloWorld helloworld = new HelloWorld();    helloworld.SayHelloWorld();  }}

native關鍵字表示的介面就是需要用C/C++來實現的介面, System.loadLibrary呼叫的就是將會實現的jni庫.
然後用javac將檔案編譯成位元組碼:

javac HelloWorld.java

生成HelloWorld.class, java中最人性的一點就是把binding的生成直接作為標準了, 這一點比Python和lua要強多了, 也使得JNI的使用是我接觸過的語言中, 類似C API最方便的一個. 直接生成C/C++ binding標頭檔案的方式是用javah命令:

javah HelloWorld

從上面的HelloWorld類生成的標頭檔案如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld#ifdef __cplusplusextern "C" {#endif  /*   * Class:     HelloWorld   * Method:    SayHelloWorld   * Signature: ()V   */  JNIEXPORT void JNICALL Java_HelloWorld_SayHelloWorld    (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif

這實在是相當人性化了, 我們只需要包含HelloWorld.h標頭檔案, 然後實現按照標頭檔案給的簽名實現Java_HelloWorld_SayHelloWorld函式就行了, 而不用自己去記住這麼複雜的函式名和引數. 實現的HelloWorld.cc檔案如下:

// HelloWorld.cc#include "HelloWorld.h"#include <cstdio>JNIEXPORT void JNICALL Java_HelloWorld_SayHelloWorld(JNIEnv *, jobject) {  printf("HelloWorld.\n");}

編譯C++程式碼的時候在MacOS下和在Linux, Windows有所不同, 不是編譯成.so或者dll, 而是MacOS自己的jnilib. 並且jni.h的目錄也比較特殊, 是/System/Library/Frameworks/JavaVM.framework/Headers/, 這個需要稍微注意一下, 具體的命令如下:

g++ -dynamiclib -o libhelloworld.jnilib HelloWorld.cc -framework JavaVM -I/System/Library/Frameworks/JavaVM.framework/Headers

此時一切就緒:

>java HelloWorldHelloWorld.

帶引數的函式

兩個語言之間的來回呼叫, 在沒有任何引數的情況下還好, 有了引數以後, 因為牽涉到兩個語言對型別的不同表示方式, 需要進行型別轉換, 是最麻煩的地方, 比如在Lua, Python中, 使用C API時, 你就需要記住Lua和Python的各種型別分別對應C語言中的哪個型別, JAVA中在呼叫C/C++函式時, 在Java中通過javah部分緩解了這個問題, 可以讓我們直接知道對應的型別是哪一個, 不過具體每個用C語言表示的JAVA型別該怎麼用, 還是查文件吧, 比如下面這個例子:

// ArugmentTest.javaimport java.lang.System.*;public class ArgumentTest {  public native int intMethod(int n);  public native boolean booleanMethod(boolean bool);  public native String stringMethod(String text);  public native int intArrayMethod(int[] intArray);  public static void main(String[] args) {    System.loadLibrary("argumenttest");    ArgumentTest obj = new ArgumentTest();    System.out.println("intMethod: " + obj.intMethod(5));    System.out.println("booleanMethod: " + obj.booleanMethod(true));    System.out.println("stringMethod: " + obj.stringMethod("JAVA"));    System.out.println("intArrayMethod: " + obj.intArrayMethod(new int[]{1,2,3,4,5}));  }}

編譯後, 用javah生成的標頭檔案如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class ArgumentTest */#ifndef _Included_ArgumentTest#define _Included_ArgumentTest#ifdef __cplusplusextern "C" {#endif/* * Class:     ArgumentTest * Method:    intMethod * Signature: (I)I */JNIEXPORT jint JNICALL Java_ArgumentTest_intMethod  (JNIEnv *, jobject, jint);/* * Class:     ArgumentTest * Method:    booleanMethod * Signature: (Z)Z */JNIEXPORT jboolean JNICALL Java_ArgumentTest_booleanMethod  (JNIEnv *, jobject, jboolean);/* * Class:     ArgumentTest * Method:    stringMethod * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_ArgumentTest_stringMethod  (JNIEnv *, jobject, jstring);/* * Class:     ArgumentTest * Method:    intArrayMethod * Signature: ([I)I */JNIEXPORT jint JNICALL Java_ArgumentTest_intArrayMethod  (JNIEnv *, jobject, jintArray);#ifdef __cplusplus}#endif#endif

這裡我們可以看到, 大概的型別對應關係:
Object=>jobject
int=>jint
boolean=>jboolean
String=>jstring
int[]=>jintArray

但是, jobject, jstring, jintArray具體怎麼使用, 總歸得再學習一遍. 但是javah的存在還是非常有意義的, 起碼我們不用記哪個型別該查什麼文件了.
實現的C++檔案如下:

// ArgumentTest.cc#include "ArgumentTest.h"#include <cstring>using namespace std;JNIEXPORT jint JNICALL Java_ArgumentTest_intMethod(JNIEnv *env, jobject obj, jint num) {  return num * num; }JNIEXPORT jboolean JNICALL Java_ArgumentTest_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean) {  return !boolean;}JNIEXPORT jstring JNICALL Java_ArgumentTest_stringMethod(JNIEnv *env, jobject obj, jstring str) {  const char *cstr = env->GetStringUTFChars(str, 0);  char cap[128] = "language: ";  strcat(cap, cstr);  env->ReleaseStringUTFChars(str, cstr);  return env->NewStringUTF(cap);}JNIEXPORT jint JNICALL Java_ArgumentTest_intArrayMethod(JNIEnv *env, jobject obj, jintArray array) {  jsize len = env->GetArrayLength(array);  jint *body = env ->GetIntArrayElements(array, 0);  int sum = 0;  for (int i=0; i<len; ++i) {    sum += body[i];  }  env->ReleaseIntArrayElements(array, body, 0);  return sum;}

這裡可以看到幾個特殊的函式, GetStringUTFChars, GetArrayLength, GetIntArrayElements, ReleaseIntArrayElements等, 還好都不算太複雜. 一旦用了JNI, 需要注意的就是, 你資源的分配釋放, 就得和C/C++中一樣了, 得自己手動來.
另外, 還值得一提的是, 因為C++對類的直接支援, 所以C++中可以用比C語言更簡潔的語法, 大概的區別看了下面的示例:
C程式碼: (*env)->GetStringUTFChars(env, string, 0);
C++程式碼: env->GetStringUTFChars(string, 0);

Android中的情況

其實實現和使用方式都類似, 只是編譯時, 需要使用不同的命令, 其實因為Android其實就是一種特殊的Linux, 所以對於Android來說, 生成方式和Linux類似, 並且都是生成Unix/Linux通用的.so動態庫檔案.
另外, 還有一些典型的Android的問題, 比如在Android中去完成前面的ArugmentTest:

import android.util.Log;import android.app.Activity;import android.view.View;import android.os.Bundle;public class ArgumentTest extends Activity{  private static final String LOG_TAG = "ArugmenetTest";  public native int intMethod(int n);  public native boolean booleanMethod(boolean bool);  public native String stringMethod(String text);  public native int intArrayMethod(int[] intArray);    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        /* Create a TextView and set its content.         * the text is retrieved by calling a native         * function.         */        Log.v(LOG_TAG, "begin.\n");        View v = new View(this);        setContentView(v);        Log.v(LOG_TAG, "intMethod: " + this.intMethod(5));        Log.v(LOG_TAG, "booleanMethod: " + this.booleanMethod(true));        Log.v(