You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过JNI实现Java类对接C++抽象类及NumberManager?

问题描述

我刚接触JNI和Java,了解基本的本地代码调用流程,但不清楚如何通过JNI让Java类对接C的抽象类(比如NumberInterface)并实现扩展。另外,我需要让Java侧能和C的NumberManagerInterface及实现类NumberManager交互,完成模块注册、数字处理等操作。

相关C++代码如下:

class NumberInterface {
public:
    using Numbers = std::unordered_set<unsigned int>;
 
    virtual Numbers getSupportedNumbers() = 0;
    virtual bool handleNumber(unsigned int number) = 0;
    virtual bool cancelNumber(unsigned int number) = 0;
};

原生侧可以链接这个共享库并实现具体类:

class Number : public NumberInterface {
public:
    Numbers getSupportedNumbers() override;
    bool handleNumber(unsigned int number) override;
    bool cancelNumber(unsigned int number) override;
    // ... 其他实现代码
};

我想实现Java侧的类对接这个NumberInterface,比如:

class JavaNumber extends JavaNumberInterfaceEquivalent {
    // ... Java侧实现
}

补充的C++工具类代码:

/// 处理数字的NumberManagerInterface
class NumberManagerInterface {
public:
    /**
     * 注册处理数字的模块
     * @return 成功返回true,否则返回false
     */
    virtual bool registerNumberModule(const std::shared_ptr<NumberInterface>& module) = 0;
 
    virtual bool handleNumber(const unsigned int number) = 0;
    virtual bool cancelNumber(const unsigned int number) = 0;
};

/**
 * 示例NumberManager类
 */
class NumberManager : public NumberManagerInterface{
public:
    static std::shared_ptr<NumberManagerInterface> createNumberManagerInstance(...);
    bool registerNumberModule(const std::shared_ptr<NumberInterface>& module) override;
    bool handleNumber(const unsigned int number) override;
    bool cancelNumber(const unsigned int number) override;
    // ... 其他成员
private:
    std::vector<std::shared_ptr<NumberInterface>> m_numberHandlers;
    // ... 其他私有成员
};

bool NumberManager::registerNumberModule(const std::shared_ptr<NumberInterface>& module){
     // 执行验证逻辑
     m_numberHandlers.push_back(module);
     // ... 其他逻辑
     return true;
}
 
bool NumberManager::handleNumber(const unsigned int number){
  // 从m_numberHandlers中找到合适的处理器
  // 调用对应处理器的handleNumber方法
  return handler->handleNumber(number);
}
 
void NumberManager::cancelNumber(const unsigned int number){
    // ... 实现逻辑
}
解决方案

要实现Java类对接C抽象类,并与NumberManager交互,需要构建JNI桥接层,核心思路是用C类作为中间层,连接Java接口和C++抽象类的调用。

1. 定义Java侧的等价接口

先在Java中定义与C++NumberInterface一一对应的接口,确保方法签名匹配:

public interface JavaNumberInterface {
    // 对应C++的getSupportedNumbers(),返回Integer集合
    Set<Integer> getSupportedNumbers();
    boolean handleNumber(int number);
    boolean cancelNumber(int number);
}

同时定义Java侧的NumberManager交互类,封装JNI调用:

public class NumberManagerJni {
    // 加载原生库
    static {
        System.loadLibrary("number-jni");
    }

    // 初始化NumberManager实例
    public native long initNumberManager();

    // 注册Java实现的数字模块
    public native boolean registerNumberModule(long managerPtr, JavaNumberInterface module);

    // 调用NumberManager处理数字
    public native boolean handleNumber(long managerPtr, int number);

    // 调用NumberManager取消数字处理
    public native boolean cancelNumber(long managerPtr, int number);

    // 释放NumberManager资源
    public native void releaseNumberManager(long managerPtr);
}

2. 实现JNI桥接C++类

创建一个C桥接类,继承自NumberInterface,内部持有Java对象的引用,将C的虚函数调用转发到Java侧:

#include <jni.h>
#include <memory>
#include <unordered_set>
#include <vector>
#include "NumberInterface.h"
#include "NumberManagerInterface.h"

// 桥接类:将C++调用转发到Java
class JavaNumberBridge : public NumberInterface {
public:
    JavaNumberBridge(JNIEnv* env, jobject javaModule) 
        : m_env(env), m_javaModule(env->NewGlobalRef(javaModule)) {}

    ~JavaNumberBridge() {
        m_env->DeleteGlobalRef(m_javaModule);
    }

    Numbers getSupportedNumbers() override {
        jclass cls = m_env->GetObjectClass(m_javaModule);
        jmethodID methodId = m_env->GetMethodID(cls, "getSupportedNumbers", "()Ljava/util/Set;");
        jobject jSet = m_env->CallObjectMethod(m_javaModule, methodId);

        // 将Java的Set转换为C++的unordered_set<unsigned int>
        Numbers result;
        jclass setCls = m_env->GetObjectClass(jSet);
        jmethodID iteratorId = m_env->GetMethodID(setCls, "iterator", "()Ljava/util/Iterator;");
        jobject iterator = m_env->CallObjectMethod(jSet, iteratorId);
        jclass iterCls = m_env->GetObjectClass(iterator);
        jmethodID hasNextId = m_env->GetMethodID(iterCls, "hasNext", "()Z");
        jmethodID nextId = m_env->GetMethodID(iterCls, "next", "()Ljava/lang/Object;");

        while (m_env->CallBooleanMethod(iterator, hasNextId)) {
            jobject obj = m_env->CallObjectMethod(iterator, nextId);
            jint num = m_env->CallIntMethod(obj, m_env->GetMethodID(m_env->GetObjectClass(obj), "intValue", "()I"));
            result.insert(static_cast<unsigned int>(num));
            m_env->DeleteLocalRef(obj);
        }

        // 释放本地引用
        m_env->DeleteLocalRef(iterator);
        m_env->DeleteLocalRef(iterCls);
        m_env->DeleteLocalRef(setCls);
        m_env->DeleteLocalRef(jSet);
        m_env->DeleteLocalRef(cls);

        return result;
    }

