JNI调用机制(二)

| 分类 Java  | 标签 JNI 

JNI可以实现Java调用本地库,也可以实现C/C++调用Java代码。使Java使用更加灵活。

本文涉及到的一些功能:

  1. 创建虚拟机
  2. 寻找class对象,创建对象
  3. 调用静态方法和成员方法
  4. 获取成员属性,修改成员属性

C/C++调用Java代码的一般步骤:

  1. 编写Java代码,并编译
  2. 编写C/C++代码
  3. 配置lib进行编译,配置PATH添加相应的dll或so并运行

编写Java代码并编译

JNITest代码仅仅有静态方法和成员方法,一个public的成员变量

public class JNITest2 {
	public String name;

	public static String sayHello(String name) {
		return "Hello, " + name + "!";
	}

	public String sayHello() {
		return "Hello, " + name + "!";
	}
}

编译成功后我们可以用命令:

  • javap -s -private JNITest2

看到JNITest2类中的签名。

编写C/C++代码

下面的代码可以当做C/C++代码调用Java代码的事例,主要做了这四件事情:

  1. 创建虚拟机JVM,在程序结束时销毁虚拟机JVM
  2. 寻找class对象
  3. 创建class对象的实例
  4. 调用方法和修改属性
#include <jni.h>
#include <string.h>
#include <stdio.h>

//环境变量PATH在windows和linux的分隔符定义
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#else 
#define PATH_SEPARATOR ':'
#endif


int main (void) {

	JavaVMOption options[1]; //相当于命令行里传入的参数
	JNIEnv *env;  //JNI环境
	JavaVM *jvm;  //我们需要创建的虚拟机实例
	JavaVMInitArgs vm_args;  //虚拟机创建的初始化参数,这个参数里面会包含JavaVMOption

	long status;
	jclass cls;
	jmethodID mid;
	jfieldID fid;
	jobject obj;

	options[0].optionString = "-Djava.class.path=.";  //传入路径,作为JVM寻找class的用户自定义路径,我们的JNITest2.clsss就在当前路径(当然也可以在别的路径)
	memset(&vm_args, 0, sizeof(vm_args));
	vm_args.version = JNI_VERSION_1_4; //Java版本,这个宏在jni.h中有定义
	vm_args.nOptions = 1;  //传入的Options有多长,我们这只有一个,所以是1
	vm_args.options = options;  // 把JavaVMOption传给JavaVMInitArgs中

	status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); //启动虚拟机,通过status返回值,可知道虚拟机启动情况

	if (status != JNI_ERR) {
		cls = (*env)->FindClass(env, "JNITest2"); //获取class对象
		if (cls != 0) {
			//Java中类名格式是java.lang.String,但是ClassName格式不同,是java/lang/String
			//获取静态方法 
			mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
			if (mid != 0) {
				const char* name = "World";
				jstring arg = (*env)->NewStringUTF(env, name);
				jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
				const char* str = (*env)->GetStringUTFChars(env, result, 0);
				printf("Result of sayHello: %s\n", str);
				(*env)->ReleaseStringUTFChars(env, result, 0);
			}
			//调用指定的构造函数,构造函数的名字叫<init>
			mid = (*env)->GetMethodID(env, cls, "<init>", "()V");  //调用非静态方法
			obj = (*env)->NewObject(env, cls, mid);
			if (obj == 0) {
				printf("Create object failed!\n");
			}
			//属性也有静态和非静态之分,有static为静态,无为非静态
			fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
			if (fid != 0) {
				const char* name = "stackvoid";
				jstring arg = (*env)->NewStringUTF(env, name);
				(*env)->SetObjectField(env, obj, fid, arg);//修改属性
			}
			//调用成员方法
			mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
			if (mid != 0) {
				jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
				const char* str = (*env)->GetStringUTFChars(env, result, 0);
				printf("Result of sayHello: %s\n", str);
				(*env)->ReleaseStringUTFChars(env, result, 0);
			}
		}
		(*jvm)->DestoryJavaVM(jvm);
		return 0;
	}
	else {
		printf("JVM Created failed!\n");
		return -1;
	}

}

注意:

  1. JNI的函数都是有一定规律的,static就表示静态,没有表示非静态。

  2. 静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.

jstring

Java的String使用的是Unicode是双字节的字符,而C/C++使用的是单字节的字符。 从C转换为Java字符使用 NewStringUTF方法;从java转换为C的字符,使用GetStringUTFChars

编译运行

编译需要头文件, 头文件在这两个目录中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, 第一个是与平台无关的, 第二个是与平台有关的, 当前系统是windows, 所以是win32.

编译的时候还要一个lib文件, 是对虚拟机的支持, 保证编译通过.

cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib 我们可以看到在当前目录下Sample2.exe, 运行的时候需要jvm.dll(不要将其复制到当前目录下, 这样不可以运行, 会导致jvm创建失败)

set PATH=%JAVA_HOME%\jre\bin\client\;%PATH% Sample2 jvm.dll在%JAVA_HOME%\jre\bin\client\目录下, 所以我把这个目录加入到PATH中, 然后就可以运行


上一篇     下一篇