什么是Java Native Interface(JNI)?

news2025/1/12 6:18:40

一、Java Native Interface(JNI)概述

Java Native Interface(JNI)是 Java 编程语言提供的一种机制,它允许 Java 代码与用其他编程语言(如 C、C++)编写的代码进行交互。这种交互能力在很多场景下都非常有用,比如:

  • 性能优化:当 Java 应用中某些关键代码片段对性能要求极高,而 Java 本身的执行效率无法满足需求时,可以使用 JNI 调用用 C 或 C++ 编写的高效代码,因为这些语言在底层操作、内存管理等方面往往能实现更高效的执行。
  • 利用现有库:在系统中已经存在大量用 C 或 C++ 编写的成熟的库(例如图像处理库、数据库访问库等),通过 JNI 可以方便地在 Java 应用中复用这些库,避免重复开发。

JNI 的核心思想是在 Java 代码中定义一些特殊的方法,这些方法能够与外部的本地代码建立连接,然后在运行时,Java 虚拟机(JVM)会负责加载和执行这些本地代码,实现 Java 与本地代码之间的数据传递和功能调用。

二、JNI 开发环境搭建

在开始使用 JNI 进行开发之前,需要搭建相应的开发环境,以下以 Java 和 C++ 为例(假设你已经安装好了 Java 开发工具包 JDK 和 C++ 编译器,如 GCC 等):

1. 编写 Java 代码

首先创建一个简单的 Java 类,用于定义要调用本地方法的接口。例如创建一个名为 HelloJNI.java 的文件,内容如下:

class HelloJNI {
    // 声明一个本地方法,该方法在本地代码中实现
    public native void sayHello();

    public static void main(String[] args) {
        System.out.println("Java程序开始...");
        HelloJNI hello = new HelloJNI();
        hello.sayHello();
        System.out.println("Java程序结束...");
    }

    // 使用静态代码块加载包含本地方法实现的库
    static {
        System.loadLibrary("HelloJNI");
    }
}

在上述代码中:

  • 定义了一个名为 sayHello 的本地方法,使用 native 关键字修饰,表示这个方法的实现不在 Java 代码中,而是在外部的本地代码里。
  • 在 main 方法中,创建了 HelloJNI 类的实例,并尝试调用 sayHello 方法。
  • 通过静态代码块使用 System.loadLibrary 方法加载名为 "HelloJNI" 的本地库,注意这里传入的库名在不同操作系统下实际对应的文件名会有所不同(例如在 Windows 下是 HelloJNI.dll,在 Linux 下是 libHelloJNI.so 等)。
2. 生成头文件

使用 javac 命令先将 HelloJNI.java 编译成字节码文件 HelloJNI.class

javac HelloJNI.java

然后使用 javah 命令(在 JDK 8 及之前版本可用,JDK 9 及之后推荐使用 javac -h 命令)生成对应的 C/C++ 头文件,命令如下:

javac -h. HelloJNI.java

这会在当前目录下生成一个名为 HelloJNI.h 的头文件,其内容大致如下(根据不同 JDK 版本可能略有差异):

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这个头文件定义了一个函数原型,该函数就是对应 Java 中 sayHello 本地方法在 C/C++ 中的接口,函数名遵循 Java_<包名>_<类名>_<方法名> 的命名规则(如果类在包中,需要带上完整的包名路径),参数中的 JNIEnv 是一个指向 JNI 环境的指针,通过它可以访问 JNI 提供的各种函数,jobject 表示调用该本地方法的 Java 对象实例(在静态方法中这个参数为 jclass,代表类对象)。

3. 编写 C/C++ 实现代码

创建一个名为 HelloJNI.cpp 的文件,用于实现头文件中定义的函数,内容如下:

#include "HelloJNI.h"
#include <iostream>

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
    std::cout << "Hello from C++ via JNI!" << std::endl;
}

在上述代码中:

  • 包含了之前生成的 HelloJNI.h 头文件以及 iostream 头文件用于输出信息。
  • 实现了 Java_HelloJNI_sayHello 函数,函数体中简单地输出了一段文字,这里可以根据实际需求编写更复杂的功能代码,比如操作内存、调用其他 C/C++ 库等。
4. 编译生成动态链接库

接下来需要将 C/C++ 代码编译成动态链接库,以便 Java 程序能够加载并调用。

  • 在 Windows 下(使用 MinGW 等编译器)
