C/C++中调用Java实现
为什么需要在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 ':'
#endifint 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 0cout<<"========================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的字符, 使用GetStringUTFCharsconst char*str = env->GetStringUTFChars(result,0);printf("Result of sayHello: %s\n", str);env->ReleaseStringUTFChars(result,0);
#elsecout<<"========================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 -= qtSOURCES += \main.cppINCLUDEPATH += E:/software/java/jdk/include E:/software/java/jdk/include/win32# LIBS+= -LE:\software\java\jdk\lib -ljvmLIBS +=-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 simpleLIBRARY 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方式声明的本地方法。
(暂时未用到,后续如有需要联系作者进行补充)