本篇文章中,我们来看一下Java与C++的相互调用是如何完成的。接下来我们分两种情况展开。
一、 Java调用C++整体流程如下:
编写带有native关键字修饰的方法的Java类
使用javac工具编译Java类
使用javah生成与native修饰的方法对应的(.h)头文件
使用C++ 实现头文件并编译为so文件
编译运行完成调用
步骤一 : 编写带有native关键字修饰的方法的Java类
public class Sample {
//声明四种类型的native方法
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) {
//将Sample.so动态类库,加载到当前进程中
System.loadLibrary("Sample");
Sample sample = new Sample();
//调用native方法
int square = sample.intMethod(5);
boolean bool = sample.booleanMethod(true);
String text = sample.stringMethod("Java");
int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});
//打印得到的值
System.out.println("intMethod: " + square);
System.out.println("booleanMethod: " + bool);
System.out.println("stringMethod: " + text);
System.out.println("intArrayMethod: " + sum);
}
}
步骤二 : 将Java文件编译生成class文件
jimmy@58deMacBook-Pro-9 ~> javac Sample.java
步骤三 : 生成头文件
>javah Sample
生成的头文件代码如下:
#include#ifndef _Included_Sample #define _Included_Sample #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_Sample_intMethod(JNIEnv *, jobject, jint); JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod(JNIEnv *, jobject, jboolean); JNIEXPORT jstring JNICALL Java_Sample1_stringMethod(JNIEnv *, jobject, jstring); JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod(JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
方法签名Signature类型详表:
| 表头java类型 | Signature | 备注 |
| boolean | Z | - |
| byte | B | |
| char | C | |
| short | S | |
| int | I | |
| long | L | |
| float | F | |
| double | D | |
| void | V | |
| object | L用/分割的完整类名 | 例如: Ljava/lang/String表示String类型 |
| Array | [签名 | 例如: [I表示int数组, [Ljava/lang/String表示String数组 |
| Method | (参数签名)返回类型签名 | 例如: ([I)I表示参数类型为int数组, 返回int类型的方法 |
步骤四 : C++ 实现头文件中的函数
#include "Sample.h" #includeJNIEXPORT jint JNICALL Java_Sample_intMethod(JNIEnv *env, jobject obj, jint num){ return num * num; } JNIEXPORT jboolean JNICALL Java_Sample_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean){ return !boolean; } JNIEXPORT jstring JNICALL Java_Sample_stringMethod(JNIEnv *env, jobject obj, jstring string){ const char* str = env->GetStringUTFChars(string, 0); char cap[128]; strcpy(cap, str); env->ReleaseStringUTFChars(string, 0); return env->NewStringUTF(strupr(cap)); } JNIEXPORT jint JNICALL Java_Sample_intArrayMethod(JNIEnv *env, jobject obj, jintArray array){ int i, sum = 0; jsize len = env->GetArrayLength(array); jint *body = env->GetIntArrayElements(array, 0); for (i = 0; i < len; ++i){ sum += body[i]; } env->ReleaseIntArrayElements(array, body, 0); return sum; }
env.GetStringUTFChars()是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符, 所以需要进行转换.
每个函数都有 JNIEnv * 参数, 它包含了很多有用的方法, 使用起来类似Java中的反射。
GetStringUTFChars() 和 NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.
ReleaseStringUTFChars()是用来释放对象的, 在Java中虚拟机是有一个垃圾回收机制的, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.
C 和 C++ 的实现非常相似, 只有一个不同点:
C代码: (*env)->GetStringUTFChars(env, string, 0);
C++ 代码: env->GetStringUTFChars(string, 0);
C语言中使用的是结构体的函数指针, 而在C++ 中使用的是struct。
步骤五 : 编译运行完成调用
步骤一的Java文件中已经完成了Java对Native的调用,需要注意的是在调用之前,首先需要将so动态类库加载到当前进行。
二、C++调用Java该方式的实现需要用到的功能如下:
创建虚拟机
寻找class对象, 创建对象
调用静态方法和成员方法
获取成员属性, 修改成员属性
该方式实现的步骤:
实现的大体流程与Java调C++ 的基本一致。唯一不同的是,C++ 调用Java的方式,需要在C++ 类中通过类似Java反射的方式来完成对Java的调用。
步骤一 : 编写Java代码
public class Sample {
public String name;
public static String sayHello(String name) {
return "Hello, " + name + "!";
}
public String sayHello() {
return "Hello, " + name + "!";
}
}
步骤二 : 编译生成class文件
>javac Sample.java
步骤三 : 编写C++ 代码并完成对Java函数的调用
#include#include #include int main(void){ //虚拟机创建所需的相关参数 JavaVMOption options[1]; //相当于在命令行里传入的参数 JNIEnv *env; //JNI环境变量 JavaVM *jvm; //虚拟机实例 JavaVMInitArgs vm_args; //虚拟机创建的初始化参数, 这个参数里面会包含JavaVMOption long status; //虚拟机启动是否成功的状态 jclass cls; //将要寻找的Class对象 jmethodID mid; //Class对象中的方法Id jfieldID fid; //Class对象中的属性Id jobject obj; //创建的新对象 //创建虚拟机 // "-Djava.class.path=."是JVM将要寻找并加载的 .class文件路径 options[0].optionString = "-Djava.class.path=."; memset(&vm_args, 0, sizeof(vm_args)); vm_args.version = JNI_VERSION_1_4; //vm_args.version是Java的版本 vm_args.nOptions = 1; //vm_args.nOptions是传入的参数的长度 vm_args.options = options; //把JavaVMOption传给JavaVMInitArgs里面去. //启动虚拟机 status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (status != JNI_ERR){ // 首先获得class对象(JVM在Java中都是自己启动的, 但在C++ 中只能手动启动, 启动完之后的事情就和在Java中一样了, 不过要使用C++ 的语法)。 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: %sn", str); (*env)->ReleaseStringUTFChars(env, result, 0); } // 获取属性ID, 通过属性名和签名 fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;"); if (fid != 0){ 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: %sn", str); (*env)->ReleaseStringUTFChars(env, result, 0); } //我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象. } //执行完操作之后,销毁虚拟机 (*jvm)->DestroyJavaVM(jvm); return 0; } else{ printf("JVM Created failed!n"); return -1; } }
额外补充知识: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);
步骤四 : 编译运行
编译运行,完成 C++对Java的调用。