    bool handleNumber(unsigned int number) override {
        jclass cls = m_env->GetObjectClass(m_javaModule);
        jmethodID methodId = m_env->GetMethodID(cls, "handleNumber", "(I)Z");
        jboolean result = m_env->CallBooleanMethod(m_javaModule, methodId, static_cast<jint>(number));
        
        m_env->DeleteLocalRef(cls);
        return static_cast<bool>(result);
    }

    bool cancelNumber(unsigned int number) override {
        jclass cls = m_env->GetObjectClass(m_javaModule);
        jmethodID methodId = m_env->GetMethodID(cls, "cancelNumber", "(I)Z");
        jboolean result = m_env->CallBooleanMethod(m_javaModule, methodId, static_cast<jint>(number));
        
        m_env->DeleteLocalRef(cls);
        return static_cast<bool>(result);
    }

private:
    JNIEnv* m_env;
    jobject m_javaModule; // 全局引用,避免GC回收
};

3. 实现JNI方法

编写JNI函数,处理Java侧的调用,完成NumberManager的初始化、模块注册等操作:

// 存储全局的JNIEnv引用(单线程示例,多线程需调整)
static JNIEnv* g_env;

// 初始化NumberManager
extern "C" JNIEXPORT jlong JNICALL
Java_com_example_NumberManagerJni_initNumberManager(JNIEnv* env, jobject thiz) {
    g_env = env;
    std::shared_ptr<NumberManagerInterface> manager = NumberManager::createNumberManagerInstance();
    // 将智能指针转换为void*,传递给Java侧作为句柄
    return reinterpret_cast<jlong>(manager.get());
}

// 注册Java模块
extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_NumberManagerJni_registerNumberModule(JNIEnv* env, jobject thiz, jlong managerPtr, jobject module) {
    auto manager = reinterpret_cast<NumberManagerInterface*>(managerPtr);
    std::shared_ptr<NumberInterface> bridge = std::make_shared<JavaNumberBridge>(env, module);
    return static_cast<jboolean>(manager->registerNumberModule(bridge));
}

// 处理数字
extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_NumberManagerJni_handleNumber(JNIEnv* env, jobject thiz, jlong managerPtr, jint number) {
    auto manager = reinterpret_cast<NumberManagerInterface*>(managerPtr);
    return static_cast<jboolean>(manager->handleNumber(static_cast<unsigned int>(number)));
}

// 取消数字处理
extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_NumberManagerJni_cancelNumber(JNIEnv* env, jobject thiz, jlong managerPtr, jint number) {
    auto manager = reinterpret_cast<NumberManagerInterface*>(managerPtr);
    return static_cast<jboolean>(manager->cancelNumber(static_cast<unsigned int>(number)));
}

// 释放NumberManager资源
extern "C" JNIEXPORT void JNICALL
Java_com_example_NumberManagerJni_releaseNumberManager(JNIEnv* env, jobject thiz, jlong managerPtr) {
    auto manager = reinterpret_cast<NumberManagerInterface*>(managerPtr);
    // 根据NumberManager的内存管理方式释放,若用智能指针需确保引用计数归零
    delete manager;
}

4. Java侧实现与使用

实现Java侧的JavaNumberInterface,并通过NumberManagerJni与原生交互:

import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;

public class JavaNumberImpl implements JavaNumberInterface {
    @Override
    public Set<Integer> getSupportedNumbers() {
        // 返回Java侧支持的数字集合
        return new HashSet<>(Arrays.asList(1, 3, 5));
    }

    @Override
    public boolean handleNumber(int number) {
        System.out.println("Java处理数字:" + number);
        return true;
    }

    @Override
    public boolean cancelNumber(int number) {
        System.out.println("Java取消数字处理:" + number);
        return true;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        NumberManagerJni managerJni = new NumberManagerJni();
        long managerPtr = managerJni.initNumberManager();

        JavaNumberImpl numberModule = new JavaNumberImpl();
        managerJni.registerNumberModule(managerPtr, numberModule);

        // 调用处理数字
        managerJni.handleNumber(managerPtr, 3);
        // 取消处理
        managerJni.cancelNumber(managerPtr, 3);

        // 释放资源
        managerJni.releaseNumberManager(managerPtr);
    }
}

关键注意事项

  • 全局引用与本地引用:JNI中持有Java对象时,必须使用NewGlobalRef避免对象被GC回收,使用完后要调用DeleteGlobalRef释放。
  • 类型转换:注意Java与C++的类型映射(如jint对应int,jboolean对应bool),集合类需要手动转换。
  • 多线程安全:如果是Android或多线程场景,不能直接存储全局JNIEnv,需要通过AttachCurrentThread获取当前线程的JNIEnv。
  • 内存管理:确保C++侧的智能指针和Java侧的对象引用正确释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者bourne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:17:01