C/C++中调用Java实现

为什么需要在C/C++中调用Java?

  1. 遗留系统或库

    • 某些遗留系统或库可能用 Java 编写,而你的 C/C++ 应用程序需要与这些系统或库集成。

  2. 跨平台兼容性

    • Java 以其“一次编写,到处运行”(Write Once, Run Anywhere)的能力而闻名。如果你需要一个跨多个操作系统的解决方案,而你的团队更熟悉 Java,可能会选择用 Java 编写核心逻辑。

  3. 生态系统和社区支持

    • Java 拥有庞大的生态系统和社区支持。如果项目依赖于特定的 Java 库或框架,可能需要在 C/C+ 应用程序中调用 Java 代码。

  4. 高级特性

    • Java 提供了许多高级语言特性,如自动内存管理和垃圾回收、丰富的标准库等,这些特性可能在 C/C+ 中难以实现或需要大量额外的工作。

  5. 并发处理

    • Java 拥有强大的并发处理能力,包括内置的多线程支持和并发工具。如果 C/C+ 应用程序需要执行复杂的并发任务,可能会考虑使用 Java。

  6. 集成第三方服务

    • 许多第三方服务和 API 提供 Java 绑定或客户端,可能没有 C/C++ 绑定。在这种情况下,你可能需要从 C/C+ 应用程序中调用 Java 代码来与这些服务交互。

  7. 性能优化

    • 在某些情况下,你可能需要在 C/C+ 中实现性能关键型代码,同时利用 Java 提供的其他功能。通过在 C/C+ 中调用 Java,可以在一个应用程序中混合使用两种语言的优势。

  8. 团队专长

    • 项目团队可能在 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方式声明的本地方法。

(暂时未用到,后续如有需要联系作者进行补充)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

only-lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值