在cc++中调⽤Java⽅法
JNI就是Java Native Interface, 即可以实现Java调⽤本地库, 也可以实现C/C++调⽤Java代码, 从⽽实现了两种语⾔的互通, 可以让我们更加灵活的使⽤.
通过使⽤JNI可以从⼀个侧⾯了解Java内部的⼀些实现.
本⽂使⽤的环境是
1. 64位的win7系统
2. JDK 1.6.0u30 (32位)
3. C/C++编译器是 Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86 (VC 6.0的, 其他版本的也可以编译通过,
测试过vs2010)
本⽂使⽤到的⼀些功能:
1. 创建虚拟机
2. 寻class对象, 创建对象
3. 调⽤静态⽅法和成员⽅法
4. 获取成员属性, 修改成员属性
C/C++调⽤Java代码的⼀般步骤:
1. 编写Java代码, 并编译
2. 编写C/C++代码
3. 配置lib进⾏编译, 配置PATH添加相应的dll或so并运⾏
编写Java代码并编译
这段代码⾮常简单, 有个静态⽅法和成员⽅法, ⼀个public的成员变量
1 2 3 4 5 6 7 8 9 10 11public class Sample2 {
public String name;
public static String sayHello(String name) {        return"Hello, "+ name + "!";
}
public String sayHello() {
java设置环境变量的方法代码return"Hello, "+ name + "!";
}
}
由于没有定义构造函数, 所以会有⼀个默认的构造函数.
运⾏下⾯的命令编译
>javac Sample2.java
可以在当前⽬录下看到Sample2.class⽂件, 编译成功, 第⼀步完成了, So easy!可以查看Sample2类中的签名
>javap -s -private Sample2
结果如下
Compiled from "Sample2.java"
public class Sample2 extends java.lang.Object{
public java.lang.String name;
Signature: Ljava/lang/String;
public Sample2();
Signature: ()V
public static java.lang.String sayHello(java.lang.String);
Signature: (Ljava/lang/String;)Ljava/lang/String;
public java.lang.String sayHello();
Signature: ()Ljava/lang/String;
}
关于签名的含义, 请参看.
编写C/C++代码调⽤Java代码
先贴代码吧1
2
3
4
5
6
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66#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;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj;
options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if(status != JNI_ERR)
{
// 先获得class对象
cls = (*env)->FindClass(env, "Sample2");
if(cls != 0)
{
// 获取⽅法ID, 通过⽅法名和签名, 调⽤静态⽅法
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);
}
/*** 新建⼀个对象 ***/
// 调⽤默认构造函数
//obj = (*env)->AllocObjdect(env, cls);
/
/ 调⽤指定的构造函数, 构造函数的名字叫做<init>
mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
if(obj == 0)
{
printf("Create object failed!\n");
}
/*** 新建⼀个对象 ***/
// 获取属性ID, 通过属性名和签名
fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
if(fid != 0)
{
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                const char* name = "icejoywoo";
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)->DestroyJavaVM(jvm);
return0;
}
else
{
printf("JVM Created failed!\n");
return-1;
}
}
这段代码⼤概做了这⼏件事
1. 创建虚拟机JVM, 在程序结束的时候销毁虚拟机JVM
2. 寻class对象
3. 创建class对象的实例
4. 调⽤⽅法和修改属性
虚拟的创建
与之相关的有这样⼏个变量
JavaVMOption options[1];    JNIEnv *env;    JavaVM *jvm;    JavaVMInitArgs vm_args; JavaVM就是我们需要创建的虚拟机实例
JavaVMOption相当于在命令⾏⾥传⼊的参数
JNIEnv在Java调⽤C/C++中每个⽅法都会有的⼀个参数, 拥有⼀个JNI的环境JavaVMInitArgs就是虚拟机创建的初始化参数, 这个参数⾥⾯会包含JavaVMOption
下⾯就是创建虚拟机
1
2 3 4 5 6 7 8    options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
"-Djava.class.path=."看着眼熟吧, 这个就是传⼊当前路径, 作为JVM寻class的⽤户⾃定义路径, 我们的Sample2.class就在当前路径(当然也可以不在当前路径, 你可以随便修改).
vm_args.version是Java的版本, 这个应该是为了兼容以前的JDK, 可以使⽤旧版的JDK, 这个宏定义是在jni.h中,  有以下四种
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
vm_args.nOptions的含义是, 你传⼊的options有多长, 我们这⾥就⼀个, 所以是1.
vm_args.options = options把JavaVMOption传给JavaVMInitArgs⾥⾯去.
然后就是启动虚拟机了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args).
可以通过这个返回值status , 知道虚拟机是否启动成功
#define JNI_OK          0                /* success */
#define JNI_ERR          (-1)              /* unknown error */
#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
#define JNI_EVERSION    (-3)              /* JNI version error */
#define JNI_ENOMEM      (-4)              /* not enough memory */
#define JNI_EEXIST      (-5)              /* VM already created */
#define JNI_EINVAL      (-6)              /* invalid arguments */
寻class对象, 并实例化
JVM在Java中都是⾃⼰启动的, 在C/C++中只能⾃⼰来启动了, 启动完之后的事情就和在Java中⼀样了, 不过要使⽤C/C++的语法.
获取class对象⽐较简单, FindClass(env, className).
cls = (*env)->FindClass(env, "Sample2");
在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使⽤'.'作为分割, ⽽是'/', 即java/lang/String.
我们知道Java中构造函数有两种, ⼀种是默认的没有参数的, ⼀种是⾃定义的带有参数的. 对应的在C/C++中, 有两种调⽤构造函数的⽅法.调⽤默认构造函数
// 调⽤默认构造函数
obj = (*env)->AllocObjdect(env, cls);
构造函数也是⽅法, 类似调⽤⽅法的⽅式.
// 调⽤指定的构造函数, 构造函数的名字叫做<init>
mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
调⽤⽅法和修改属性
关于⽅法和属性是有两个ID与之对应, 这两个ID⽤来标识⽅法和属性.
jmethodID mid;
jfieldID fid;
⽅法分为静态和⾮静态的, 所以对应的有
mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
上⾯两个⽅法是同名的, 都叫sayHello, 但是签名不同, 所以可以区分两个⽅法.
JNI的函数都是有⼀定规律的, Static就表⽰是静态, 没有表⽰⾮静态.
⽅法的调⽤如下
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
我们可以看到静态⽅法是只需要class对象, 不需要实例的, ⽽⾮静态⽅法需要使⽤我们之前实例化的对象.
属性也有静态和⾮静态, ⽰例中只有⾮静态的.
获取属性ID
fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
修改属性的值
(*env)->SetObjectField(env, obj, fid, arg); // 修改属性
关于jstring的说明
java的String都是使⽤了unicode, 是双字节的字符, ⽽C/C++中使⽤的单字节的字符.
从C转换为java的字符, 使⽤NewStringUTF⽅法
jstring arg = (*env)->NewStringUTF(env, name);
从java转换为C的字符, 使⽤GetStringUTFChars
const char* str = (*env)->GetStringUTFChars(env, result, 0);
编译和运⾏
编译需要头⽂件, 头⽂件在这两个⽬录中%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
我们可以看到在当前⽬录下, 运⾏的时候需要jvm.dll(不要将其复制到当前⽬录下, 这样不可以运⾏, 会导致jvm创建失败)
set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
Sample2
jvm.dll在%JAVA_HOME%\jre\bin\client\⽬录下, 所以我把这个⽬录加⼊到PATH中, 然后就可以运⾏
Result of sayHello: Hello, World!
Result of sayHello: Hello, icejoywoo!
关于C++的说明
本⽰例的C++版本, 请⾃⾏下载后⾯的源代码来查看, 区别不是很⼤.
主要是JNIEnv和JavaVM两个对象, 在C中是结构体, 是函数指针的集合, 在C++中结构体拥有类的能⼒, 使⽤起来更为简便, 与Java之间的差异更⼩⼀些.
结语
本⽂介绍了⼀个简单的例⼦, 分析了其中的⼀些代码, 笔者希望通过这篇⽂章让⼤家对JNI的了解更加深⼊⼀些.
⽔平有限, 错漏在所难免, 欢迎指正!
源代码下载:
使⽤⽅法: 参照⾥⾯的build&run.bat, 使⽤了%JAVA_HOME%环境变量.
注意:
1. 动态链接库和JDK都有32位和64位的区别, 使⽤64位系统的朋友, 要注意这个问题, 可能导致运⾏或编
译错误.
2. 还要注意区分C和C++代码, 在JNI中两种代码有⼀定的区别, 主要是env和jvm两个地⽅.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。