当前位置: 首页 > news >正文

Java 动态库开发和调试(JNI 和 FFM)

JNI动态库开发和调试

JNI

  1. Hello World
    使用vscode cmake生成 library 项目

  2. 写一个HelloWorld.java

    public class HelloWorld {static {System.loadLibrary("hello"); // 加载本地库}public static void main(String[] args){final var helloWorld = new HelloWorld();helloWorld.sayHello(); // 调用本地方法System.out.println(helloWorld.getString()); // 调用本地方法并打印结果}// 声明本地方法public native void sayHello();public native String getString();
    }
    
  3. 执行javac -h . HelloWorld.java生成HelloWorld.h,简单改造一下为HelloWorld.hpp

    /* DO NOT EDIT THIS FILE - it is machine generated */
    /* Header for class HelloWorld */#ifndef _Included_HelloWorld
    #define _Included_HelloWorld#include <jni.h>
    #include <iostream>/** Class:     HelloWorld* Method:    sayHello* Signature: ()V*/
    extern "C" 
    JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv*, jobject);/** Class:     HelloWorld* Method:    getString* Signature: ()Ljava/lang/String;*/
    extern "C"
    JNIEXPORT jstring JNICALL Java_HelloWorld_getString(JNIEnv*, jobject);#endif // _Included_HelloWorld
  4. 实现头文件HelloWorld.cpp

    #include "HelloWorld.hpp"extern "C"
    JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv* env, jobject obj) {(void)env;(void)obj;std::cout << "Hello, World from JNI!" << std::endl;
    }extern "C"
    JNIEXPORT jstring JNICALL Java_HelloWorld_getString(JNIEnv* env, jobject obj) {(void)obj;return env->NewStringUTF("Hello from JNI!");
    }
    
  5. cmake配置

    cmake_minimum_required(VERSION 3.22.1)
    project(hello VERSION 0.1.0 LANGUAGES C CXX)# ✅ 设置 C++ 标准
    set(CMAKE_CXX_STANDARD 26)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 强制使用指定标准
    set(CMAKE_CXX_EXTENSIONS OFF)        # 禁用编译器扩展(使用纯标准)# 查找源文件
    file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS"src/*.cpp""src/*.c"
    )
    message("SOURCES: ${SOURCES}")
    message("CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")# 设置头文件目录
    # 设置头文件包含目录
    message(CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME})
    message(PROJECT_NAME: ${PROJECT_NAME})
    message(CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR})add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp ${SOURCES})if(WIN32)
    # 让 JNI 在 Windows/MinGW 下生成 hello.dll,避免默认的 libhello.dll
    set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIESPREFIX ""OUTPUT_NAME ${CMAKE_PROJECT_NAME}
    )
    endif()# 设置头文件包含路径
    target_include_directories(${CMAKE_PROJECT_NAME}PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/jniPUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/bsdiffPUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/brotliPUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/HelloWorld
    )if(MSVC)target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE /W4 /WX)
    else()message("no MSVC")target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall-Werror # 警告转错误-Wextra # 发现更多潜在的逻辑和风格问题-pedantic # 追求极致的标准兼容性-D NDEBUG # 定义 NDEBUG 以禁用 assert() 的评估)
    endif()include(CTest)
    enable_testing()set(CPACK_PROJECT_NAME ${PROJECT_NAME})
    set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
    include(CPack)
  6. build生成动态库

  7. 执行命令

    java "--enable-native-access=ALL-UNNAMED" "-Djava.library.path=.\build" HelloWorld.java
    
  8. 调试.vscode/launch.json

    {// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "(gdb) Attach","type": "cppdbg","request": "attach","program": "D:/SoftWare/jdk25/bin/java.exe","processId": "${command:pickProcess}","MIMode": "gdb","miDebuggerPath": "D:/SoftWare/msys64/ucrt64/bin/gdb.exe","setupCommands": [{"description": "Enable pretty-printing for gdb","text": "-enable-pretty-printing","ignoreFailures": true},{"description": "Set Disassembly Flavor to Intel","text": "-gdb-set disassembly-flavor intel","ignoreFailures": true}]}]
    }
    
  9. 修改HelloWorld.java

    public class HelloWorld {static {System.loadLibrary("hello"); // 加载本地库}public static void main(String[] args) throws Exception{System.out.println("PID: " + ProcessHandle.current().pid()); // 打出 PID 方便选进程System.in.read(); // 暂停,等你附加 GDB 后按回车继续final var helloWorld = new HelloWorld();helloWorld.sayHello(); // 调用本地方法System.out.println(helloWorld.getString()); // 调用本地方法并打印结果}// 声明本地方法public native void sayHello();public native String getString();
    }
    
  10. 调试

    java "--enable-native-access=ALL-UNNAMED" "-Djava.library.path=.\build" HelloWorld.java
    # vscode 运行 debug (gdb) Attach
    # 去C++打点然后回车
    

    简单项目的话可以使用下面的命令。

    g++ -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \BrotliNative.c -o libbrotli_jni.so
    

FFM API

全称: Foreign Function & Memory API.
ffm可以直接调用c/c++函数不需要再使用jni包一层了。

新建vscode library项目

main.cpp

#include <iostream>extern "C" __declspec(dllexport) // windows 独有,其他不需要 __declspec(dllexport) 
void sayHello(){std::cout << "Hello, from hello_ffm!\n";
}extern "C" __declspec(dllexport)
const char* getString(){return "Hello, from getString!";
}

HelloWorld.java

import java.lang.foreign.*;
import java.lang.invoke.*;
import java.util.Optional;public class HelloWorld {static {System.loadLibrary("hello_ffm");}@SuppressWarnings("UnnecessaryModifier")public static void main(@SuppressWarnings("unused") final String[] args) throws Throwable {final Linker linker = Linker.nativeLinker();final SymbolLookup lookup = SymbolLookup.loaderLookup();// 调用 void sayHello()final MethodHandle sayHello = linker.downcallHandle(lookup.find("sayHello").orElseThrow(),FunctionDescriptor.ofVoid());sayHello.invoke();// 调用 const char* getString() → 返回指针final MethodHandle getString = linker.downcallHandle(lookup.find("getString").orElseThrow(),FunctionDescriptor.of(ValueLayout.ADDRESS));// invoke 和 invokeExact(调用点类型静态类型)区别     Optional.ofNullable(getString.invoke()).filter(MemorySegment.class::isInstance).map(MemorySegment.class::cast).ifPresent((MemorySegment result) -> {// 从指针读取字符串final String str = result.reinterpret(Long.MAX_VALUE).getString(0);System.out.println("str: " + str);});// final MemorySegment result = (MemorySegment) getString.invokeExact();// // 从指针读取字符串// final String str = result.reinterpret(Long.MAX_VALUE).getString(0);// System.out.println("str: " + str);}
}//  java "--enable-native-access=ALL-UNNAMED" "-Djava.library.path=.\build" HelloWorld.java

调试

HelloWorld.java

import java.lang.foreign.*;
import java.lang.invoke.*;
import java.util.Optional;public class HelloWorld {static {System.loadLibrary("hello_ffm");}@SuppressWarnings("UnnecessaryModifier")public static void main(@SuppressWarnings("unused") final String[] args) throws Throwable {System.out.println("PID: " + ProcessHandle.current().pid()); // 打出 PID 方便选进程System.in.read(); // 暂停,等你附加 GDB 后按回车继续final Linker linker = Linker.nativeLinker();final SymbolLookup lookup = SymbolLookup.loaderLookup();// 调用 void sayHello()final MethodHandle sayHello = linker.downcallHandle(lookup.find("sayHello").orElseThrow(),FunctionDescriptor.ofVoid());sayHello.invoke();// 调用 const char* getString() → 返回C/C++ 常量区字符串 指针final MethodHandle getString = linker.downcallHandle(lookup.find("getString").orElseThrow(),FunctionDescriptor.of(ValueLayout.ADDRESS));Optional.ofNullable(getString.invoke()).filter(MemorySegment.class::isInstance).map(MemorySegment.class::cast).ifPresent((MemorySegment result) -> {// 从指针读取字符串final String str = result.reinterpret(Long.MAX_VALUE).getString(0);System.out.println("str: " + str);});// final MemorySegment result = (MemorySegment) getString.invokeExact();// // 从指针读取字符串// final String str = result.reinterpret(Long.MAX_VALUE).getString(0);// System.out.println("str: " + str);}
}//  java "--enable-native-access=ALL-UNNAMED" "-Djava.library.path=.\build" HelloWorld.java

.vscode/launch.json

{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "(gdb) Attach","type": "cppdbg","request": "attach","program": "D:/SoftWare/jdk25/bin/java.exe","processId": "${command:pickProcess}","MIMode": "gdb","miDebuggerPath": "D:/SoftWare/msys64/ucrt64/bin/gdb.exe","setupCommands": [{"description": "Enable pretty-printing for gdb","text": "-enable-pretty-printing","ignoreFailures": true},{"description": "Set Disassembly Flavor to Intel","text": "-gdb-set disassembly-flavor intel","ignoreFailures": true}]}]
}

Kotlin 也可以使用

internal suspend fun main(args: Array<String>): Unit = coroutineScope {val linker: Linker? = Linker.nativeLinker()val stdlib: SymbolLookup? = linker?.defaultLookup()val methodAddress: MemorySegment? = stdlib?.find("strlen")?.orElseThrow()val strlenFuncDesc: FunctionDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)val methodHande: MethodHandle? = linker?.downcallHandle(methodAddress, strlenFuncDesc)// 堆外,只能由创建者线程访问,手动管理Arena.ofConfined().use { arena: Arena ->val cString: MemorySegment = arena.allocateFrom("Hello World!")val length: Long? = methodHande?.invoke(cString) as? Longlogger.info { "length: $length" }}delay(100)
}

