简介
JNI 是 Java Native Interface 的缩写,它提供了若干的API实现了 Java 和其他语言(主要是 C&C++)的通信。
本篇主要介绍通过 C++ 调用 Java 代码的一般方法。
本例的开发环境为:WIN10 + VS2015 + JDK1.8.0_131
首先我们需要建立一个 Java 项目,为了简单起见,这里没有用到 IDE 。
建立 Java 项目
建立如下的目录结构:
JNITest\ -- 项目目录
|-src\ -- 该目录保存源代码文件
| |-com\
| |-cynhard\
| |-test\
| |-Test.java
|-classes\ -- 该目录保存 class 文件
|-MANIFEST.MF -- 打 jar 包时用到的配置文件
打开 Test.java 文件,编写如下代码。这里定义了两个简单的方法: add()
用来计算两个整数的和; toUpperCase() 用来将字符串转换为大写。Main()
方法对这两个函数进行了简单的测试。
package com.cynhard.test;
public class Test {
public static void main(String[] args) {
Test test = new Test();
// test add
System.out.printf("1 + 2 = %d\n", test.add(1, 2));
// test toUpperCase
String greeting = "hello, world!";
System.out.printf("Upper case of \"%s\" is \"%s\"\n",
greeting, test.toUpperCase(greeting));
}
public int add(int num1, int num2) {
return num1 + num2;
}
public String toUpperCase(String str) {
return str.toUpperCase();
}
}
接下来编译这个类。打开命令行,切换到 JNITest
目录,执行下面的命令进行编译。首先我们设置了一个环境变量 SRCPATH
用来指定源文件的目录。然后通过 javac
来进行编译,-sourcepath
用来指定源代码的目录。注意虽然用 -sourcepath
指定了源代码的目录,但后面仍然要指定 java
文件相对于当前目录的相对路径。 -d
用来指定 class
文件的输出目录。
> set SRCPATH=src\com\cynhard\test
> javac -sourcepath %SRCPATH% %SRCPATH%\Test.java -d classes
编译后,可以在 classes\com\cynhard\test
目录下找到编译好的 Test.class
文件。可以通过以下命令执行这个文件。-cp
用来指定 class
文件目录。后面是用来执行的完整类名。
> java -cp classes com.cynhard.test.Test
1 + 2 = 3
Upper case of "hello, world!" is "HELLO, WORLD!"
接着我们将 class
文件打成 jar
包。
打开 MANIFEST.MF
,编写如下内容。Main-Class
指定了运行该 jar
包时应执行的类。注意后面必须有一个回车。
Main-Class: com.cynhard.test.Test
执行以下命令打 jar
包。-c
用来创建 jar
包。-v
用来打印详细信息。-f
指定 jar
包名称。-m
指定我们要包含 MANIFEST.MF
中的清单信息。-C
指定输入目录。
> jar -cvfm test.jar MANIFEST.MF -C classes .
打包后会在 JNITest
目录下找到 test.jar
。可以通过如下命令执行这个 jar
包。
> java -jar test.jar
1 + 2 = 3
Upper case of "hello, world!" is "HELLO, WORLD!"
到这里我们的 Java 项目就建立完成了。下面开始使用 C++ 调用最后生成的这个 jar
包。
C++ 调用 Java 代码
打开 VS2015,新建一个控制台项目,在 main.cpp 中编写如下代码:
#include "jni.h"
#include <Windows.h>
int main()
{
/* 设置 JVM 参数 */
JavaVMInitArgs vmArgs;
vmArgs.version = JNI_VERSION_1_8;
const int OPTION_COUNT = 2;
vmArgs.nOptions = OPTION_COUNT;
JavaVMOption options[OPTION_COUNT] = { 0 };
// classpath 指定为我们要调用的 jar 包路径
options[0].optionString = "-Djava.class.path=G:\\projects\\java\\JNITest\\test.jar";
options[1].optionString = "-Xmx1024m"; // 最大堆大小
vmArgs.options = options;
vmArgs.ignoreUnrecognized = JNI_TRUE;
/* 启动 JVM */
HMODULE hModule = LoadLibrary(TEXT("G:\\Java\\jre1.8.0_131\\bin\\server\\jvm.dll"));
if (hModule == NULL)
{
return -1;
}
typedef jint (JNICALL *CreateJavaVMFuncPtr)(JavaVM **pvm, void **penv, void *args);
CreateJavaVMFuncPtr CreateJavaVM = (CreateJavaVMFuncPtr)GetProcAddress(hModule, "JNI_CreateJavaVM");
JavaVM *jvm = nullptr;
JNIEnv *env = nullptr;
jint res = (*CreateJavaVM)(&jvm, (void**)&env, &vmArgs);
if (res < 0)
{
return -1;
}
/* 调用 class */
// 找到 class
jclass class_Test = env->FindClass("com/cynhard/test/Test");
jmethodID methodId_main = env->GetStaticMethodID(class_Test, "main", "([Ljava/lang/String;)V");
env->CallStaticVoidMethod(class_Test, methodId_main, "");
// 释放资源
jvm->DestroyJavaVM();
FreeLibrary(hModule);
return 0;
}