前言
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_setoptif (!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/?.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 100
end
test()
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/
本文为原创,转载请申明