FFM 调用 bsdiff

package io.githubimport io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import java.lang.foreign.Arena
import java.lang.foreign.FunctionDescriptor
import java.lang.foreign.Linker
import java.lang.foreign.SymbolLookup
import java.lang.foreign.ValueLayout
import java.lang.invoke.MethodHandle
import java.nio.file.Pathprivate val logger: KLogger = KotlinLogging.logger {}internal object CxxUtils {private val CREATE_PATCH: MethodHandleprivate val APPLY_PATCH: MethodHandleinit {// 1. 先用传统的系统方法加载,它会识别 -Djava.library.pathSystem.loadLibrary("bsdiff")// 2. 使用 loaderLookup,它能找到上面加载的库里的函数val symbolLookup: SymbolLookup = SymbolLookup.loaderLookup()//                                                  // 对应Java返回值类型    // 参数列表类型           val desc: FunctionDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)val linker: Linker = Linker.nativeLinker()CREATE_PATCH = linker.downcallHandle(symbolLookup.find("bsdiff_create_patch")?.orElseThrow(), desc)APPLY_PATCH = linker.downcallHandle(symbolLookup.find("bsdiff_apply_patch")?.orElseThrow(), desc)}internal fun bsDiffCreatePatch(oldPath: String, newPath: String, patchPath: String){Arena.ofConfined().use { arena: Arena ->val result: Int = CREATE_PATCH.invoke(arena.allocateFrom(oldPath),arena.allocateFrom(newPath),arena.allocateFrom(patchPath),) as Intlogger.info { "bsDiffCreatePatch result: $result" }if (result != 0){throw RuntimeException("bsdiff_create_patch error")}}}internal fun bsDiffApplyPatch(oldPath: String, newPath: String, patchPath: String){Arena.ofConfined().use { arena: Arena ->val result: Int = APPLY_PATCH.invoke(arena.allocateFrom(oldPath),arena.allocateFrom(newPath),arena.allocateFrom(patchPath),) as Intlogger.info { "bsDiffApplyPatch result: $result" }if (result != 0){throw RuntimeException("bsdiff_create_patch error")}}}
}

使用官方工具

jextract --output src/main/java \-t io.github.bsdiff \ # 包名-l bsdiff \                # library name HelloWorld.h

C++ 代码请参考 myfaverate-bsdiff

http://www.jsqmd.com/news/699125/

相关文章:

  • Wan2.2-I2V-A14B部署教程:LDAP统一认证对接企业SSO系统
  • 广州市黄埔区鑫邦租赁:广州二手空压机回收服务商 - LYL仔仔
  • 不容易晒黑的防晒霜推荐,Leeyo防晒霜硬核抗晒远离暗沉变黑 - 全网最美
  • 温岭市大溪致翔机械设备租赁:靠谱的台州吊车租赁公司 - LYL仔仔
  • 从订单履约到会员增长:游戏电竞护航陪玩源码系统小程序全开源 v4.0 解决方案 - 壹软科技
  • 3大场景解析:如何用Path of Building彻底改变你的流放之路Build规划思维?
  • 3步搞定B站视频下载难题:BilibiliDown高效下载实战指南
  • 信息套利窗口倒计时:深圳本地GEO优化公司推荐与AI搜索卡位指南 - 品牌评测官
  • 太原龙盛腾达商贸:专业的太原格力空调出售公司 - LYL仔仔
  • 从写实到二次元:用Stable Diffusion打造你的专属AI画师,附保姆级模型搭配方案
  • 2026年深圳粤港两地牌租车公司推荐:中港跨境租车/深港跨境租车服务商精选 - 品牌推荐官
  • 潍坊悍龙机械设备:口碑好的杭州液压钻床出售公司 - LYL仔仔
  • 别再只会用PBR了!手把手教你用Matcap贴图快速制作风格化角色材质(附资源包)
  • 2026年3月石灰岩制造厂家哪个好,目前石灰岩精选国内优质品牌分析 - 品牌推荐师
  • 3步解锁CrossOver游戏兼容性:Mac游戏优化完整方案
  • 重庆雅田实业(集团):高新区古法自建房电话多少 - LYL仔仔
  • TMSpeech:Windows本地实时语音转文字工具,彻底告别云端隐私泄露
  • 安信可ESP32-CAM到手即用:5分钟快速验证硬件与基础功能(附常见启动失败排查)
  • 敏肌用什么防晒温和修护皮肤?Leeyo防晒霜修护维稳防晒养肤双在线 - 全网最美
  • TV Bro浏览器终极指南:在智能电视上享受完整上网体验的简单教程
  • 青岛佳讯通网络工程:青岛智慧工地安装哪家经验足 - LYL仔仔
  • 避开这些坑!用STM32CubeMX快速复现蓝桥杯G431电压监测赛题
  • 2026届最火的五大AI论文平台实测分析
  • 从Double到BigDecimal:一次支付金额计算Bug引发的Java精度问题排查实录
  • Python 协程池限速机制实现
  • 2026年最新评测:宁波鄞州区口碑排名前五装修设计公司榜单揭秘 - 疯一样的风
  • 北京弘语航:北京吊车出租服务贴心公司 - LYL仔仔
  • QQ空间历史说说完整备份指南:GetQzonehistory让你一键保存青春记忆
  • 安徽诚鑫物资回收:合肥电线回收排名 - LYL仔仔
  • 如何高效使用MarkDownload:5个提升网页内容管理效率的实用技巧