【转】android JNI编程 一些技巧(整理)

原文网址:http://blog.csdn.net/linweig/article/details/5203716

本篇将介绍在JNI编程中如何传递参数和返回值。

首先要强调的是,native方法不但可以传递Java的基本类型做参数,还可以传递更复杂的类型,比如String,数组,甚至自定义的类。这一切都可以在jni.h中找到答案。

1. Java基本类型的传递

用过Java的人都知道,Java中的基本类型包括boolean,byte,char,short,int,long,float,double 这样几种,如果你用这几种类型做native方法的参数,当你通过javah -jni生成.h文件的时候,只要看一下生成的.h文件,就会一清二楚,这些类型分别对应的类型是 jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble 。这几种类型几乎都可以当成对应的C++类型来用,所以没什么好说的。

2. String参数的传递

Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦。先看一个例子,

class Prompt {

// native method that prints a prompt and reads a line

private native String getLine(String prompt);

public static void main(String args[]) {

Prompt p = new Prompt();

String input = p.getLine("Type a line: ");

System.out.println("User typed: " + input);

}

static {

System.loadLibrary("Prompt");

}

}

在这个例子中,我们要实现一个native方法
String getLine(String prompt);
读入一个String参数,返回一个String值。

通过执行javah -jni得到的头文件是这样的

#include <jni.h>

#ifndef _Included_Prompt

#define _Included_Prompt

#ifdef __cplusplus

extern "C" {

#endif

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);

#ifdef __cplusplus

}

#endif

#endif

jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。如果你用
cout << prompt << endl;
编译器肯定会扔给你一个错误信息的。

其实要处理jstring有很多种方式,这里只讲一种我认为最简单的方式,看下面这个例子,

#include "Prompt.h"

#include <iostream>

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)

{

const char* str;

str = env->GetStringUTFChars(prompt, false);

if(str == NULL) {

return NULL; /* OutOfMemoryError already thrown */

}

std::cout << str << std::endl;

env->ReleaseStringUTFChars(prompt, str);

char* tmpstr = "return string succeeded";

jstring rtstr = env->NewStringUTF(tmpstr);

return rtstr;

}

在上面的例子中,作为参数的prompt不能直接被C++程序使用,先做了如下转换
str = env->GetStringUTFChars(prompt, false);
将jstring类型变成一个char*类型。

返回的时候,要生成一个jstring类型的对象,也必须通过如下命令,
jstring rtstr = env->NewStringUTF(tmpstr);

这里用到的GetStringUTFChars和NewStringUTF都是JNI提供的处理String类型的函数,还有其他的函数这里就不一一列举了。

3. 数组类型的传递

和String一样,JNI为Java基本类型的数组提供了j*Array类型,比如int[]对应的就是jintArray。来看一个传递int数组的例子,Java程序就不写了,

JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)

{

jint *carr;

carr = env->GetIntArrayElements(arr, false);

if(carr == NULL) {

return 0; /* exception occurred */

}

jint sum = 0;

for(int i=0; i<10; i++) {

sum += carr[i];

}

env->ReleaseIntArrayElements(arr, carr, 0);

return sum;

}

这个例子中的GetIntArrayElements和ReleaseIntArrayElements函数就是JNI提供用于处理int数组的函 数。如果试图用arr[i]的方式去访问jintArray类型,毫无疑问会出错。JNI还提供了另一对函数GetIntArrayRegion和 ReleaseIntArrayRegion访问int数组,就不介绍了,对于其他基本类型的数组,方法类似。

4. 二维数组和String数组

在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。仍然用一个例子来说明,这次是一个二维int数组,作为返回值。

JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)

{

jobjectArray result;

jclass intArrCls = env->FindClass("[I");

result = env->NewObjectArray(size, intArrCls, NULL);

for (int i = 0; i < size; i++) {

jint tmp[256]; /* make sure it is large enough! */

jintArray iarr = env->NewIntArray(size);

for(int j = 0; j < size; j++) {

tmp[j] = i + j;

}

env->SetIntArrayRegion(iarr, 0, size, tmp);

env->SetObjectArrayElement(result, i, iarr);

env->DeleteLocalRef(iarr);

}

return result;

}

