Android中c++调用java
java类的实例化
都知道java需要 obj var = new obj();这样一个过程,我们在C++中调用java类的成员函数,当然也要先示例话一个类。
示例化的函数如下所示
jobject getInstance(JNIEnv* env, jclass obj_class)
{
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, construction_id);
return obj;
}
这个函数中的env表示环境参数,jclass表示一个java类的句柄。
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
GetMethodID的参数分别为(类句柄,方法名称,参数名称)
这个是为了获取java类中某个方法的句柄,有一点需要特别注意的,在获取构造方法的句柄和别的方法的句柄是不一样的。
获取一般方法的句柄所填写“方法名称”参数直接就是这个方法的名称,而构造函数的话就必须填写"<init>"。除了这点区别外,就没有区别了。
而我们的“参数名称”似乎写的就有些奇怪了。但是细说下他的规律也就不怪了。
参数名称的书写规则为 (参数1类型,参数2类型….)返回类型。而相应类型的表示如下所示
Java类型
本地类型
JNI中定义的别名
int
long
jint
long
_int64
jlong
byte
signed char
jbyte
boolean
unsigned char
jboolean
char
unsigned short
jchar
short
short
jshort
float
float
jfloat
double
double
jdouble
Object
_jobject*
jobject
比如我们要找个int func(double)类型的函数,就该这么写:
jmethodID construction_id = env->GetMethodID(obj_class, " func ", "(D)I");
找到了构造函数的方法之后我们直接调用构造函数,然后把生成的类返回即可
jobject obj = env->NewObject(obj_class, construction_id);
其中obj_class为类的句柄construction_id为构造函数的句柄,而类的实例被该函数返回。
5.3 调用java类的其他函数
JNIEXPORT jstring JNICALL Java_com_hm_hello_CActivityMain_stringFromJNI
(JNIEnv* env, jobject)
{
jstring str;
jclass java_class = env->FindClass("com/hm/hello/CForCall");
if (java_class == 0)
{
return env->NewStringUTF("not find class!");
}
jobject java_obj = getInstance(env, java_class);
if (java_obj == 0)
{
return env->NewStringUTF("not find java OBJ!");
}
jmethodID java_method = env->GetMethodID(java_class, "GetJavaString", "()Ljava/lang/String;");
if(java_method == 0)
{
return env->NewStringUTF("not find java method!");
}
str = (jstring)env->CallObjectMethod(java_obj, java_method);
return str;
}
看完了构造函数的介绍,这段代码其实很容易就看懂了,我们发现调用java中的函数的步骤无非几步:
1找到类句柄
2找到类的方法的句柄
3实例化类
4调用实例化类的方法。
而以上代码中只有一段没说过,就是
jclass java_class = env->FindClass("com/hm/hello/CForCall");
看名字就知道,是在找相应的类的句柄,其中准备调用类的完整包名(把.转换为/),而CforCall就是我们要调用的类的名称。
至此,编译,然后在Android调试,可爱的机器人又出现了,哈哈,屏幕上显示出现了12345
成功!!!!
Android NDK开发之Jni调用Java对象
本地代码中使用Java对象
通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域 和方法的函数的必须参数。
下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
函数
描述
GetFieldID
得到一个实例的域的ID
GetStaticFieldID
得到一个静态的域的ID
GetMethodID
得到一个实例的方法的ID
GetStaticMethodID
得到一个静态方法的ID
构造一个Java对象的实例
jclass cls = (*env)->FindClass(env, "Lpackagename/classname;"); //创建一个class的引用
jmethodID id = (*env)->GetMethodID(env, cls, "", "(D)V"); //注意这里方法的名称是"",它表示这是一个构造函数,而且构造参数是double型的
jobject obj = (*env)->NewObjectA(env, cls, id, args); //获得一实例,args是构造函数的参数,它是一个jvalue*类型
首先是获得一个Java类的class引用 (*env)->FindClass(env, "Lpackagename/classname;"); 请注意参数:Lpackagename/classname; ,L代表这是在描述一个对象类型,packagename/classname是该对象耳朵class路径,请注意一定要以分号(;)结束!
然后是获取函数的id,jmethodID id = env->GetMethodID(cls, "", "(D)V"); 第一个是刚刚获得的class引用,第二个是方法的名称,最后一个就是方法的签名了
还是不懂?我曾经如此,请接着看...
还是不懂?我曾经如此,请接着看...
难理解的函数签名
JNINativeMethod的定义如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
那其他情况呢?请查看下表:
类型
符号
boolean
Z
byte
B
char
C
short
S
int
I
long
L
float
F
double
D
void
V
object对象
LClassName; L类名;
Arrays
[array-type [数组类型
methods方法
(argument-types)return-type (参数类型)返回类型
稍稍补充一下:
1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则
比如说 为“java/lang/String”,为"Lcom /nedu/jni/helloword/Student;"
2、方法参数或者返回值为数组类型时,请前加上[
例如[I表示 int[],[[[D表示 double[][][],即几维数组就加几个[
在本地方法中调用Java对象的方法
1、获取你需要访问的Java对象的类:
jclass cls = (*env)->GetObjectClass(env, obj); // 使用GetObjectClass方法获取obj对应的jclass。
jclass cls = (*env)->FindClass(“android/util/log”) // 直接搜索类名,需要是static修饰的类。
2、获取MethodID:
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V"); //GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID
其参数的意义:
env-->JNIEnv
cls-->第一步获取的jclass
"callback"-->要调用的方法名
"(I)V"-->方法的Signature, 签名同前面的JNI规则。
3、调用方法:
(*env)->CallVoidMethod(env, obj, mid, depth);// CallStaticIntMethod(….) , 调用静态方法
使用CallVoidMethod方法调用方法。参数的意义:
env-->JNIEnv
obj-->通过本地方法穿过来的jobject
mid-->要调用的MethodID(即第二步获得的MethodID)
depth-->方法需要的参数(对应方法的需求,添加相应的参数)
注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。
CallVoidMethod CallStaticVoidMethod
CallIntMethod CallStaticVoidMethod
CallBooleanMethod CallStaticVoidMethod
CallByteMethod CallStaticVoidMethod
现在稍稍明白文章开始构造Java对象那个实例了吧?让我们继续深入一下:
Jni操作Java的String对象
从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做 char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv提供的方法转换。
const char *str = (*env)->GetStringUTFChars(env, jstr, 0);
(*env)->ReleaseStringUTFChars(env, jstr, str);
这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是Jni访问String对象的一些方法:
• GetStringUTFChars 将jstring转换成为UTF-8格式的char*
• GetStringChars 将jstring转换成为Unicode格式的char*
• ReleaseStringUTFChars 释放指向UTF-8格式的char*的指针
• ReleaseStringChars 释放指向Unicode格式的char*的指针
• NewStringUTF 创建一个UTF-8格式的String对象
• NewString 创建一个Unicode格式的String对象
• GetStringUTFLength 获取UTF-8格式的char*的长度
• GetStringLength 获取Unicode格式的char*的长度
下面提供两个String对象和char*互转的方法:
/* c/c++ string turn to java jstring */
jstring charToJstring(JNIEnv* env, const char* pat)
{
jclass strClass = (*env)->FindClass(env, "java/lang/String");
jmethodID ctorID = (*env)->GetMethodID(env, strClass, "", "([BLjava/lang/String;)V");
jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat));
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = (*env)->NewStringUTF(env, "UTF-8");
return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes, encoding);
}
/* java jstring turn to c/c++ char* */
char* jstringToChar(JNIEnv* env, jstring jstr)
{
char* pStr = NULL;
jclass jstrObj = (*env)->FindClass(env, "java/lang/String");
jstring encode = (*env)->NewStringUTF(env, "utf-8");
jmethodID methodId = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);
jsize strLen = (*env)->GetArrayLength(env, byteArray);
jbyte *jBuf = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (jBuf > 0)
{
pStr = (char*)malloc(strLen + 1);
if (!pStr)
{
return NULL;
}
memcpy(pStr, jBuf, strLen);
pStr[strLen] = 0;
}
env->ReleaseByteArrayElements(byteArray, jBuf, 0);
return pStr;
}
如何在C/C++中调用Java
Java跨平台的特性使Java越来越受开发人员的欢迎,但也往往会听到不少的抱怨:用Java开发的图形用户窗口界面每次在启动的时候都会跳出一个控制 台窗口,这个控制台窗口让本来非常棒的界面失色不少。怎么能够让通过Java开发的GUI程序不弹出Java的控制台窗口呢?其实现在很多流行的开发环境 例如JBuilder、Eclipse都是使用纯Java开发的集成环境。这些集成环境启动的时候并不会打开一个命令窗口,因为它使用了JNI(Java Native Interface)的技术。通过这种技术,开发人员不一定要用命令行来启动Java程序,可以通过编写一个本地GUI程序直接启动Java程序,这样就 可避免另外打开一个命令窗口,让开发的Java程序更加专业。
JNI允许运行在虚拟机的Java程序能够与其它语言(例如C和C++)编写的程序或者类库进行相互间的调用。同时JNI提供的一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。
本 文将介绍如何在C/C++中调用Java方法,并结合可能涉及到的问题介绍整个开发的步骤及可能遇到的难题和解决方法。本文所采用的工具是Sun公司创建 的 Java Development Kit (JDK) 版本 1.3.1,以及微软公司的Visual C++ 6开发环境。
环境搭建
为 了让本文以下部分的代码能够正常工作,我们必须建立一个完整的开发环境。首先需要下载并安装JDK 1.3.1,其下载地址为“”。假设安装路径为C:\JDK。下一步就是设置集成开发环境,通过Visual C++ 6的菜单Tools→Options打开选项对话框。
将目录C:\JDK\include和C:\JDK\include \win32加入到开发环境的Include Files目录中,同时将C:\JDK\lib目录添加到开发环境的Library Files目录中。这三个目录是JNI定义的一些常量、结构及方法的头文件和库文件。集成开发环境已经设置完毕,同时为了执行程序需要把Java虚拟机所 用到的动态链接库所在的目录C:\JDK \jre\bin\classic设置到系统的Path环境变量中。这里需要提出的是,某些开发人员为了方便直接将JRE所用到的DLL文件直接拷贝到系 统目录下。这样做是不行的,将导致初始化Java虚拟机环境失败(返回值-1),原因是Java虚拟机是以相对路径来寻找所用到的库文件和其它一些相关文 件的。至此整个JNI的开发环境设置完毕,为了让此次JNI旅程能够顺利进行,还必须先准备一个Java类。在这个类中将用到Java中几乎所有有代表性 的属性及方法,如静态方法与属性、数组、异常抛出与捕捉等。我们定义的Java程序()如下,本文中所有的代码演示都将基于该Java 程序,代码如下:
package ;
/**
* 该类是为了演示JNI如何访问各种对象属性等
* @author liudong
*/
public class Demo {
//用于演示如何访问静态的基本类型属性
public static int COUNT = 8;
//演示对象型属性
public String msg;
private int[] counts;
public Demo() {
this(“缺省构造函数”);
}
/**
* 演示如何访问构造器
*/
public Demo(String msg) {
(“<init>:” + msg);
= msg;
= null;
}
/**
* 该方法演示如何访问一个访问以及中文字符的处理
*/
public String getMessage() {
return msg;
}
/**
* 演示数组对象的访问
*/
public int[] getCounts() {
return counts;
}
/**
* 演示如何构造一个数组对象
*/
public void setCounts(int[] counts) {
= counts;
}
/**
* 演示异常的捕捉
*/
public void throwExcp() throws IllegalAccessException {
throw new IllegalAccessException(“exception occur.”);
}
}
初始化虚拟机
本 地代码在调用Java方法之前必须先加载Java虚拟机,而后所有的Java程序都在虚拟机中执行。为了初始化Java虚拟机,JNI提供了一系列的接口 函数Invocation API。通过这些API可以很方便地将虚拟机加载到内存中。创建虚拟机可以用函数 jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args)。对于这个函数有一点需要注意的是,在JDK 1.1中第三个参数总是指向一个结构JDK1_ 1InitArgs, 这个结构无法完全在所有版本的虚拟机中进行无缝移植。在JDK 1.2中已经使用了一个标准的初始化结构JavaVMInitArgs来替代JDK1_1InitArgs。下面我们分别给出两种不同版本的示例代码。
在JDK 1.1初始化虚拟机:
#include <>
int main() {
JNIEnv *env;
JavaVM *jvm;
JDK1_1InitArgs vm_args;
jint res;
/* IMPORTANT: 版本号设置一定不能漏 */
vm_args.version = 0×00010001;
/*获取缺省的虚拟机初始化参数*/
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* 添加自定义的类路径 */
sprintf(classpath, “%s%c%s”,
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/*设置一些其他的初始化参数*/
/* 创建虚拟机 */
res = JNI_CreateJavaVM(&jvm,&env,&vm_args);
if (res < 0) {
fprintf(stderr, “Can’t create Java VM\n”);
exit(1);
}
/*释放虚拟机资源*/
(*jvm)->DestroyJavaVM(jvm);
}
JDK 1.2初始化虚拟机:
/* */
#include <>
int main() {
int res;
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[3];
vm_args.version=JNI_VERSION_1_2;//这个字段必须设置为该值
/*设置初始化参数*/
options[0].optionString = “-=NONE”;
options[1].optionString = “-=.”;
options[2].optionString = “-verbose:jni”; //用于跟踪运行时的信息
/*版本号设置不能漏*/
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 3;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (res < 0) {
fprintf(stderr, “Can’t create Java VM\n”);
exit(1);
}
(*jvm)->DestroyJavaVM(jvm);
fprintf(stdout, “Java VM destory.\n”);
}
为了保证JNI代码的可移植性,建议使用JDK 1.2的方法来创建虚拟机。JNI_CreateJavaVM函数的第二个参数JNIEnv *env,就是贯穿整个JNI始末的一个参数,因为几乎所有的函数都要求一个参数就是JNIEnv *env。
访问类方法
初始化了Java虚拟机后,就可以开始调用Java的方法。要调用一个Java对象的方法必须经过几个步骤:
1.获取指定对象的类定义(jclass)
有两种途径来获取对象的类定义:第一种是在已知类名的情况下使用FindClass来查找对应的类。但是要注意类名并不同于平时写的Java代码,例如要得到类的定义必须调用如下代码:
jclass cls = (*env)->FindClass(env, “jni/test/Demo”); //把点号换成斜杠
然后通过对象直接得到其所对应的类定义:
jclass cls = (*env)-> GetObjectClass(env, obj);
//其中obj是要引用的对象,类型是jobject
2.读取要调用方法的定义(jmethodID)
我们先来看看JNI中获取方法定义的函数:
jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name,
const char *sig);
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass class, const char
*name, const char *sig);
这 两个函数的区别在于GetStaticMethodID是用来获取静态方法的定义,GetMethodID则是获取非静态的方法定义。这两个函数都需要提 供四个参数:env就是初始化虚拟机得到的JNI环境;第二个参数class是对象的类定义,也就是第一步得到的obj;第三个参数是方法名称;最重要的 是第四个参数,这个参数是方法的定义。因为我们知道Java中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要第四个参数来指 定方法的具体定义。但是怎么利用一个字符串来表示方法的具体定义呢?JDK中已经准备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、 方法的定义。下面就来看看的定义:
打开命令行窗口并运行 javap -s -p 得到运行结果如下:
Compiled from
public class extends {
public static int COUNT;
/* I */
public msg;
/* Ljava/lang/String; */
private int counts[];
/* [I */
public ();
/* ()V */
public ();
/* (Ljava/lang/String;)V */
public getMessage();
/* ()Ljava/lang/String; */
public int getCounts()[];
/* ()[I */
public void setCounts(int[]);
/* ([I)V */
public void throwExcp() throws ;
/* ()V */
static {};
/* ()V */
}
我们看到类中每个属性和方法下面都有一段注释。注释中不包含空格的内容就是第四个参数要填的内容(关于javap具体参数请查询JDK的使用帮助)。下面这段代码演示如何访问的getMessage方法:
/*
假设我们已经有一个的实例obj
*/
jmethodID mid;
jclass cls = (*env)-> GetObjectClass (env, obj); //获取实例的类定义
mid=(*env)->GetMethodID(env,cls,"getMessage"," ()Ljava/lang/String; ");
/*如果mid为0表示获取方法定义失败*/
jstring msg = (*env)-> CallObjectMethod(env, obj, mid);
/*
如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可:
jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid);
*/
3.调用方法
为 了调用对象的某个方法,可以使用函数Call<TYPE>Method或者CallStatic<TYPE>Method(访问 类的静态方法),<TYPE>根据不同的返回类型而定。这些方法都是使用可变参数的定义,如果访问某个方法需要参数时,只需要把所有参数按照 顺序填写到方法中就可以。在讲到构造函数的访问时,将演示如何访问带参数的构造函数。
访问类属性
访问类的属性与访问类的方法大体上是一致的,只不过是把方法变成属性而已。
1.获取指定对象的类(jclass)
这一步与访问类方法的第一步完全相同,具体使用参看访问类方法的第一步。
2.读取类属性的定义(jfieldID)
在JNI中是这样定义获取类属性的方法的:
jfieldID (JNICALL *GetFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID (JNICALL *GetStaticFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
这两个函数中第一个参数为JNI环境;clazz为类的定义;name为属性名称;第四个参数同样是为了表达属性的类型。前面我们使用javap工具获取类的详细定义的时候有这样两行:
public msg;
/* Ljava/lang/String; */
其中第二行注释的内容就是第四个参数要填的信息,这跟访问类方法时是相同的。
3.读取和设置属性值
有 了属性的定义要访问属性值就很容易了。有几个方法用来读取和设置类的属性,它们是:Get<TYPE>Field、 Set<TYPE>Field、GetStatic<TYPE>Field、 SetStatic<TYPE>Field。比如读取Demo类的msg属性就可以用GetObjectField,而访问COUNT用 GetStaticIntField,相关代码如下:
jfieldID field = (*env)->GetFieldID(env,obj,"msg"," Ljava/lang/String;");
jstring msg = (*env)-> GetObjectField(env, cls, field); //msg就是对应Demo的msg
jfieldID field2 = (*env)->GetStaticFieldID(env,obj,"COUNT","I");
jint count = (*env)->GetStaticIntField(env,cls,field2);
访问构造函数
很 多人刚刚接触JNI的时候往往会在这一节遇到问题,查遍了整个看到这样一个函数NewObject,它应该是可以用来访问类的构造函数。但是该 函数需要提供构造函数的方法定义,其类型是jmethodID。从前面的内容我们知道要获取方法的定义首先要知道方法的名称,但是构造函数的名称怎么来填 写呢?其实访问构造函数与访问一个普通的类方法大体上是一样的,惟一不同的只是方法名称不同及方法调用时不同而已。访问类的构造函数时方法名必须填写 “<init>”。下面的代码演示如何构造一个Demo类的实例:
jclass cls = (*env)->FindClass(env, "jni/test/Demo");
/**
首先通过类的名称获取类的定义,相当于Java中的方法
*/
if (cls == 0)
<error handler>
jmethodID mid = (*env)->GetMethodID(env,cls,"<init>","(Ljava/lang/String;)V ");
if(mid == 0)
<error handler>
jobject demo = jenv->NewObject(cls,mid,0);
/**
访问构造函数必须使用NewObject的函数来调用前面获取的构造函数的定义
上面的代码我们构造了一个Demo的实例并传一个空串null
*/
数组处理
创建一个新数组
要 创建一个数组,我们首先应该知道数组元素的类型及数组长度。JNI定义了一批数组的类型j<TYPE>Array及数组操作的函数 New<TYPE>Array,其中<TYPE>就是数组中元素的类型。例如,要创建一个大小为10并且每个位置值分别为 1-10的整数数组,编写代码如下:
int i = 1;
jintArray array; //定义数组对象
(*env)-> NewIntArray(env, 10);
for(; i<= 10; i++)
(*env)->SetIntArrayRegion(env, array, i-1, 1, &i);
访问数组中的数据
访问数组首先应该知道数组的长度及元素的类型。现在我们把创建的数组中的每个元素值打印出来,代码如下:
int i;
/* 获取数组对象的元素个数 */
int len = (*env)->GetArrayLength(env, array);
/* 获取数组中的所有元素 */
jint* elems = (*env)-> GetIntArrayElements(env, array, 0);
for(i=0; i< len; i++)
printf("ELEMENT %d IS %d\n", i, elems[i]);
中文处理
中 文字符的处理往往是让人比较头疼的事情,特别是使用Java语言开发的软件,在JNI这个问题更加突出。由于Java中所有的字符都是Unicode编 码,但是在本地方法中,例如用VC编写的程序,如果没有特殊的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文 字符及Java访问本地方法产生的中文字符串,我定义了两个方法用来做相互转换。
· 方法一,将Java中文字符串转为本地字符串
/**
第一个参数是虚拟机的环境指针
第二个参数为待转换的Java字符串定义
第三个参数是本地存储转换后字符串的内存块
第三个参数是内存块的大小
*/
int JStringToChar(JNIEnv *env, jstring str, LPTSTR desc, int desc_len)
{
int len = 0;
if(desc==NULL||str==NULL)
return -1;
//在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型
wchar_t *w_buffer = new wchar_t[1024];
ZeroMemory(w_buffer,1024*sizeof(wchar_t));
//使用GetStringChars而不是GetStringUTFChars
wcscpy(w_buffer,env->GetStringChars(str,0));
env->ReleaseStringChars(str,w_buffer);
ZeroMemory(desc,desc_len);
//调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串
//关于函数WideCharToMultiByte的使用请参考MSDN
len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL);
//len = wcslen(w_buffer);
if(len>0 && len<desc_len)
desc[len]=0;
delete[] w_buffer;
return strlen(desc);
}
· 方法二,将C的字符串转为Java能识别的Unicode字符串
jstring NewJString(JNIEnv* env,LPCTSTR str)
{
if(!env || !str)
return 0;
int slen = strlen(str);
jchar* buffer = new jchar[slen];
int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);
if(len>0 && len < slen)
buffer[len]=0;
jstring js = env->NewString(buffer,len);
delete [] buffer;
return js;
}
异常
由 于调用了Java的方法,因此难免产生操作的异常信息。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java 中抛出的异常信息。之前我们在Demo类中定义了一个方法throwExcp,下面将访问该方法并捕捉其抛出来的异常信息,代码如下:
/**
假设我们已经构造了一个Demo的实例obj,其类定义为cls
*/
jthrowable excp = 0; /* 异常信息定义 */
jmethodID mid=(*env)->GetMethodID(env,cls,”throwExcp”,”()V”);
/*如果mid为0表示获取方法定义失败*/
jstring msg = (*env)-> CallVoidMethod(env, obj, mid);
/* 在调用该方法后会有一个IllegalAccessException的异常抛出 */
excp = (*env)->ExceptionOccurred(env);
if(excp){
(*env)->ExceptionClear(env);
//通过访问excp来获取具体异常信息
/*
在Java中,大部分的异常信息都是扩展类,因此可以访问excp的toString
或者getMessage来获取异常信息的内容。访问这两个方法同前面讲到的如何访问类的方法是相同的。
*/
}
线程和同步访问
有 些时候需要使用多线程的方式来访问Java的方法。我们知道一个Java虚拟机是非常消耗系统的内存资源,差不多每个虚拟机需要内存大约在20MB左右。 为了节省资源要求每个线程使用的是同一个虚拟机,这样在整个的JNI程序中只需要初始化一个虚拟机就可以了。所有人都是这样想的,但是一旦子线程访问主线 程创建的虚拟机环境变量,系统就会出现错误对话框,然后整个程序终止。
其实这里面涉及到两个概念,它们分别是虚拟机(JavaVM *jvm)和虚拟机环境(JNIEnv *env)。真正消耗大量系统资源的是jvm而不是env,jvm是允许多个线程访问的,但是env只能被创建它本身的线程所访问,而且每个线程必须创建 自己的虚拟机环境env。这时候会有人提出疑问,主线程在初始化虚拟机的时候就创建了虚拟机环境env。为了让子线程能够创建自己的env,JNI提供了 两个函数:AttachCurrentThread和DetachCurrentThread。下面代码就是子线程访问Java方法的框架:
DWORD WINAPI ThreadProc(PVOID dwParam)
{
JavaVM jvm = (JavaVM*)dwParam; /* 将虚拟机通过参数传入 */
JNIEnv* env;
(*jvm)-> AttachCurrentThread(jvm, (void**)&env, NULL);
………
(*jvm)-> DetachCurrentThread(jvm);
}
时间
关 于时间的话题是我在实际开发中遇到的一个问题。当要发布使用了JNI的程序时,并不一定要求客户要安装一个Java运行环境,因为可以在安装程序中打包这 个运行环境。为了让打包程序利于下载,这个包要比较小,因此要去除JRE(Java运行环境)中一些不必要的文件。但是如果程序中用到Java中的日历类 型,例如等,那么有个文件一定不能去掉,这个文件就是[JRE]\lib\tzmappings。它是一个时区映射 文件,一旦没有该文件就会发现时间操作上经常出现与正确时间相差几个小时的情况。下面是打包JRE中必不可少的文件列表(以Windows环境为例),其 中[JRE]为运行环境的目录,同时这些文件之间的相对路径不能变。
文件名 目录
[JRE]\bin
[JRE]\bin
[JRE]\bin
[JRE]\bin
[JRE]\bin
[JRE]\bin
[JRE]\bin\classic
[JRE]\lib
tzmappings [JRE]\lib
由于有差不多10MB,但是其中有很大一部分文件并不需要,可以根据实际的应用情况进行删除。例如程序如果没有用到Java Swing,就可以把涉及到Swing的文件都删除后重新打包
android C调用JAVA的方法
学习并转载了老罗的Java到C通过JNI调用的过程,在自己的平台上逐一实现,很是受用。本文将在此基础上,进行C到JAVA调用的验证。C到JAVA的调用在android系统GPS部分数据上报部分已经用到。
为了测试,在hardware中添加dev->init函数,实现callback的简历,并在init中创建线程,循环上报,添加后的代码如下:
/hardware/libhardware/include/hardware/
[cpp]view plaincopy
1 #ifndef ANDROID_HELLO_INTERFACE_H
2 #define ANDROID_HELLO_INTERFACE_H
3 #include <sys/>
4 #include <hardware/>
5 #include <>
6 __BEGIN_DECLS
7
8 /*定义模块ID*/
9 #define HELLO_HARDWARE_MODULE_ID "hello"
10
11
12 typedef void (* hello_val_callback)(const char* val,int length);
13 typedef pthread_t (* hello_create_thread)(const char* name, void (*start)(void *), void* arg);
14
15 typedef struct {
16 /** set to sizeof(GpsCallbacks) */
17 size_t size;
18 hello_val_callback hello_cb;
19 hello_create_thread create_thread_cb;
20 } HelloCallbacks;
21
22
23
24 /*硬件模块结构体*/
25 struct hello_module_t {
26 struct hw_module_t common;
27 };
28
29 /*硬件接口结构体*/
30 struct hello_device_t {
31 struct hw_device_t common;
32 int fd;
33 int (*set_val)(struct hello_device_t* dev, int val);
34 int (*get_val)(struct hello_device_t* dev, int* val);
35 int (*init)( HelloCallbacks* callbacks );
36 int (*remove)(struct hello_device_t* dev, int* val);
37 };
38
39 __END_DECLS
40
41 #endif
/hardware/xxxx/libhellotest/
[cpp]view plaincopy
42 #define LOG_TAG "HelloStub"
43
44 #include <hardware/>
45 #include <hardware/>
46 #include <>
47 #include <>
48 #include <cutils/>
49 #include <cutils/>
50 #include <>
51 #include <sys/>
52 #include <>
53
54
55 #define DEVICE_NAME "/dev/hello"
56 #define MODULE_NAME "Hello"
57 #define MODULE_AUTHOR "zhhpudk@"
58
59
60
61 typedef struct {
62 bool use;
63 HelloCallbacks callbacks;
64 pthread_t thread;
65 } HelloState;
66
67 static HelloState _hello_state[1];
68 static HelloState *hello_state = _hello_state;
69
70
71 /*设备打开和关闭接口*/
72 static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
73 static int hello_device_close(struct hw_device_t* device);
74
75
76 /*设备访问接口*/
77 static int hello_set_val(struct hello_device_t* dev, int val);
78 static int hello_get_val(struct hello_device_t* dev, int* val);
79 static int hello_init(HelloCallbacks* callbacks);
80 //static int hello_close(struct hello_device_t* dev);
81 static int hello_remove(struct hello_device_t* dev, int* val);
82
83
84 /*模块方法表*/
85 static struct hw_module_methods_t hello_module_methods = {
86 open: hello_device_open
87 };
88
89 /*模块实例变量*/
90 struct hello_module_t HAL_MODULE_INFO_SYM = {
91 common: {
92 tag: HARDWARE_MODULE_TAG,
93 version_major: 1,
94 version_minor: 0,
95 id: HELLO_HARDWARE_MODULE_ID,
96 name: MODULE_NAME,
97 author: MODULE_AUTHOR,
98 methods: &hello_module_methods,
99 }
100 };
101
102 static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {
103 struct hello_device_t* dev;
104 dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));
105
106 if(!dev) {
107 LOGE("Hello Stub: failed to alloc space");
108 return -EFAULT;
109 }
110
111 memset(dev, 0, sizeof(struct hello_device_t));
112 dev-> = HARDWARE_DEVICE_TAG;
113 dev-> = 0;
114 dev-> = (hw_module_t*)module;
115 dev-> = hello_device_close;
116 dev->set_val = hello_set_val;
117 dev->get_val = hello_get_val;
118 dev->init=hello_init;
119 dev->remove=hello_remove;
120
121
122 if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
123 LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));
124 free(dev);
125 return -EFAULT;
126 }
127
128 //*device = &(dev->common);
129 *device = (struct hw_device_t*)dev;
130 LOGI("Hello Stub: open /dev/hello successfully.");
131
132 return 0;
133 }
134
135 static int hello_device_close(struct hw_device_t* device) {
136 struct hello_device_t* hello_device = (struct hello_device_t*)device;
137
138 LOGI("Hello Stub: close /dev/hello successfully.");
139
140 if(hello_device) {
141 close(hello_device->fd);
142 free(hello_device);
143 }
144
145 return 0;
146 }
147
148 static int hello_set_val(struct hello_device_t* dev, int val) {
149 LOGI("Hello Stub: set value %d to device.", val);
150
151 write(dev->fd, &val, sizeof(val));
152
153 return 0;
154 }
155
156 static int hello_get_val(struct hello_device_t* dev, int* val) {
157 if(!val) {
158 LOGE("Hello Stub: error val pointer");
159 return -EFAULT;
160 }
161
162 read(dev->fd, val, sizeof(*val));
163
164 LOGI("Hello Stub: get value %d from device", *val);
165
166 return 0;
167 }
168
169
170 static void *hello_state_thread( void* arg )
171 {
172 HelloState* state = (HelloState*) arg;
173 char val[1]={0};
174 // now loop
175 for (;;)
176 {
177 sleep(5);
178
179 if(!state->use)
180 {
181 LOGI("Hello Stub: hello thread exit");
182 break;
183 }
184
185 val[0]++;
186 LOGI("Hello Stub: hello thread val %d .",val[0]);
187 state->callbacks.hello_cb(val,1);
188 }
189 return NULL;
190 }
191
192 static int hello_init(HelloCallbacks* callbacks)
193 {
194 HelloState* s = _hello_state;
195
196 LOGI("Hello Stub: hello init.");
197 s->callbacks = *callbacks;
198
199 if(callbacks!= NULL && callbacks->create_thread_cb != NULL)
200 s->thread = s->callbacks.create_thread_cb("hello", hello_state_thread, s);
201
202 if (!s->thread)
203 {
204 LOGE("could not create hello thread: %s", strerror(errno));
205 return -1;
206 }
207 s->use = true;
208 return 0;
209 }
210
211 static int hello_remove(struct hello_device_t* dev, int* val)
212 {
213 struct hello_device_t* hello_device = (struct hello_device_t*)dev;
214 void *dummy;
215 hello_state->use=false;
216
217 LOGI("Hello Stub: hello_remove value %d to device.", val);
218
219 if(hello_device) {
220 close(hello_device->fd);
221 free(hello_device);
222 }
223 return 0;
224 }
JNI中通过GetMethodID方法实现,具体代码如下:
/frameworks/base/services/jni/com_android_server_HelloSerivce.cpp
[javascript]view plaincopy
225 #define LOG_TAG "HelloService"
226 #include ""
227 #include ""
228 #include "android_runtime/"
229 #include <utils/>
230 #include <utils/>
231 #include <hardware/>
232 #include <hardware/>
233 #include <>
234 #include <>
235
236 static jobject mCallbacksObj = NULL;
237 static jmethodID method_receive;
238
239 namespace android
240 {
241 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
242 if (env->ExceptionCheck()) {
243 LOGE("An exception was thrown by callback '%s'.", methodName);
244 LOGE_EX(env);
245 env->ExceptionClear();
246 }
247 }
248
249 static void hello_recval_callback(const char* buf,int len)
250 {
251 JNIEnv* env = AndroidRuntime::getJNIEnv();
252 jbyteArray var = NULL;
253 //jbyte buffer[100];
254 jbyte *buffer;
255 LOGI("hello recval_callback buf[0] %d len %d",buf[0],len);
256
257 if(len)
258 {
259 var = env->NewByteArray(len);
260 buffer = (jbyte*)calloc(len, sizeof(jbyte));
261 }
262
263 if(var)
264 {
265 for(int i=0;i<len;i++)
266 //buffer[i] = buf[i];
267 *(buffer+i)=*(buf+i);
268 env->SetByteArrayRegion(var,0,len,buffer);
269 }
270
271 //调用java方法上报数据
272 env->CallVoidMethod(mCallbacksObj, method_receive,var,len);
273
274 if(var){
275 env->DeleteLocalRef(var);
276 free(buffer);
277 buffer = NULL;
278 }
279 checkAndClearExceptionFromCallback(env, __FUNCTION__);
280 }
281
282 static pthread_t hello_thread_callback(const char* name, void (*start)(void *), void* arg)
283 {
284 return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
285 }
286
287 HelloCallbacks sHelloCallback =
288 {
289 sizeof(HelloCallbacks),
290 hello_recval_callback,
291 hello_thread_callback
292 };
293
294 static void android_hello_class_init_native(JNIEnv* env, jclass clazz)
295 {
296 method_receive = env->GetMethodID(clazz, "Receive", "([BI)V");
297 }
[javascript]view plaincopy
298
[javascript]view plaincopy
299 /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/>*/
300 struct hello_device_t* hello_device = NULL;
301 /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/
302 static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {
303 int val = value;
304 LOGI("Hello JNI: set value %d to device.", val);
305 if(!hello_device) {
306 LOGI("Hello JNI: device is not open.");
307 return;
308 }
309
310 hello_device->set_val(hello_device, val);
311 }
312 /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/
313 static jint hello_getVal(JNIEnv* env, jobject clazz) {
314 int val = 0;
315 if(!hello_device) {
316 LOGI("Hello JNI: device is not open.");
317 return val;
318 }
319 hello_device->get_val(hello_device, &val);
320
321 LOGI("Hello JNI: get value %d from device.", val);
322
323 return val;
324 }
325 /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
326 static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
327 return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
328 }
329 /*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/
330 static jboolean hello_init(JNIEnv* env, jobject clazz) {
331 hello_module_t* module;
332
333
334 // this must be set before calling into the HAL library
335 if (!mCallbacksObj)
336 mCallbacksObj = env->NewGlobalRef(clazz);
337
338
339 LOGI("Hello JNI: initializing......");
340 if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
341 LOGI("Hello JNI: hello Stub found.");
342 if(hello_device_open(&(module->common), &hello_device) == 0) {
343 LOGI("Hello JNI: hello device is open.");
344
345 if(hello_device->init(&sHelloCallback)==0){
346 LOGI("Hello JNI: Init HelloCallback.");
347 return true;
348 }
349 LOGE("Hello JNI : Init HelloCallback Error");
350 return false;
351 }
352 LOGE("Hello JNI: failed to open hello device.");
353 return false;
354 }
355 LOGE("Hello JNI: failed to get hello stub module.");
356 return false;
357 }
358
359 static jboolean hello_remove(JNIEnv* env, jobject clazz) {
360 int val = 0;
361 if(!hello_device) {
362 LOGI("Hello JNI: device is not open.");
363 return false;
364 }
365 LOGI("hello_close 111");
366 if(hello_device->remove(hello_device,&val)!=0)
367 return false;
368 else
369 return true;
370 // LOGI("hello_close");
371 //return true;
372 }
373 /*JNI方法表*/
374 static const JNINativeMethod method_table[] = {
375 {"class_init_native", "()V", (void *)android_hello_class_init_native},
376 {"init_native", "()Z", (void*)hello_init},
377 {"remove_native", "()Z", (void*)hello_remove},
378 {"setVal_native", "(I)V", (void*)hello_setVal},
379 {"getVal_native", "()I", (void*)hello_getVal},
380 };
381 /*注册JNI方法*/
382 int register_android_server_HelloService(JNIEnv *env) {
383 return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
384 }
385 };
Java中实现Receive方法,回调实现:
/framework/base/services/java/com/android/server/
[javascript]view plaincopy
386 package ;
387 import ;
388 import ;
389 import ;
390 import ;
391 import ;
392
393 public class HelloService extends {
394
395 private static final String TAG = "HelloService";
396
397 private static Context mContext = null;
398
399
400
401 HelloService(Context context)
402 {
403 mContext = context;
404 }
405
406 public void setVal(int val) {
407 setVal_native(val);
408 ("Hello Test", "Setval");
409 }
410 public int getVal() {
411 ("Hello Test", "getval");
412 return getVal_native();
413 }
414
415 public boolean hello_init() {
416 ("Hello Test","init");
417 return init_native();
418 }
419
420 public void hello_close(){
421 ("Hello Test","close");
422 remove_native();
423 return;
424 }
425
426 private void Receive(byte[] buffer,int length){
427 //
428 ("Hello Test", "buffer "+(buffer[0]&0xFF)+" length "+length);
429 }
430
431 static { class_init_native(); }
432
433 private static native boolean init_native();
434 private static native boolean remove_native();
435 private static native void class_init_native();
436 private static native void setVal_native(int val);
437 private static native int getVal_native();
438 };
实现aidl接口,/frameworks/base/core/java/android/os/
[javascript]view plaincopy
439 package ;
440
441 interface IHelloService {
442 boolean hello_init();
443 void hello_close();
444 void setVal(int val);
445 int getVal();
446 }
Package 实现
[javascript]view plaincopy
447 package ;
448
449 import ;
450 import ;
451 import ;
452 import ;
453 import ;
454 import ;
455 import ;
456 import ;
457 import ;
458 import ;
459 import ;
460
461 public class Hello extends Activity implements OnClickListener {
462 private final static String LOG_TAG = "";
463
464 private IHelloService helloService = null;
465
466 private EditText valueText = null;
467 private Button readButton = null;
468 private Button writeButton = null;
469 private Button clearButton = null;
470
471 /** Called when the activity is first created. */
472 @Override
473 public void onCreate(Bundle savedInstanceState) {
474 (savedInstanceState);
475 setContentView();
476
477 helloService = (
478 ("hello"));
479
480 try {
481 helloService.hello_init();
482 } catch (RemoteException e) {
483 (LOG_TAG, "Remote Exception while init_native device.");
484 }
485
486 valueText = (EditText)findViewById(.edit_value);
487 readButton = (Button)findViewById(.button_read);
488 writeButton = (Button)findViewById(.button_write);
489 clearButton = (Button)findViewById(.button_clear);
490
491 (this);
492 (this);
493 (this);
494
495 (LOG_TAG, "Hello Activity Created");
496 }
497
498 @Override
499 public void onClick(View v) {
500 if((readButton)) {
501 try {
502 int val = ();
503 String text = (val);
504 (text);
505 } catch (RemoteException e) {
506 (LOG_TAG, "Remote Exception while reading value from device.");
507 }
508 }
509 else if((writeButton)) {
510 try {
511 String text = ().toString();
512 int val = (text);
513 (val);
514 } catch (RemoteException e) {
515 (LOG_TAG, "Remote Exception while writing value to device.");
516 }
517 }
518 else if((clearButton)) {
519 String text = "";
520 (text);
521 // try {
522 // helloService.hello_close();
523 // } catch (RemoteException e) {
524 // (LOG_TAG, "Remote Exception while Destroy device.");
525 // }
526 }
527 }
528
529
530 public void onDestroy() {
531 (LOG_TAG, "Hello Activity onDestroy called!");
532 try {
533 helloService.hello_close();
534 } catch (RemoteException e) {
535 (LOG_TAG, "Remote Exception while Destroy device.");
536 }
537 ();
538 }
539 }
注意:在编译时,一定要在添加文件下面通过mm编译后,再打包系统,或者是在make 文件中把添加的lib和apk添加到PRODUCT_PACKAGES下面才可以打包进去