前言
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)); //11
printfHandler.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 ascii
jextract `
--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/?.dll'
local cjson = require("cjson")
function test()
local map = {}
map["a"] = "张三"
map["b"] = "李四"
for k,v in pairs(map) do
print(k .. '=' .. v)
end
print(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.dll
jextract --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.dll
jextract --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;
}
// pcall
lua_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);
// pcall
int 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/
本文为原创,转载请申明