上面代码中的第三行,
jobjectArray result;
因为要返回值,所以需要新建一个jobjectArray对象。

jclass intArrCls = env->FindClass("[I");
是创建一个jclass的引用,因为result的元素是一维int数组的引用,所以intArrCls必须是一维int数组的引用,这一点是如何保证的 呢?注意FindClass的参数"[I",JNI就是通过它来确定引用的类型的,I表示是int类型,[标识是数组。对于其他的类型,都有相应的表示方 法,

Z boolean 
B byte 
C char 
S short 
I int 
J long 
F float 
D double

String是通过“Ljava/lang/String;”表示的,那相应的,String数组就应该是“[Ljava/lang/String;”。

还是回到代码,
result = env->NewObjectArray(size, intArrCls, NULL);
的作用是为result分配空间。

jintArray iarr = env->NewIntArray(size);
是为一维int数组iarr分配空间。

env->SetIntArrayRegion(iarr, 0, size, tmp);
是为iarr赋值。

env->SetObjectArrayElement(result, i, iarr);
是为result的第i个元素赋值。

通过上面这些步骤,我们就创建了一个二维int数组,并赋值完毕,这样就可以做为参数返回了。

如果了解了上面介绍的这些内容,基本上大部分的任务都可以对付了。虽然在操作数组类型,尤其是二维数组和String数组的时候,比起在单独的语言中编程要麻烦,但既然我们享受了跨语言编程的好处,必然要付出一定的代价。

有一点要补充的是,本文所用到的函数调用方式都是针对C++的,如果要在C中使用,所有的env->都要被替换成(*env)->,而 且后面的函数中需要增加一个参数env,具体请看一下jni.h的代码。另外还有些省略的内容,可以参考JNI的文档:Java Native Interface 6.0 Specification,在JDK的文档里就可以找到。如果要进行更深入的JNI编程,需要仔细阅读这个文档。接下来的高级篇,也会讨论更深入的话 题。

关于JNI编程更深入的话题,包括:在native方法中访问Java类的域和方法,将Java中自定义的类作为参数和返回值传递等等。了解这些内容,将会对JNI编程有更深入的理解,写出的程序也更清晰,易用性更好。

1. 在一般的Java类中定义native方法

在前两篇的例子中,都是将native方法放在main方法的Java类中,实际上,完全可以在任何类中定义native方法。这样,对于外部来说,这个类和其他的Java类没有任何区别。

2. 访问Java类的域和方法

native方法虽然是native的,但毕竟是方法,那么就应该同其他方法一样,能够访问类的私有域和方法。实际上,JNI的确可以做到这一点,我们通过几个例子来说明,

public class ClassA {

String str_ = "abcde";

int number_;

public native void nativeMethod();

private void javaMethod() {

System.out.println("call java method succeeded");

}

static {

System.loadLibrary("ClassA");

}

}

在这个例子中,我们在一个没有main方法的Java类中定义了native方法。我们将演示如何在nativeMethod()中访问域str_,number_和方法javaMethod(),nativeMethod()的C++实现如下,

JNIEXPORT void JNICALL Java_testclass_ClassCallDLL_nativeMethod(JNIEnv *env, jobject obj) {

// access field

jclass cls = env->GetObjectClass(obj);

jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");

jstring jstr = (jstring)env->GetObjectField(obj, fid);

const char *str = env->GetStringUTFChars(jstr, false);

if(std::string(str) == "abcde")

std::cout << "access field succeeded" << std::endl;

jint i = 2468;

fid = env->GetFieldID(cls, "number_", "I");

env->SetIntField(obj, fid, i);

// access method

jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");

env->CallVoidMethod(obj, mid);

}

上面的代码中,通过如下两行代码获得str_的值,
jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");
jstring jstr = (jstring)env->GetObjectField(obj, fid);

第一行代码获得str_的id,在GetFieldID函数的调用中需要指定str_的类型,第二行代码通过str_的id获得它的值,当然我们读到的是一个jstring类型,不能直接显示,需要转化为char*类型。

接下来我们看如何给Java类的域赋值,看下面两行代码,
fid = env->GetFieldID(cls, "number_", "I");
env->SetIntField(obj, fid, i);

第一行代码同前面一样,获得number_的id,第二行我们通过SetIntField函数将i的值赋给number_,其他类似的函数可以参考JDK的文档。

访问javaMethod()的过程同访问域类似,
jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");
env->CallVoidMethod(obj, mid);

需要强调的是,在GetMethodID中,我们需要指定javaMethod方法的类型,域的类型很容易理解,方法的类型如何定义呢,在上面的例子中,我们用的是()V,V表示返回值为空,()表示参数为空。如果是更复杂的函数类型如何表示?看一个例子,
long f (int n, String s, int[] arr);
这个函数的类型符号是(ILjava/lang/String;[I)J,I表示int类型,Ljava/lang/String;表示String类型,[I表示int数组,J表示long。这些都可以在文档中查到。

3. 在native方法中使用用户定义的类

JNI不仅能使用Java的基础类型,还能使用用户定义的类,这样灵活性就大多了。大体上使用自定义的类和使用Java的基础类(比如 String)没有太大的区别,关键的一点是,如果要使用自定义类,首先要能访问类的构造函数,看下面这一段代码,我们在native方法中使用了自定义 的Java类ClassB,

jclass cls = env->FindClass("Ltestclass/ClassB;");

jmethodID id = env->GetMethodID(cls, "<init>", "(D)V");

jdouble dd = 0.033;

jvalue args[1];

args[0].d = dd;

jobject obj = env->NewObjectA(cls, id, args);

首先要创建一个自定义类的引用,通过FindClass函数来完成,参数同前面介绍的创建String对象的引用类似,只不过类名称变成自定义类的 名称。然后通过GetMethodID函数获得这个类的构造函数,注意这里方法的名称是"<init>",它表示这是一个构造函数。

jobject obj = env->NewObjectA(cls, id, args);
生成了一个ClassB的对象,args是ClassB的构造函数的参数,它是一个jvalue*类型。

通过以上介绍的三部分内容,native方法已经看起来完全像Java自己的方法了,至少主要功能上齐备了,只是实现上稍麻烦。而了解了这 些,JNI编程的水平也更上一层楼。下面要讨论的话题也是一个重要内容,至少如果没有它,我们的程序只能停留在演示阶段,不具有实用价值。

4. 异常处理

在C++和Java的编程中,异常处理都是一个重要的内容。但是在JNI中,麻烦就来了,native方法是通过C++实现的,如果在native方法中发生了异常,如何传导到Java呢?

JNI提供了实现这种功能的机制。我们可以通过下面这段代码抛出一个Java可以接收的异常,

jclass errCls;

env->ExceptionDescribe();

env->ExceptionClear();

errCls = env->FindClass("java/lang/IllegalArgumentException");

env->ThrowNew(errCls, "thrown from C++ code");

如果要抛出其他类型的异常,替换掉FindClass的参数即可。这样,在Java中就可以接收到native方法中抛出的异常。

时间: 2024-07-28 12:33:16

【转】android JNI编程 一些技巧(整理)的相关文章

Android jni 编程入门

本文将介绍如何使用eclipse和ndk-build来编写一个基于Android4.4版本的包含有.so动态库的安卓程序. 前提是已经安装和配置好了诸如SDK,NDK等编译环境.下面开始编程! 1 程序逻辑 我们要编写的程序包含两部分:java部分--负责界面和调用JNI native函数:JNI native 部分--负责native函数的具体实现(本文使用C语言). native 函数伪代码如下: ? 1 2 3 4 5 6 7 8 /* funtion: 传入两个整形变量,计算他们之和 r

Android jni 编程3(对基本类型一维整型数组的操作)总结版

主要学习资料:黑马程序员的NDK方法使用(生产类库so)              jni编程指南中文版(已上传至博客园) 博主文章(它使用的是VS和eclipse联合开发):http://www.cnblogs.com/activity-life/p/3643047.html //0.传入一维整型数组,无返回值(但已对数组进行了修改) public native void arrayEncode(int[] arr); //1.传入一维整型数组,数组长度(因为c不容易获取而Java方便),返回

【转】Android JNI编程—JNI基础

原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它们都用到了JNI编程,并且JNI编程也贯穿了Android系统,学会JNI编程对于我们学习研究Android源码.Android安全以及Android安全加固等都是有所帮助的.但是对于我们这些写Android应用的,大部分时间都是在使用Java编程,很少使用C/C++编程,对于JNI编程也了解的比较

Android JNI编程—JNI基础

什么是JNI,怎么使用 JNI--Java Native Interface,它是Java平台的一个特性(并不是Android系统特有的).其实主要是定义了一些JNI函数,让开发者可以通过调用这些函数实现Java代码调用C/C++的代码,C/C++的代码也可以调用Java的代码,这样就可以发挥各个语言的特点了.那么怎么使用JNI呢,一般情况下我们首先是将写好的C/C++代码编译成对应平台的动态库(windows一般是dll文件,linux一般是so文件等),这里我们是针对Android平台,所以

Android JNI编程(七)——使用AndroidStudio编写第一个JNI程序

目录(?)[+] 1.简单介绍一下NDK和JNI NDK:NDK是Native Development Kit的缩写,是Google提供的一套工具集,可以让你其他语言(C.C++或汇编)开发 Android的 JNI.NDK可以编译多平台的so,开发人员只需要简单修改 mk 文件说明需要的平台,不需要改动任何代码,NDK就可以帮你编译出所需的so库. JNI:JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++) NDK网

Android jni 编程1(对基本类型字符串的操作)

最近一直在学安卓的jni,主要参考的是黑马程序员的视频教程,讲的确实不错. 那就自己总结一下吧,算是对学习的复习. 这篇博客也主要参考了这位博主:http://www.cnblogs.com/activity-life/p/3643047.html,在这里谢谢了 通过他的博客和视频教程,我结合了两者的方法,把功能实现了,毕竟这个是应用为主,自己学艺不精,这里积累下: 第一篇博客的总结所有Java调取jni是关于字符串的操作,主要包括以下三种类型: //对基本数据类型字符串的访问 //0.无输入,

Android JNI编程(一)——JNI概念以及C语言Dev-C++开发环境搭建、编写HelloWorld

目录(?)[+] 一:JNI是什么呢? JNI:JNI是JavaNative Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++) ,简单来说就是Java调C,C调Java. 二:开始搭建我们的开发环境,也是非常的简单的 Dev-C++工具下载,安装一路默认到Finish就ok了. 工欲善其事,必先利其器. 调整字体大小:菜单栏下 Tools–>Editor Options这里面的Font和Color就可以更改字体和颜色了 快捷键查看与修改:菜单栏

Android JNI编程(二)——C语言的基本数据类型,输出函数,输入函数

目录(?)[+] 在学习C语言数据类型之前,我们先来回顾一下Java中的基本数据类型和其特点 一:基本数据类型 1.在Java中有八大基本数据类型和一个引用数据(String)类型如下: byte 1字节 char 2字节 short 2字节 int 4字节 float 4字节 long 8字节 double 8字节 boolean 2.那在C语言中又有哪些类型呢?我们可以来看一下 int 4字节 char 1字节 float 4字节 double 8字节 long 4字节 short 2字节

Android JNI编程(三)——C语言指针的初步认识、指针变量、互换两个数、函数返回多个值

目录(?)[+] 一.什么是指针? 简单来说: 指针就是内存地址      内存地址就是指针.来看个小案例 #include<stdio.h> #include<stdlib.h> /** 指针就是内存地址 内存地址就是指针 */ main() { //定义一个int类型的变量i,并且赋值为10 int i=10; //定义一个int类型的一级指针变量p int* p; //把i对应的地址给p变量 p=&i; //指针取值*p:把p变量地址对应的值取出来 printf(&q