g++ -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.cpp -o HelloJNI.dll

这里的 -shared 表示生成动态链接库,-I 选项用于指定头文件搜索路径,%JAVA_HOME% 是 Java 安装目录的环境变量,最后生成的动态链接库文件名为 HelloJNI.dll

  • 在 Linux 下(使用 GCC 等编译器)
g++ -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" HelloJNI.cpp -o libHelloJNI.so

-fPIC 选项用于生成位置无关代码(Position Independent Code),这是生成共享库所需要的,最终生成的库文件名为 libHelloJNI.so

5. 运行 Java 程序

确保生成的动态链接库文件在 Java 程序能找到的路径下(可以通过设置系统环境变量 LD_LIBRARY_PATH 等方式,或者将库文件放在 Java 程序的当前目录等),然后运行 Java 程序:

java HelloJNI

就可以看到 Java 程序调用了 C++ 实现的本地方法,并输出相应的信息。

三、JNI 数据类型映射

在 JNI 中,Java 数据类型和本地(C/C++)数据类型之间有明确的映射关系,了解这些映射关系对于正确地在 Java 和本地代码之间传递数据非常重要。

1. 基本数据类型映射
Java 数据类型C/C++ 数据类型(JNI 定义)
booleanjboolean(实际上是 unsigned char,值为 0 表示 false,非 0 表示 true
bytejbyte(等价于 signed char
charjchar(等价于 unsigned short
shortjshort(等价于 short
intjint(等价于 int
longjlong(等价于 long long
floatjfloat(等价于 float
doublejdouble(等价于 double

例如,下面是一个在 Java 和 C++ 之间传递基本数据类型的示例。

Java 代码(BasicTypeJNI.java

class BasicTypeJNI {
    public native int add(int a, int b);

    public static void main(String[] args) {
        BasicTypeJNI basic = new BasicTypeJNI();
        int result = basic.add(5, 3);
        System.out.println("在Java中调用本地方法计算结果:" + result);

        static {
            System.loadLibrary("BasicTypeJNI");
        }
    }
}

生成的头文件(BasicTypeJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class BasicTypeJNI */

#ifndef _Included_BasicTypeJNI
#define _Included_BasicTypeJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     BasicTypeJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_BasicTypeJNI_add(JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(BasicTypeJNI.cpp

#include "BasicTypeJNI.h"

JNIEXPORT jint JNICALL Java_BasicTypeJNI_add(JNIEnv *, jobject, jint a, jint b) {
    return a + b;
}

在这个示例中,Java 中的 int 类型参数传递到 C++ 代码中对应的就是 jint 类型,并且 C++ 中计算的结果(也是 jint 类型)可以正确地返回给 Java 代码。

2. 引用类型映射
  • 对象引用

在 JNI 中,Java 对象在本地代码中通过 jobject 类型来表示,对于不同的具体对象类型(如 StringObject 等),也有对应的特定类型,例如 jstring 表示 Java 中的 String 对象。

示例:在 Java 和 C++ 之间传递和操作 String 对象

Java 代码(StringJNI.java

class StringJNI {
    public native void printString(String str);

    public static void main(String[] args) {
        StringJNI stringJNI = new StringJNI();
        stringJNI.printString("Hello from Java to C++ via JNI!");

        static {
            System.loadLibrary("StringJNI");
        }
    }
}

生成的头文件(StringJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class StringJNI */

#ifndef _Included_StringJNI
#define _Included_StringJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     StringJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_StringJNI_printString(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(StringJNI.cpp

#include "StringJNI.h"
#include <iostream>

JNIEXPORT void JNICALL Java_StringJNI_printString(JNIEnv *env, jobject obj, jstring jstr) {
    const char *str = env->GetStringUTFChars(jstr, NULL);
    if (str!= NULL) {
        std::cout << "从Java传递过来的字符串:" << str << std::endl;
        env->ReleaseStringUTFChars(jstr, str);
    }
}

在上述 C++ 代码中,通过 env->GetStringUTFChars 函数将 jstring 类型的对象转换为 C 风格的字符串(以 UTF-8 编码格式)进行操作,操作完成后需要使用 env->ReleaseStringUTFChars 函数释放相关的资源,避免内存泄漏。

  • 数组引用

对于 Java 中的数组,在 JNI 中也有对应的类型表示,例如 jintArray 表示 int 类型的数组,jbyteArray 表示 byte 类型的数组等。

示例:在 Java 和 C++ 之间传递和处理数组

Java 代码(ArrayJNI.java

class ArrayJNI {
    public native int[] sumArray(int[] arr);

    public static void main(String[] args) {
        ArrayJNI arrayJNI = new ArrayJNI();
        int[] arr = {1, 2, 3, 4, 5};
        int[] result = arrayJNI.sumArray(arr);
        System.out.println("在Java中处理数组结果:");
        for (int num : result) {
            System.out.print(num + " ");
        }

        static {
            System.loadLibrary("ArrayJNI");
        }
    }
}

生成的头文件(ArrayJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ArrayJNI */

#ifndef _Included_ArrayJNI
#define _Included_ArrayJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ArrayJNI
 * Method:    sumArray
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_ArrayJNI_sumArray(JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(ArrayJNI.cpp

#include "ArrayJNI.h"

JNIEXPORT jintArray JNICALL Java_ArrayJNI_sumArray(JNIEnv *env, jobject obj, jintArray jarr) {
    // 获取数组长度
    jsize len = env->GetArrayLength(jarr);
    // 获取数组元素指针
    jint *arr = env->GetIntArrayElements(jarr, NULL);
    if (arr!= NULL) {
        for (int i = 0; i < len; i++) {
            arr[i] += 1;
        }
        // 创建一个新的数组用于返回结果
        jintArray result = env->NewIntArray(len);
        if (result!= NULL) {
            env->SetIntArrayRegion(result, 0, len, arr);
            env->ReleaseIntArrayElements(jarr, arr, JNI_ABORT);
            return result;
        }
    }
    return NULL;
}

在这个示例中,首先通过 env->GetArrayLength 获取数组长度,然后使用 env->GetIntArrayElements 获取数组元素的指针,在对数组元素进行修改后,创建一个新的数组 result,通过 env->SetIntArrayRegion 将修改后的元素复制到新数组中,最后释放相关资源并返回新数组给 Java 代码。

四、JNI 中的函数调用与内存管理

1. JNIEnv 指针的使用

JNIEnv 指针是在本地代码中访问 JNI 各种功能函数的关键,它提供了众多的函数来操作 Java 对象、调用 Java 方法、进行内存管理等。

例如,除了前面示例中展示的操作字符串、数组的函数外,还可以通过 JNIEnv 调用 Java 对象的方法:

Java 代码(CallMethodJNI.java

class CallMethodJNI {
    public void printMessage(String msg) {
        System.out.println("在Java中打印消息:" + msg);
    }

    public native void callJavaMethod();

    public static void main(String[] args) {
        CallMethodJNI call = new CallMethodJNI();
        call.callJavaMethod();

        static {
            System.loadLibrary("CallMethodJNI");
        }
    }
}

生成的头文件(CallMethodJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CallMethodJNI */

#ifndef _Included_CallMethodJNI
#define _Included_CallMethodJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     CallMethodJNI
 * Method:    callJavaMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_CallMethodJNI_callJavaMethod(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(CallMethodJNI.cpp

#include "CallMethodJNI.h"

JNIEXPORT void JNICALL Java_CallMethodJNI_callJavaMethod(JNIEnv *env, jobject obj) {
    jclass cls = env->GetObjectClass(obj);
    if (cls!= NULL) {
        jmethodID mid = env->GetMethodID(cls, "printMessage", "(Ljava/lang/String;)V");
        if (mid!= NULL) {
            jstring msg = env->NewStringUTF("从C++调用Java方法");
            env->CallVoidMethod(obj, mid, msg);
            env->DeleteLocalRef(msg);
        }
        env->DeleteLocalRef(cls);
    }
}

在上述 C++ 代码中:

  • 首先通过 env->GetObjectClass 获取 Java 对象对应的 jclass(类对象)。
  • 然后使用 env->GetMethodID 获取要调用的 Java 方法的 jmethodID(方法标识符),这里需要传入正确的方法名和方法签名(可以通过 javap -s 命令查看 Java 类的方法签名)。
  • 接着使用 env->NewStringUTF 创建一个 jstring 对象作为方法参数,再通过 env->CallVoidMethod 调用 Java 对象的 printMessage 方法,最后使用 env->DeleteLocalRef 释放创建的局部引用对象(如 jclassjstring 等),避免内存泄漏,因为 JNI 中局部引用在使用完后需要手动释放。
2. 内存管理

在 JNI 中,内存管理是一个重要且容易出错的部分,由于涉及到 Java 和本地代码(如 C/C++)之间的交互,需要谨慎处理内存的分配、使用和释放,以确保程序的正确性和稳定性。

1. 局部引用与全局引用

  • 局部引用(Local References)
    当通过 JNIEnv 相关函数创建的对象引用(比如 jstringjclassjobject 等),在默认情况下都是局部引用。局部引用只在创建它的本地方法执行期间有效,当本地方法执行完毕返回 Java 代码时,JVM 会自动释放这些局部引用所指向的对象内存。例如在前面调用 Java 对象方法的示例中:
JNIEXPORT void JNICALL Java_CallMethodJNI_callJavaMethod(JNIEnv *env, jobject obj) {
    jclass cls = env->GetObjectClass(obj);
    if (cls!= NULL) {
        jmethodID mid = env->GetMethodID(cls, "printMessage", "(Ljava/lang/String;)V");
        if (mid!= NULL) {
            jstring msg = env->NewStringUTF("从C++调用Java方法");
            env->CallVoidMethod(obj, mid, msg);
            env->DeleteLocalRef(msg);  // 手动释放局部引用jstring对象
        }
        env->DeleteLocalRef(cls);  // 手动释放局部引用jclass对象
    }
}

这里使用 env->DeleteLocalRef 显式地释放了 jstring 和 jclass 局部引用,虽然在本地方法返回时 JVM 也会清理它们,但及时释放可以避免在复杂的代码逻辑中过早地耗尽内存,尤其是当局部引用数量较多时。

  • 全局引用(Global References)
    与局部引用不同,全局引用在整个 Java 应用程序的生命周期内都有效,直到显式地使用 JNIEnv 的 DeleteGlobalRef 函数释放它。全局引用适用于需要长期保存 Java 对象引用的情况,比如在一个本地代码模块中,要多次访问同一个 Java 对象,并且希望在多次调用本地方法的间隔期间也能保留对该对象的引用。

以下是一个简单的创建和使用全局引用的示例:

Java 代码(GlobalRefJNI.java

class GlobalRefJNI {
    public native void createGlobalRef();
    public native void useGlobalRef();
    public static void main(String[] args) {
        GlobalRefJNI global = new GlobalRefJNI();
        global.createGlobalRef();
        global.useGlobalRef();

        static {
            System.loadLibrary("GlobalRefJNI");
        }
    }
}

生成的头文件(GlobalRefJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class GlobalRefJNI */

#ifndef _Included_GlobalRefJNI
#define _Included_GlobalRefJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     GlobalRefJNI
 * Method:    createGlobalRef
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_GlobalRefJNI_createGlobalRef(JNIEnv *, jobject);

/*
 * Class:     GlobalRefJNI
 * Method:    useGlobalRef
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_GlobalRefJNI_useGlobalRef(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(GlobalRefJNI.cpp

#include "GlobalRefJNI.h"

jobject globalObj = NULL;

JNIEXPORT void JNICALL Java_GlobalRefJNI_createGlobalRef(JNIEnv *env, jobject obj) {
    globalObj = env->NewGlobalRef(obj);
}

JNIEXPORT void JNICALL Java_GlobalRefJNI_useGlobalRef(JNIEnv *env, jobject obj) {
    if (globalObj!= NULL) {
        jclass cls = env->GetObjectClass(globalObj);
        if (cls!= NULL) {
            jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
            if (mid!= NULL) {
                jstring result = (jstring)env->CallObjectMethod(globalObj, mid);
                const char *str = env->GetStringUTFChars(result, NULL);
                if (str!= NULL) {
                    std::cout << "通过全局引用调用toString方法的结果:" << str << std::endl;
                    env->ReleaseStringUTFChars(result, str);
                }
                env->DeleteLocalRef(result);
            }
            env->DeleteLocalRef(cls);
        }
    }
}

在这个示例中,createGlobalRef 方法通过 env->NewGlobalRef 创建了一个全局引用 globalObj,保存了传入的 Java 对象的引用。然后在 useGlobalRef 方法中,就可以利用这个全局引用在后续的调用中访问对应的 Java 对象的方法等,最后当不再需要这个全局引用时(比如应用程序退出前),需要使用 env->DeleteGlobalRef 来释放它,避免内存泄漏。

2. 直接内存访问与内存释放

除了通过 JNIEnv 提供的函数来操作 Java 对象的内存,在一些情况下,可能需要直接访问和操作内存,比如在处理大量数据的数组或者和底层硬件交互时。

例如,在处理 Java 的字节数组时,可以通过 GetByteArrayElements 获取指向字节数组元素的指针,然后进行直接的内存读写操作,但操作完成后一定要记得释放内存,使用 ReleaseByteArrayElements 函数来告知 JVM 内存的使用情况。

以下是一个示例,展示了在 C++ 中直接修改 Java 字节数组的内容:

Java 代码(DirectMemoryJNI.java

class DirectMemoryJNI {
    public native void modifyByteArray(byte[] arr);
    public static void main(String[] args) {
        byte[] arr = new byte[10];
        for (int i = 0; i < 10; i++) {
            arr[i] = (byte)i;
        }
        DirectMemoryJNI direct = new DirectMemoryJNI();
        direct.modifyByteArray(arr);
        System.out.println("在Java中查看修改后的数组:");
        for (byte b : arr) {
            System.out.print(b + " ");
        }

        static {
            System.loadLibrary("DirectMemoryJNI");
        }
    }
}

生成的头文件(DirectMemoryJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class DirectMemoryJNI */

#ifndef _Included_DirectMemoryJNI
#define _Included_DirectMemoryJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     DirectMemoryJNI
 * Method:    modifyByteArray
 * Signature: ([B)V
 */
JNIEXPORT void JNICALL Java_DirectMemoryJNI_modifyByteArray(JNIEnv *, jobject, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(DirectMemoryJNI.cpp

#include "DirectMemoryJNI.h"

JNIEXPORT void JNICALL Java_DirectMemoryJNI_modifyByteArray(JNIEnv *env, jobject obj, jbyteArray jarr) {
    jbyte *arr = env->GetByteArrayElements(jarr, NULL);
    if (arr!= NULL) {
        for (int i = 0; i < 10; i++) {
            arr[i] += 10;
        }
        env->ReleaseByteArrayElements(jarr, arr, 0);
    }
}

在这个示例中,通过 GetByteArrayElements 获取到字节数组的指针后,对每个元素进行了加 10 的操作,然后通过 ReleaseByteArrayElements 释放内存,其中第三个参数 0 表示将修改后的内容复制回 Java 数组中(不同的参数值有不同的含义,比如 JNI_ABORT 表示不复制修改后的内容回 Java 数组等)。

五、JNI 在实际项目中的应用场景与案例

1. 性能敏感的算法优化

在一些科学计算、图形处理等领域,Java 的性能可能无法满足实时性和计算效率的要求。例如在一个图像识别的 Java 应用中,图像的特征提取算法如果用 Java 实现可能会比较耗时,而采用 C++ 编写的成熟的图像特征提取库(如 OpenCV 中的相关算法),通过 JNI 将其集成到 Java 应用中,就可以大大提高处理速度。

假设我们有一个简单的图像灰度化处理功能,原本用 Java 实现可能是这样:

Java 代码(ImageProcessJava.java,简单示意,非完整可用代码)

class ImageProcessJava {
    public static int[] grayScale(int[] pixels, int width, int height) {
        int[] grayPixels = new int[pixels.length];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int argb = pixels[i * width + j];
                int alpha = (argb >> 24) & 0xff;
                int red = (argb >> 16) & 0xff;
                int green = (argb >> 8) & 0xff;
                int blue = argb & 0xff;
                int gray = (int)(0.299 * red + 0.587 * green + 0.114 * blue);
                grayPixels[i * width + j] = (alpha << 24) | (gray << 16) | (gray << 8) | gray;
            }
        }
        return grayPixels;
    }

    public static void main(String[] args) {
        int width = 100;
        int height = 100;
        int[] pixels = new int[width * height];
        // 初始化像素数据等
        int[] result = grayScale(pixels, width, height);
    }
}

如果使用 OpenCV(通过 JNI 调用)来实现同样的功能,首先需要配置好 JNI 环境以及 OpenCV 库(这里省略具体的配置步骤),然后编写如下代码:

Java 代码(ImageProcessJNI.java,部分示意)

class ImageProcessJNI {
    public native int[] grayScale(int[] pixels, int width, int height);
    public static void main(String[] args) {
        ImageProcessJNI process = new ImageProcessJNI();
        int width = 100;
        int height = 100;
        int[] pixels = new int[width * height];
        // 初始化像素数据等
        int[] result = process.grayScale(pixels, width, height);

        static {
            System.loadLibrary("ImageProcessJNI");
        }
    }
}

生成的头文件(ImageProcessJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ImageProcessJNI */

#ifndef _Included_ImageProcessJNI
#define _Included_ImageProcessJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ImageProcessJNI
 * Method:    grayScale
 * Signature: ([III)[I
 */
JNIEXPORT jintArray JNICALL Java_ImageProcessJNI_grayScale(JNIEnv *, jobject, jintArray, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(结合 OpenCV,ImageProcessJNI.cpp,部分示意)

#include "ImageProcessJNI.h"
#include <opencv2/opencv.hpp>

JNIEXPORT jintArray JNICALL Java_ImageProcessJNI_grayScale(JNIEnv *env, jobject obj, jintArray jpixels, jint width, jint height) {
    // 将jintArray转换为cv::Mat(OpenCV中的图像数据结构)
    jint *pixels = env->GetIntArrayElements(jpixels, NULL);
    cv::Mat img(height, width, CV_8UC4, pixels);
    cv::Mat grayImg;
    cv::cvtColor(img, grayImg, cv::COLOR_RGB2GRAY);
    // 将处理后的图像数据再转换回jintArray返回给Java
    jintArray result = env->NewIntArray(width * height);
    env->SetIntArrayRegion(result, 0, width * height, (jint*)grayImg.data);
    env->ReleaseIntArrayElements(jpixels, pixels, JNI_ABORT);
    return result;
}

通过这样的方式,利用了 OpenCV 强大且高效的图像灰度化算法,相比纯 Java 实现能显著提升处理速度,尤其是在处理大规模图像数据时。

2. 复用现有 C/C++ 库

在企业级开发中,常常会有很多已有的用 C 或 C++ 编写的库,比如数据库连接库(如 MySQL 的 C API)、加密解密库等。以使用 MySQL 数据库为例,如果要在 Java 应用中直接访问 MySQL 数据库,除了使用 Java 的 JDBC(Java Database Connectivity)方式外,也可以通过 JNI 调用 MySQL 的 C API 来实现更底层、更灵活的数据库操作。

以下是一个简单的示意,展示如何通过 JNI 调用 MySQL 的 C API 来执行一个简单的查询操作(这里省略了完整的错误处理等代码,仅作原理演示):

Java 代码(MySQLJNI.java,部分示意)

class MySQLJNI {
    public native void queryData();
    public static void main(String[] args) {
        MySQLJNI mySQL = new MySQLJNI();
        mySQL.queryData();

        static {
            System.loadLibrary("MySQLJNI");
        }
    }
}

生成的头文件(MySQLJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MySQLJNI */

#ifndef _Included_MySQLJNI
#define _Included_MySQLJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     MySQLJNI
 * Method:    queryData
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MySQLJNI_queryData(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现代码(MySQLJNI.cpp,部分示意,需要链接 MySQL 的 C 库)

#include "MySQLJNI.h"
#include <mysql/mysql.h>

JNIEXPORT void JNICALL Java_MySQLJNI_queryData(JNIEnv *env, jobject obj) {
    MYSQL *mysql = mysql_init(NULL);
    if (mysql!= NULL) {
        if (mysql_real_connect(mysql, "localhost", "user", "password", "database", 0, NULL, 0)) {
            // 执行查询语句
            if (mysql_query(mysql, "SELECT * FROM some_table")) {
                // 处理查询错误
            } else {
                MYSQL_RES *result = mysql_store_result(mysql);
                if (result!= NULL) {
                    // 处理查询结果
                    mysql_free_result(result);
                }
            }
            mysql_close(mysql);
        }
    }
}

通过这样的 JNI 调用,可以在 Java 应用中充分利用 MySQL 的 C API 的功能特性,实现一些特殊的数据库操作需求,或者在对性能、资源控制有更高要求的场景下进行数据库交互。

六、JNI 的局限性与注意事项

1. 平台相关性

由于 JNI 涉及到与本地代码(不同语言如 C/C++)的交互,而本地代码编译生成的动态链接库等是与具体的操作系统平台相关的。例如,在 Windows 下编译生成的 .dll 文件不能直接在 Linux 系统上使用,反之亦然。这就意味着如果要在多个平台上部署使用包含 JNI 的 Java 应用,需要针对每个平台分别进行本地代码的编译和配置,增加了开发和部署的复杂性。

2. 内存管理复杂性

如前面所述,JNI 中的内存管理涉及到 Java 和本地代码之间的协调,既要遵循 Java 的垃圾回收机制,又要正确地处理本地代码中手动分配和释放内存(如全局引用、局部引用、直接内存操作等),很容易出现内存泄漏、悬空指针等问题,需要开发者对内存管理有深入的理解并且非常谨慎地编写代码。

3. 调试难度大

当出现问题时,调试包含 JNI 的应用程序会比较困难。因为涉及到 Java 代码和本地代码的交互,调试工具需要同时支持两种语言的调试,并且在追踪错误时,需要同时考虑 Java 代码的逻辑、本地代码的逻辑以及它们之间的数据传递和调用关系,不像纯 Java 应用那样可以方便地使用常见的 Java 调试工具(如 Eclipse、Intellij IDEA 中的调试功能)进行调试。

4. 安全性考量

由于 JNI 允许外部的本地代码与 Java 应用进行交互,这在一定程度上增加了安全风险。如果本地代码存在漏洞(比如缓冲区溢出等常见的 C/C++ 安全漏洞),可能会影响到整个 Java 应用的安全性,甚至被恶意利用来执行非法操作。所以在使用 JNI 时,需要对引入的本地代码进行严格的安全审查和测试。

总之,JNI 虽然为 Java 应用提供了强大的与本地代码交互的能力,但在使用过程中需要充分认识到其局限性和注意事项,谨慎地进行开发、测试和部署,以确保应用程序的性能、稳定性和安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2275348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hutool糊涂工具通过注解设置excel宽度

import java.lang.annotation.*;Documented Retention(RetentionPolicy.RUNTIME) Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) public interface ExcelStyle {int width() default 0; }/*** 聊天记录*/ Data public class DialogContentInfo {/**…

全面教程:Nacos 2.4.2 启用鉴权与 MySQL 数据存储配置

全面教程&#xff1a;Nacos 2.4.2 启用鉴权与 MySQL 数据存储配置 1. 配置 Nacos 开启鉴权功能 1.1 修改 application.properties 配置文件 在 Nacos 2.4.2 中&#xff0c;开启鉴权功能需要修改 conf/application.properties 文件。按照以下方式配置&#xff1a; # 开启鉴权…

【学习】CMMM智能制造能力成熟度评估的重要性

CMMM认证通过对企业当前生产状态的全面评估&#xff0c;能够精准地确定其智能化生产的程度&#xff0c;并将企业的智能化生产水平划分为五个等级&#xff0c;包括初始级、已定义级、以管理级、卓越级和顶级。这种等级划分使得不同类型的企业能够根据自身实际情况&#xff0c;选…

特制一个自己的UI库,只用CSS、图标、emoji图 第二版

图&#xff1a; 代码&#xff1a; index.html <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>M…

Y3编辑器地图教程:ORPG教程、防守图教程

文章目录 Part1&#xff1a;ORPG教程一、章节人物选择1.1 Logo与界面动画1.2 章节选择与投票1.2.1 设计章节选择完毕后的操作1.2.2 玩家投票统计 1.3 多样化的人物选择系统1.3.1 异步模型显示1.3.2 双击和键盘选人1.3.3 UI选人 1.4 简易存档 二、对话与任务系统2.1对话UI与触发…

Ubuntu问题 -- 硬盘存储不够了, 如何挂载一个新的硬盘上去, 图文简单明了, 已操作成功

需求 我现在有一个ubuntu22.04操作系统的服务器, 但是当前硬盘不够用了, 我买了一个1T的SSD固态硬盘, 且已经安装在服务器上了, 我需要将这个硬盘挂载到当前ubuntu的某个目录上 开始 1. 确认新硬盘是否被系统识别 打开终端&#xff0c;输入以下命令查看系统识别到的硬盘&…

吴恩达 提示词工程 课程笔记

一、Introduction 二、Guidelines Principle1: 清晰&#xff08;不一定是简短的&#xff09;而具体的指令 Tactic1: 使用分隔符 Triple quotes: “”" Triple backticks: Triple dashes: — Angle brackets:< > XML tags: < tag></ tag> Tactic2:…

网络安全设备主要有什么

网络安全设备指的肯定是硬件设备了&#xff0c;国内卖安全硬件的没几家&#xff0c;天融信&#xff0c;启明星辰&#xff0c;绿盟&#xff0c;深信服&#xff0c;就这四家卖的比较齐全吧&#xff0c;上它们官网看一下&#xff0c;就知道市面上主要的网络安全设备有哪些了。分类…

【C++补充】第一弹---位图技术揭秘:内存优化与快速访问

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1 位图 1.1 位图相关面试题 1.2 位图的设计及实现 1.3 C库中的位图 bitset 1.4 位图的模拟实现 1.5 位图的优缺点 1.6 位图相关考察题目 1 …

解决nginx多层代理后应用部署后访问发现css、js、图片等样式加载失败

一般是采用前后端分离部署方式&#xff0c;被上一层ng代理后&#xff0c;通过域名访问报错&#xff0c;例如&#xff1a;sqx.com.cn/应用代理路径。 修改nginx配置&#xff0c;配置前端页面的路径&#xff1a; location / {proxy_pass http://前端页面所在服务器的IP:PORT;pro…

第34天:安全开发-JavaEE应用反射机制攻击链类对象成员变量方法构造方法

时间轴&#xff1a; Java反射相关类图解&#xff1a; 反射&#xff1a; 1、什么是 Java 反射 参考&#xff1a; https://xz.aliyun.com/t/9117 Java 提供了一套反射 API &#xff0c;该 API 由 Class 类与 java.lang.reflect 类库组成。 该类库包含了 Field 、 Me…

Qt天气预报系统获取天气数据

Qt天气预报系统获取天气数据 1、获取天气数据1.1添加天气类头文件1.2定义今天和未来几天天气数据类1.3定义一个解析JSON数据的函数1.4在mainwindow中添加weatherData.h1.5创建今天天气数据和未来几天天气数据对象1.6添加parseJson定义1.7把解析JSON数据添加进去1.8添加错误1.9解…

SQL SERVER 2016 创建用户。

一、在实例中创建用户 二、在数据库中创建用户分配表格权限. 三、也可以在表格属性中分配用户权限 四、搜索对象中可以选择表、视图等等内容.

汽车信息安全 -- S32K1如何更新BOOT_MAC

目录 1.安全启动模式回顾 2.为什么要讨论BOOT_MAC 3.S32K1如何更新? 1.安全启动模式回顾 之前提到过,S32K1系列提供了Crypto Service Engine硬件加密模块(简称CSEc),大家可以通过该芯片系统寄存器SDID.FEATURES(System Device Identification Register)来判断自己的片子…

7 分布式定时任务调度框架

先简单介绍下分布式定时任务调度框架的使用场景和功能和架构&#xff0c;然后再介绍世面上常见的产品 我们在大型的复杂的系统下&#xff0c;会有大量的跑批&#xff0c;定时任务的功能&#xff0c;如果在独立的子项目中单独去处理这些任务&#xff0c;随着业务的复杂度的提高…

智慧城市应急指挥中心系统平台建设方案

建设背景与目标 智慧城市应急指挥中心系统平台的建设&#xff0c;源于对城市管理精细化、智能化的迫切需求。平台旨在通过整合各方资源&#xff0c;实现应急事件的快速响应与高效处置&#xff0c;提升城市安全管理水平。 前端设计与信息采集 前端设计注重立体化、全方位信息…

Playwright实战:Locators(定位器)指南

Locators Locators是Playwright自动等待和重试能力的核心部分。简而言之&#xff0c;Locators代表了一种随时在页面上查找元素的方法。 快速指南 这些是推荐的内置定位器。 page.getbyrole()通过显式和隐式可访问性属性进行定位。page.get_by_text()用于按文本内容定位。pa…

HTTP 核心概念

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

centos7.6 安装nginx 1.21.3与配置ssl

1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…

Homestyler 和 Tripo AI 如何利用人工智能驱动的 3D 建模改变定制室内设计

让设计梦想照进现实 在Homestyler,我们致力于为每一个梦想设计师提供灵感的源泉,而非挫折。无论是初学者打造第一套公寓,或是专业设计师展示作品集,我们的直观工具都能让您轻松以惊人的3D形式呈现空间。 挑战:实现定制设计的新纪元 我们知道,将个人物品如传家宝椅子、…