最近一段时间重拾C++开发,做了几年的javascript开发后,在回到C++开发中,确实感觉C++的开发效率以及便利性方面不是很好,不知道像C、C++这些语言以后还能不能重回霸主地位。
C++中某些类库不太健全,java正好有响应的类库,简单的做法就是C++通过JNI调用java类库了。
1、搭建java环境
很多年以前学过一点java知识,现在已经不记得了,甚至配置java的开发环境都不知道了,搭建java环境需要安装jdk,jdk中包含了java的类库、字节码运行环境jvm、与C++相互调用的接口(如JNI)、jre(java运行环境就在jre中)。
下载官网的jdk,官网的jdk版本已经到了17版本了,网上的教程大多使用的是1.8的版本,我已使用的是1.8,下载后安装完成后需要配置jdk的环境变量,比如JAVA_HOME、CLASSPATH、PATH等。
下载eclipse,傻瓜式安装就可以了。
2、编写java的例程
package test;
public class Demo
{
public String append(String str, int i)
{
return str + i;
}
public int add(int a, int b)
{
return a + b;
}
}
编写例子的时候犯了几个错误,首先package这个关键字是Demo.java编译出来存放的目录,就因为这个目录导致我在写C++的时候一直调用不成功。
3、编译java程序
使用java命令行工具:"javac D:\\Demo.java"后会在我的盘符下创建一个Demo.class文件,将这个文件放到"D:\test\Demo.class"目录下才是正确的目录
4、方法签名
C++调用java的方式是通过方法签名的方式调用的,形如:
//查找test.Demo类,返回JAVA类的CLASS对象
jclass cls = env->FindClass("test/Demo");
//根据类的CLASS对象获取该类的实例
jobject obj = env->AllocObject(cls);
//获取类中的方法,最后一个参数是方法的签名,通过javap -s -p 文件名可以获得
jmethodID mid = env->GetMethodID(cls, "add","(II)I");
//构造参数并调用对象的方法
const char szTest[] = "123";
GetMethodID(cls, "add","(II)I");是用来获取类似函数指针的东西,需要三个参数:类的名称、函数名称、方法签名。
方法签名的获取方式是通过javap这个命令“javap -s -p c:\ test\Demo.class”
D:\>javap -s -p D:\test\Demo.class
Compiled from "Demo.java"
public class test.Demo {
public test.Demo();
descriptor: ()V
public java.lang.String append(java.lang.String, int);
descriptor: (Ljava/lang/String;I)Ljava/lang/String;
public int add(int, int);
descriptor: (II)I
}
4、C++调用
#include "windows.h"
#include <jni.h>
#include <string>
#include <iostream>
using namespace std;
jstring NewJString(JNIEnv *env, std::string str);
int main()
{
//定义一个函数指针,下面用来指向JVM中的JNI_CreateJavaVM函数
typedef jint (WINAPI *PFunCreateJavaVM)(JavaVM **, void **, JavaVMInitArgs *);
int res;
JavaVMInitArgs vm_args;
JavaVMOption options[3];
JavaVM *jvm;
JNIEnv *env;
/*设置初始化参数*/
//disable JIT,这是JNI文档中的解释,具体意义不是很清楚 ,能取哪些值也不清楚。
//从JNI文档里给的示例代码中搬过来的
options[0].optionString = const_cast<char*>("-Djava.compiler=NONE");
//设置classpath,如果程序用到了第三方的JAR包,也可以在这里面包含进来
options[1].optionString = const_cast<char*>("-Djava.class.path=.;D:\\");
//设置显示消息的类型,取值有gc、class和jni,如果一次取多个的话值之间用逗号格开,如-verbose:gc,class
//该参数可以用来观察C++调用JAVA的过程,设置该参数后,程序会在标准输出设备上打印调用的相关信息
options[2].optionString = const_cast<char*>("-verbose:NONE");
//设置版本号,版本号有JNI_VERSION_1_1,JNI_VERSION_1_2和JNI_VERSION_1_4
//选择一个根你安装的JRE版本最近的版本号即可,不过你的JRE版本一定要等于或者高于指定的版本号
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = 3;
vm_args.options = options;
//该参数指定是否忽略非标准的参数,如果填JNI_FLASE,当遇到非标准参数时,JNI_CreateJavaVM会返回JNI_ERR
vm_args.ignoreUnrecognized = JNI_TRUE;
//加载JVM.DLL动态库
HINSTANCE hInstance = ::LoadLibrary(L"../x64/Debug/jre/bin/server/jvm.dll");
if (hInstance == NULL)
{
return false;
}
//取得里面的JNI_CreateJavaVM函数指针
PFunCreateJavaVM funCreateJavaVM = (PFunCreateJavaVM)::GetProcAddress(hInstance, "JNI_CreateJavaVM");
//调用JNI_CreateJavaVM创建虚拟机
res = (*funCreateJavaVM)(&jvm, (void**)&env, &vm_args);
if (res < 0)
{
return -1;
}
//查找test.Demo类,返回JAVA类的CLASS对象
jclass cls = env->FindClass("test/Demo");
//根据类的CLASS对象获取该类的实例
jobject obj = env->AllocObject(cls);
//获取类中的方法,最后一个参数是方法的签名,通过javap -s -p 文件名可以获得
jmethodID mid = env->GetMethodID(cls, "add","(II)I");
//构造参数并调用对象的方法
int result = (int) env->CallObjectMethod(obj, mid, 11, 12);
cout<<result;
//销毁虚拟机并释放动态库
jvm->DestroyJavaVM();
::FreeLibrary(hInstance);
return 0;
}