前言

JDK22即将发布,Java Foreign Function & Memory API将会退出预览,是时候开始学习一波了。

FFM API介绍

FFM API由两大部分组成,一个是Foreign Function Interface,另一个是Memory API。前者是外部函数接口,简称FFI,用它来实现Java代码和外部代码之间相互操作;后者是内存接口,用于安全地管理堆外内存。

上面说这么多,可以简单的认为是jni的替代品。

本文不关注底层如何实现,一两个常用的c库为例子,重点在了解api的使用以及使用jextract生成java代码

入门

使用c语言的strlen和printf格式化打印helloworld

/** * @author authorZhao * @since 2024-03-13 */public class FFMHelloWorld {public static void main(String[] args) {Linker linker = Linker.nativeLinker();SymbolLookup defaultLookup = linker.defaultLookup();MethodHandle strlenHandle = linker.downcallHandle(defaultLookup.find("strlen").orElseThrow(),FunctionDescriptor.of(JAVA_LONG, ADDRESS));MethodHandle printfHandler = linker.downcallHandle(defaultLookup.find("printf").orElseThrow(),FunctionDescriptor.of(JAVA_LONG, ADDRESS,JAVA_INT));try (Arena offHeap = Arena.ofConfined()) {MemorySegment pointers = offHeap.allocateUtf8String("Hello world%d!");System.out.println(strlenHandle.invoke(pointers));//11printfHandler.invoke(pointers,8);} catch (Throwable e) {throw new RuntimeException(e);}}}

实战级别的HelloWorld

工具准备

下载curl

下载lua

  • 注意需要下载源码最好,有条件的自行编译,编译为动态库

  • 效果如图,本文只需要dll即可没动态库名字我自己改的

  • 头文件目录



这里的cjson已经被移动到同级目录的dll目录下面

下载jextract 本文选择jdk22的版本

打开jextract的github链接

一、CURL

打开curl的例子 https://github.com/openjdk/jextract/tree/master/samples/libcurl

windows脚本如下

param([Parameter(Mandatory=$true, HelpMessage="The path to the lib curl installation")][string]$curlpath)jextract `-I "$curlpath\include" `-I "$curlpath\include\curl" `--dump-includes 'includes_all.conf' `"$curlpath\include\curl\curl.h"Select-String -Path 'includes_all.conf' -Pattern '(curl|sockaddr )' | %{ $_.Line } | Out-File -FilePath 'includes_filtered.conf' -Encoding asciijextract `--output src `-t org.jextract `-I "$curlpath\include" `-I "$curlpath\include\curl" `-llibcurl `'@includes_filtered.conf' `"$curlpath\include\curl\curl.h"javac -d classes (ls -r src/*.java)

本人以自己的curl为例

生成脚本

本人的curlpath = E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw我按照github执行了两次命令1. 这个执行完毕得到一个includes_filtered.conf文件jextract --output src -t org.jextract -I "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include" -I "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include/curl" -llibcurl '@includes_filtered.conf' "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include/curl/curl.h"2.这个执行完毕直接得到生成的java代jextract --output src -t org.jextract -I "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include" -I "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include/curl" "E:/tool/curl-8.6.0_6-win64-mingw/curl-8.6.0_6-win64-mingw/include/curl/curl.h"

拷贝生成java代码(好家伙52M)到idea

生成的工具有一个方法和我本地jdk有点不一致(可能是版本原因,本地jdk21,工具依赖的jdk是22), ctrl+shift+r 全局替换一下getUtf8String

arena.allocateFrom(urlStr); 替换为arena.getUtf8String(urlStr);

启动

import java.lang.foreign.Arena;import static java.lang.foreign.MemorySegment.NULL;import static org.jextract.curl_h.*;public class CurlMain {static {System.load("E:\\tool\\curl-8.6.0_6-win64-mingw\\curl-8.6.0_6-win64-mingw\\bin\\libcurl-x64.dll");} public static void main(String[] args) { var urlStr = "https://www.baidu.com"; curl_global_init(CURL_GLOBAL_DEFAULT()); var curl = curl_easy_init(); //curl_easy_setopt if (!curl.equals(NULL)) { try (var arena = Arena.ofConfined()) { var url = arena.allocateUtf8String(urlStr); //忽略ssl忽略证书检查 curl_easy_setopt.makeInvoker(C_LONG_LONG).apply(curl, CURLOPT_SSL_VERIFYPEER(), 0); curl_easy_setopt.makeInvoker(C_LONG_LONG).apply(curl, CURLOPT_URL(), url.address()); int res = curl_easy_perform(curl); if (res != CURLE_OK()) { String error = curl_easy_strerror(res).getUtf8String(0); System.out.println("Curl error: " + error); curl_easy_cleanup(curl); } } } curl_global_cleanup(); }}

curl的java执行结果

二、LUA

lua源代码

print("package.cpath=" .. package.cpath)-- 注意cjson不在lua.exe同级目录,再下一级别package.cpath = package.cpath .. ';E:/tool/lua5.4-zhao/dll/" />local cjson = require("cjson")function test()local map = {}map["a"] = "张三"map["b"] = "李四"for k,v in pairs(map) doprint(k .. '=' .. v)endprint(cjson.encode(map))return 100endtest()

lua源代码的直接执行结果

生成命令

生成代码并,拷贝代码到idea

jextract --output src -t com.lua -I "E:/tool/lua-5.4.4/src" "E:/tool/lua-5.4.4/src/lua.h" -l :E:/tool/lua5.4-zhao/liblua5.4.0.dlljextract --output src -t com.lua -I "E:/tool/lua-5.4.4/src" "E:/tool/lua-5.4.4/src/lualib.h" -l :E:/tool/lua5.4-zhao/liblua5.4.0.dlljextract --output src -t com.lua -I "E:/tool/lua-5.4.4/src" "E:/tool/lua-5.4.4/src/lauxlib.h" -l :E:/tool/lua5.4-zhao/liblua5.4.0.dll

idea导入生成的java代码

/** * @author authorZhao */public class LuaMain {static {//这里不适用load,生成代码使用-l指定动态库的位置,生成的代码直接包含了,但是这种方式用的是绝对路径,使用SymbolLookup.libraryLookup//System.load("E:/tool/lua5.4-zhao/liblua5.4.0.dll");}public static void main(String[] args) {var lua_State = luaL_newstate();if (lua_State == NULL) {return;}//打开内置库luaL_openlibs(lua_State);try (var arena = Arena.ofConfined()) {var code = arena.allocateUtf8String("print(\"渣渣辉\") return \"嘿嘿\"");//加载而不执行int i = luaL_loadstring(lua_State, code);if (i != 0) {return;}// pcalllua_h.lua_pcallk(lua_State, 0, 1, 0, 0L, NULL);var data2 = lua_h.lua_tolstring(lua_State, -1,NULL);System.out.println("data2 = " + data2.getUtf8String(0L));//这里的utf-8可能对中文路径有问题,英文影响应该不大int loadResult = luaL_loadfilex(lua_State, arena.allocateUtf8String("E:/tool/lua5.4-zhao/test2.lua"), NULL);System.out.println("loadResult = " + loadResult);// pcallint iRet = lua_h.lua_pcallk(lua_State, 0, 1, 0, 0L, NULL);if (iRet==0){var data = lua_h.lua_tointegerx(lua_State, -1,NULL);System.out.println("data = " + data);}}//关闭lua虚拟机lua_h.lua_close(lua_State);}}

执行结果

本文运行环境

本文系统win10 运行jdk21,jextract工具是jdk22的版本,lua5.4

使用总结

  • curl本身就3M生成的代码文件有55M,编译的class有2万多个(可能是姿势不对,跟多类都没用到)
  • 体验还行,但是如果不了解这个c库,可能很多api都不会用,例如图上的ssl证书,不处理https都报错,lua的capi
  • 该工具目前无法识别c++的语法,以qt为例目前使用失败(有可能是使用姿势不对),理论上是要支持的
  • 以lua为例,一些宏无法识别
  • lua里面使用别的dll或者so,搜索路径是jdk下面开头的,这个需要注意
  • System.load是以前的jni加载动态库的方式,在这里照样可以这么用,网上查了一下SymbolLookup.libraryLookup会忽略jni_onload等函数
  • 生成代码的时候如果使用绝对路径指定动态库的位置,可能会造成跨平台问题,这里需要注意

参考

jextract的github地址 https://github.com/openjdk/jextract

lua5.4文档 https://www.lua.org/manual/5.4/

lua5.3中文文档(这里面对capi翻译的很详细) https://www.runoob.com/manual/lua53doc/manual.html#lua_tolstring

openjdk的jextract下载地址 https://jdk.java.net/jextract/

本文为原创,转载请申明