JAVA22 FFM实战之HelloWorld 本文以curl和lua为例,提前研究jdk22的ffm api使用方式

前言

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/

本文为原创,转载请申明

作者:平原人原文地址:https://blog.csdn.net/weixin_43328357/article/details/136745309

%s 个评论

要回复文章请先登录注册