Mac及Android环境下的JNI学习

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

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

Mac及Android环境下的JNI学习

简介

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(

猜你喜欢

转载自blog.csdn.net/rgjtfc/article/details/83956872