为什么需要在C/C++中调用Java?
-
遗留系统或库:
-
某些遗留系统或库可能用 Java 编写,而你的 C/C++ 应用程序需要与这些系统或库集成。
-
-
跨平台兼容性:
-
Java 以其“一次编写,到处运行”(Write Once, Run Anywhere)的能力而闻名。如果你需要一个跨多个操作系统的解决方案,而你的团队更熟悉 Java,可能会选择用 Java 编写核心逻辑。
-
-
生态系统和社区支持:
-
Java 拥有庞大的生态系统和社区支持。如果项目依赖于特定的 Java 库或框架,可能需要在 C/C+ 应用程序中调用 Java 代码。
-
-
高级特性:
-
Java 提供了许多高级语言特性,如自动内存管理和垃圾回收、丰富的标准库等,这些特性可能在 C/C+ 中难以实现或需要大量额外的工作。
-
-
并发处理:
-
Java 拥有强大的并发处理能力,包括内置的多线程支持和并发工具。如果 C/C+ 应用程序需要执行复杂的并发任务,可能会考虑使用 Java。
-
-
集成第三方服务:
-
许多第三方服务和 API 提供 Java 绑定或客户端,可能没有 C/C++ 绑定。在这种情况下,你可能需要从 C/C+ 应用程序中调用 Java 代码来与这些服务交互。
-
-
性能优化:
-
在某些情况下,你可能需要在 C/C+ 中实现性能关键型代码,同时利用 Java 提供的其他功能。通过在 C/C+ 中调用 Java,可以在一个应用程序中混合使用两种语言的优势。
-
-
团队专长:
-
项目团队可能在 Java 方面有特定的专业知识或资源,而 C/C+ 团队可能在系统的核心功能或性能优化方面更为擅长。
-
注意事项
虽然在 C/C+ 中调用 Java 是可能的,但需要注意以下事项:
-
JNI 的复杂性:Java Native Interface (JNI) 可以复杂且难以正确使用。它增加了代码的复杂性,并可能导致不稳定和难以维护的应用程序。
-
性能开销:JNI 调用可能引入额外的性能开销,特别是在需要频繁调用的情况下。
-
内存管理:JNI 需要手动管理内存,这可能导致内存泄漏或其他内存相关的问题。
在决定在 C/C+ 应用程序中调用 Java 之前,应该仔细评估是否真的需要这么做,以及这么做的潜在影响。在某些情况下,可能更合理的选择是将 Java 代码重写为 C/C+,或者寻找可以替代 Java 功能的 C/C+ 库。
必备环境
1.必须要在电脑上安装Java环境。配置环境参考文章:
Java详细安装配置教程(Windows),从下载到配置——Java-1.8(jdk)安装_jre1.8-CSDN博客
在C++代码中需要配置JVM的动态库才可以使用。
实现步骤
在C++中调用Java代码通常通过Java Native Interface(JNI)来实现。以下是详细的步骤和示例代码:
步骤 1:编写Java代码
首先,编写一个Java类,其中包含你希望从C++调用的方法。例如:
public class Sample2 {
public String name;
public static String sayHello(String name) {
return "Hello, " + name + "!";
}
public String sayHello() {
return "Hello, " + name + "!";
}
}
步骤 2:编译Java代码并生成JNI头文件
使用javac
编译Java代码,并使用javah
工具生成JNI头文件。javah
工具在较新的JDK版本中已被javac -h
替代。
javac Sample2.java
这将生成一个名为JavaClass.class
的JNI头文件。
步骤 3:编写C++代码
在C++代码中,使用JNI调用Java方法。以下是示例代码:
// #include <iostream>
// using namespace std;
// int main()
// {
// cout << "cmake Hello World!" << endl;
// return 0;
// }
#include <iostream>
using namespace std;
#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=.";
// options[0].optionString = "-Djava.class.path=E:\\C++\\src\\C++_call_java\\java_src";
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);
cout<< status<<endl;
if (status != JNI_ERR)
{
cout<<"open JVM"<<endl;
}
else
{
cout<<"error open JVM"<<endl;
return -1;
}
cls = env->FindClass("Sample2");
if(cls == 0)
{
cout<<"NO class "<<endl;
return -2;
}
cout<<"获取到了class对象"<<endl;
#if 0
cout<<"========================1==========================="<<endl;
// 获取方法ID, 通过方法名和签名, 调用静态方法
mid = env->GetStaticMethodID(cls,"sayHello","(Ljava/lang/String;)Ljava/lang/String;");
if (mid == 0)
{
cout<<"NO method "<<endl;
return -2;
}
const char* name = "World";
//从C转换为java的字符, 使用NewStringUTF方法
jstring arg = env->NewStringUTF(name);
//调用静态方法
jstring result =(jstring)env->CallStaticObjectMethod(cls,mid,arg);
// 从java转换为C的字符, 使用GetStringUTFChars
const char*str = env->GetStringUTFChars(result,0);
printf("Result of sayHello: %s\n", str);
env->ReleaseStringUTFChars(result,0);
#else
cout<<"========================2==========================="<<endl;
/*** 新建一个对象 ***/
// 调用默认构造函数
//obj = (*env)->AllocObject(env, cls);
// 调用指定的构造函数, 构造函数的名字叫做<init>
mid = env->GetMethodID(cls,"<init>","()V");
obj = env->NewObject(cls,mid);
if(obj == 0)
{
printf("Create object failed!\n");
return -2;
}
// 获取属性ID, 通过属性名和签名
fid = env->GetFieldID(cls,"name","Ljava/lang/String;");
if(fid ==0)
{
printf("attribute get failed.\n");
}
const char* name = "icejoywoo";
jstring arg = env->NewStringUTF(name);
env->SetObjectField(obj,fid,arg);
mid = env->GetMethodID(cls,"sayHello", "()Ljava/lang/String;");
if(mid ==0)
{
cout<<"NO method "<<endl;
return -2;
}
// 调用成员方法
jstring result = (jstring)env->CallObjectMethod(obj, mid);
const char* str = env->GetStringUTFChars( result, 0);
printf("Result of sayHello: %s\n", str);
env->ReleaseStringUTFChars(result, 0);
jvm->DestroyJavaVM();
#endif
#if 0
// 启动虚拟机
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)->AllocObject(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)
{
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);
return 0;
}
else
{
printf("JVM Created failed!\n");
return -1;
}
#endif
}
步骤 4:加载Java虚拟机(JVM)
在C++代码中,需要加载JVM并获取JNIEnv
指针。以下是加载JVM的代码:
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=.";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
jint res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (res < 0) {
std::cerr << "Failed to create JVM" << std::endl;
return -1;
}
步骤 5:调用Java方法
在C++代码中,使用JNIEnv
指针调用Java方法。这已经在前面的callJavaMethod
函数中展示。
步骤 6:清理资源
在程序结束时,需要清理资源并关闭JVM:
jvm->DestroyJavaVM();
注意事项
-
异常处理:在调用Java方法时,可能会抛出异常。使用
env->ExceptionOccurred()
和env->ExceptionClear()
来处理异常。 -
数据类型转换:JNI提供了函数来处理Java和C++之间的数据类型转换。
-
性能开销:频繁的JNI调用可能会带来性能开销,因此建议在非频繁交互的场景中使用。
通过以上步骤,你可以在C++程序中调用Java代码,利用JNI在两种语言之间传递数据和功能。
文件编译
pro文件
TEMPLATE = app
CONFIG += console c++17
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += \
main.cpp
INCLUDEPATH += E:/software/java/jdk/include E:/software/java/jdk/include/win32
# LIBS+= -LE:\software\java\jdk\lib -ljvm
LIBS +=-LE:\software\java\jdk\jre\bin\server -ljvm
# QMAKE_CXXFLAGS += -fPIC
CMakeLists.txt文件
cmake_minimum_required(VERSION 3.16)
project(simple LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(simple main.cpp)
# include_directories target_include_directories 添加头文件的路径
include_directories("E:/software/java/jdk/include" E:/software/java/jdk/include/win32 ) #ok
# include_directories("E:\software\java\jdk\include") #no
# include_directories("E:\\software\\java\\jdk\\include\\win32") #ok
# set (JAVA_PATH E:/software/java/jdk/include)
# include_directories(${JAVA_PATH}/win32)
# 设置 Java 安装路径
set(JAVA_HOME "E:/software/java/jdk")
# 查找所有 DLL 文件
file(GLOB JavaDlls "${JAVA_HOME}/jre/bin/server/*.dll")
# 链接 Java DLL 文件
target_link_libraries(simple ${JavaDlls})
# link_directories(simple "E:\\software\\java\\jdk\\jre\\bin\\server\\")
# link_libraries(simple "E:\\software\\java\\jdk\\jre\\bin\\server\\jvm.dll")
# 链接生成的库
# target_link_libraries(simple "E:\\software\\java\\jdk\\jre\\bin\\server\\jvm.dll")
include(GNUInstallDirs)
install(TARGETS simple
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
二 Java调用C/C++
Java调用C/C++时,遵循几个步骤:
1、 用Java native关键字声明方法为本地方法(非Java语言实现)。
2、 编译该声明类,得到XXX.class文件。
3、 用“javah –jni XXX”命令从该class文件生成C语言头文件(XXX.h)。
4、 采用C语言实现该头文件声明的方法,将实现类编译成库文件(libXXX.so)。
5、在Java程序中使用System.loadLibrary(XXX)加载该库文件(需要设置-Djava.library.path环境变量指向该库文件存放路径)。
6、 即可象调用Java方法一样,调用native方式声明的本地方法。
(暂时未用到,后续如有需要联系作者进行补充)