Skip to content

FFI相关

javascript
const core = require('cheese-js');
const ffi = core.ffi;
lua
local core = require('cheese-lua')
local ffi = core.ffi

createCDef()

说明:

  • 创建函数声明构建器,用于描述 native 方法签名。

链式方法:

  • add(name, returnType, ...args):添加一个 native 方法定义
  • build():生成最终 specs

类型来源:

  • ffi.NativeTypes.INT()
  • ffi.NativeTypes.POINTER()
  • ffi.NativeTypes.VOID()
  • ffi.NativeTypes.FLOAT()
  • ffi.NativeTypes.DOUBLE()
  • ffi.NativeTypes.CHAR()
  • ffi.NativeTypes.SHORT()
  • ffi.NativeTypes.LONG()
  • ffi.NativeTypes.BOOLEAN()
  • ffi.NativeTypes.BYTE()
  • ffi.NativeTypes.STRING()

createStruct()

说明:

  • 创建结构体布局构建器。

链式方法:

  • add(name, fieldType, count?):添加字段,count 可用于定长数组
  • build():生成结构体布局

字段类型来源:

  • ffi.NativeStructs.FieldType.INT8
  • ffi.NativeStructs.FieldType.INT32
  • ffi.NativeStructs.FieldType.INT64
  • ffi.NativeStructs.FieldType.FLOAT32
  • ffi.NativeStructs.FieldType.FLOAT64
  • ffi.NativeStructs.FieldType.POINTER

cString(str)

参数:

  • string (str): 要转为 C 字符串的文本

返回值:

  • 🟢 C 字符串对象,可通过 pointer() 传入结构体指针字段
  • 🔴 null

buffer(layout)

参数:

  • layout: 由 createStruct().build() 生成的结构体布局

返回值:

  • 🟢 Buffer 对象,支持链式读写字段
  • 🔴 null

buffer.pointer()

返回值:

  • 🟢 当前缓冲区的 native 指针

buffer.setInt/getInt

说明:

  • 写入/读取 INT32 字段。

buffer.setLong/getLong

说明:

  • 写入/读取 INT64 字段。

buffer.setFloat/getFloat

说明:

  • 写入/读取 FLOAT32 字段。

buffer.setDouble/getDouble

说明:

  • 写入/读取 FLOAT64 字段。

buffer.setPointer/getPointer

说明:

  • 写入/读取指针字段(例如字符串指针、子结构体指针)。

buffer.setFixedCString/getFixedCString

说明:

  • 写入/读取定长 char[] 字段。

createFromAssets(name, specs)

参数:

  • string (name): assets 中动态库名,例如 libmyapplication.so
  • specs: createCDef().build() 生成的签名定义

返回值:

  • 🟢 Native API 调用对象

createFromLibraryName(name, specs)

参数:

  • string (name): 系统库名,例如 c
  • specs: createCDef().build() 生成的签名定义

返回值:

  • 🟢 Native API 调用对象

createFromAbsolutePath(path, specs)

参数:

  • string (path): 动态库绝对路径
  • specs: createCDef().build() 生成的签名定义

返回值:

  • 🟢 Native API 调用对象

api.callInt/callLong/callString/callBoolean/callByte/callShort/callFloat/callDouble/callPointer/callVoid

参数:

  • string (methodName): 已定义的 native 函数名
  • ...args: 按签名传入参数

返回值:

  • 根据调用方法返回对应类型,callVoid 无返回值

示例一:嵌套结构体调用

javascript
const core = require('cheese-js'); // 导入 cheese-js 核心模块
const { ffi } = core; // 从核心模块中解构出 ffi 对象

const cdef = ffi.createCDef() // 创建 C 函数签名构建器
    .add("process_complex", ffi.NativeTypes.INT(), ffi.NativeTypes.POINTER()); // 声明 process_complex(ComplexUser*) -> int

const api = ffi.createFromAssets("libmyapplication.so", cdef.build()); // 从 assets 加载 libmyapplication.so 并创建调用实例

const childLayout = ffi.createStruct() // 创建子结构体布局构建器
    .add("score", ffi.NativeStructs.FieldType.FLOAT64) // 添加 score 字段,类型为 double
    .add("level", ffi.NativeStructs.FieldType.INT32) // 添加 level 字段,类型为 int32
    .build(); // 生成子结构体布局

const userLayout = ffi.createStruct() // 创建主结构体布局构建器
    .add("id", ffi.NativeStructs.FieldType.INT32) // 添加 id 字段,类型为 int32
    .add("name", ffi.NativeStructs.FieldType.POINTER) // 添加 name 字段,类型为指针
    .add("child", ffi.NativeStructs.FieldType.POINTER) // 添加 child 字段,类型为指针
    .add("weight", ffi.NativeStructs.FieldType.FLOAT64) // 添加 weight 字段,类型为 double
    .add("name_len", ffi.NativeStructs.FieldType.INT32) // 添加 name_len 字段,类型为 int32
    .add("total", ffi.NativeStructs.FieldType.FLOAT64) // 添加 total 字段,类型为 double
    .build(); // 生成主结构体布局

const child = ffi.buffer(childLayout) // 按子结构体布局创建内存缓冲区
    .setDouble("score", 88.75) // 设置 score = 88.75
    .setInt("level", 6); // 设置 level = 6

const name = ffi.cString("alice-复杂结构"); // 创建 C 字符串并获得其 native 内存对象

const user = ffi.buffer(userLayout) // 按主结构体布局创建内存缓冲区
    .setInt("id", 101) // 设置 id = 101
    .setPointer("name", name.pointer()) // 设置 name 指针指向 C 字符串
    .setPointer("child", child.pointer()) // 设置 child 指针指向子结构体
    .setDouble("weight", 12.5); // 设置 weight = 12.5

const result = api.callInt("process_complex", user.pointer()); // 传入主结构体指针并调用 native 方法
console.log("nested struct result:", result); // 输出函数返回值
console.log("nested struct name_len:", user.getInt("name_len")); // 输出 native 回填的 name_len
console.log("nested struct total:", user.getDouble("total")); // 输出 native 回填的 total
lua
local core = require('cheese-lua') -- 导入 cheese-lua 核心模块
local ffi = core.ffi -- 从核心模块获取 ffi 对象

local cdef = ffi.createCDef() -- 创建 C 函数签名构建器
    :add("process_complex", ffi.NativeTypes.INT(), ffi.NativeTypes.POINTER()) -- 声明 process_complex(ComplexUser*) -> int

local api = ffi.createFromAssets("libmyapplication.so", cdef:build()) -- 从 assets 加载 libmyapplication.so 并创建调用实例

local childLayout = ffi.createStruct() -- 创建子结构体布局构建器
    :add("score", ffi.NativeStructs.FieldType.FLOAT64) -- 添加 score 字段,类型为 double
    :add("level", ffi.NativeStructs.FieldType.INT32) -- 添加 level 字段,类型为 int32
    :build() -- 生成子结构体布局

local userLayout = ffi.createStruct() -- 创建主结构体布局构建器
    :add("id", ffi.NativeStructs.FieldType.INT32) -- 添加 id 字段,类型为 int32
    :add("name", ffi.NativeStructs.FieldType.POINTER) -- 添加 name 字段,类型为指针
    :add("child", ffi.NativeStructs.FieldType.POINTER) -- 添加 child 字段,类型为指针
    :add("weight", ffi.NativeStructs.FieldType.FLOAT64) -- 添加 weight 字段,类型为 double
    :add("name_len", ffi.NativeStructs.FieldType.INT32) -- 添加 name_len 字段,类型为 int32
    :add("total", ffi.NativeStructs.FieldType.FLOAT64) -- 添加 total 字段,类型为 double
    :build() -- 生成主结构体布局

local child = ffi.buffer(childLayout) -- 按子结构体布局创建内存缓冲区
    :setDouble("score", 88.75) -- 设置 score = 88.75
    :setInt("level", 6) -- 设置 level = 6

local name = ffi.cString("alice-复杂结构") -- 创建 C 字符串并获得其 native 内存对象

local user = ffi.buffer(userLayout) -- 按主结构体布局创建内存缓冲区
    :setInt("id", 101) -- 设置 id = 101
    :setPointer("name", name:pointer()) -- 设置 name 指针指向 C 字符串
    :setPointer("child", child:pointer()) -- 设置 child 指针指向子结构体
    :setDouble("weight", 12.5) -- 设置 weight = 12.5

local result = api:callInt("process_complex", user:pointer()) -- 传入主结构体指针并调用 native 方法
print("nested struct result:", result) -- 输出函数返回值
print("nested struct name_len:", user:getInt("name_len")) -- 输出 native 回填的 name_len
print("nested struct total:", user:getDouble("total")) -- 输出 native 回填的 total

示例二:定长字符数组结构体

javascript
const core = require('cheese-js'); // 导入 cheese-js 核心模块
const { ffi } = core; // 从核心模块中解构出 ffi 对象

const cdef = ffi.createCDef() // 创建 C 函数签名构建器
    .add("process_fixed_char", ffi.NativeTypes.INT(), ffi.NativeTypes.POINTER()); // 声明 process_fixed_char(FixedCharData*) -> int

const api = ffi.createFromAssets("libmyapplication.so", cdef.build()); // 从 assets 加载 libmyapplication.so 并创建调用实例

const layout = ffi.createStruct() // 创建结构体布局构建器
    .add("tag", ffi.NativeStructs.FieldType.INT8, 16) // 添加 tag 字段,类型为长度16的 char 数组
    .add("value", ffi.NativeStructs.FieldType.INT32) // 添加 value 字段,类型为 int32
    .add("tag_len", ffi.NativeStructs.FieldType.INT32) // 添加 tag_len 字段,类型为 int32
    .build(); // 生成结构体布局

const buffer = ffi.buffer(layout) // 按结构体布局创建内存缓冲区
    .setFixedCString("tag", "char-array-demo") // 写入定长字符串 tag
    .setInt("value", 20); // 设置 value = 20

const result = api.callInt("process_fixed_char", buffer.pointer()); // 传入结构体指针并调用 native 方法
console.log("fixed char struct result:", result); // 输出函数返回值
console.log("fixed char struct tag:", buffer.getFixedCString("tag")); // 输出结构体中的 tag
console.log("fixed char struct tag_len:", buffer.getInt("tag_len")); // 输出 native 回填的 tag_len
console.log("fixed char struct value:", buffer.getInt("value")); // 输出 native 更新后的 value
lua
local core = require('cheese-lua') -- 导入 cheese-lua 核心模块
local ffi = core.ffi -- 从核心模块获取 ffi 对象

local cdef = ffi.createCDef() -- 创建 C 函数签名构建器
    :add("process_fixed_char", ffi.NativeTypes.INT(), ffi.NativeTypes.POINTER()) -- 声明 process_fixed_char(FixedCharData*) -> int

local api = ffi.createFromAssets("libmyapplication.so", cdef:build()) -- 从 assets 加载 libmyapplication.so 并创建调用实例

local layout = ffi.createStruct() -- 创建结构体布局构建器
    :add("tag", ffi.NativeStructs.FieldType.INT8, 16) -- 添加 tag 字段,类型为长度16的 char 数组
    :add("value", ffi.NativeStructs.FieldType.INT32) -- 添加 value 字段,类型为 int32
    :add("tag_len", ffi.NativeStructs.FieldType.INT32) -- 添加 tag_len 字段,类型为 int32
    :build() -- 生成结构体布局

local buffer = ffi.buffer(layout) -- 按结构体布局创建内存缓冲区
    :setFixedCString("tag", "char-array-demo") -- 写入定长字符串 tag
    :setInt("value", 20) -- 设置 value = 20

local result = api:callInt("process_fixed_char", buffer:pointer()) -- 传入结构体指针并调用 native 方法
print("fixed char struct result:", result) -- 输出函数返回值
print("fixed char struct tag:", buffer:getFixedCString("tag")) -- 输出结构体中的 tag
print("fixed char struct tag_len:", buffer:getInt("tag_len")) -- 输出 native 回填的 tag_len
print("fixed char struct value:", buffer:getInt("value")) -- 输出 native 更新后的 value

示例三:调用系统库函数

javascript
const core = require('cheese-js'); // 导入 cheese-js 核心模块
const { ffi } = core; // 从核心模块中解构出 ffi 对象
importClass(java.lang.Integer); // 导入 Java Integer 类型用于参数封装

const specs = ffi.createCDef() // 创建 C 函数签名构建器
    .add("getpid", ffi.NativeTypes.INT()) // 声明 getpid() -> int
    .add("abs", ffi.NativeTypes.INT(), ffi.NativeTypes.INT()) // 声明 abs(int) -> int
    .build(); // 生成最终签名定义

const inst = ffi.createFromLibraryName("c", specs); // 从系统 c 库创建调用实例

console.log("当前进程ID:", inst.callInt("getpid")); // 调用 getpid 并输出当前进程ID
console.log("abs(-123) =", inst.callInt("abs", Integer(-123))); // 调用 abs 并输出结果
lua
local core = require('cheese-lua') -- 导入 cheese-lua 核心模块
local ffi = core.ffi -- 从核心模块获取 ffi 对象
local Integer = java.import('java.lang.Integer') -- 导入 Java Integer 类型用于参数封装

local specs = ffi.createCDef() -- 创建 C 函数签名构建器
    :add("getpid", ffi.NativeTypes.INT()) -- 声明 getpid() -> int
    :add("abs", ffi.NativeTypes.INT(), ffi.NativeTypes.INT()) -- 声明 abs(int) -> int
    :build() -- 生成最终签名定义

local inst = ffi.createFromLibraryName("c", specs) -- 从系统 c 库创建调用实例

print("当前进程ID:", inst:callInt("getpid")) -- 调用 getpid 并输出当前进程ID
print("abs(-123) =", inst:callInt("abs", Integer(-123))) -- 调用 abs 并输出结果

示例四:调用自定义的普通方法

javascript
const core = require('cheese-js'); // 导入 cheese-js 核心模块
const { ffi } = core; // 从核心模块中解构出 ffi 对象
importClass(java.lang.Byte)
const specs = ffi.createCDef() // 创建 C 函数签名构建器
    .add("sum", ffi.NativeTypes.INT(), ffi.NativeTypes.BYTE(), ffi.NativeTypes.BYTE()) // 声明 sum(int8_t,int8_t) -> int
    .add("getMessage", ffi.NativeTypes.STRING()) // 声明 getMessage() -> const char*
    .add("getMessageLength", ffi.NativeTypes.INT(), ffi.NativeTypes.STRING()) // 声明 getMessageLength(const char*) -> int
    .build(); // 生成最终签名定义

const api = ffi.createFromAssets("libmyapplication.so", specs); // 从 assets 加载 libmyapplication.so 并创建调用实例

const sumResult = api.callInt("sum",  Byte(7),  Byte(5)); // 调用 sum 计算 7 + 5
console.log("sum(7,5) =", sumResult); // 输出 sum 的结果

const msg = api.callString("getMessage"); // 调用 getMessage 获取字符串
console.log("getMessage() =", msg); // 输出返回的消息

const msgLen = api.callInt("getMessageLength", msg); // 把字符串传回 native 计算长度
console.log("getMessageLength(msg) =", msgLen); // 输出返回的长度
lua
local core = require('cheese-lua') -- 导入 cheese-lua 核心模块
local ffi = core.ffi -- 从核心模块获取 ffi 对象
local Byte = java.import("java.lang.Byte")
local specs = ffi.createCDef() -- 创建 C 函数签名构建器
    :add("sum", ffi.NativeTypes.INT(), ffi.NativeTypes.BYTE(), ffi.NativeTypes.BYTE()) -- 声明 sum(int8_t,int8_t) -> int
    :add("getMessage", ffi.NativeTypes.STRING()) -- 声明 getMessage() -> const char*
    :add("getMessageLength", ffi.NativeTypes.INT(), ffi.NativeTypes.STRING()) -- 声明 getMessageLength(const char*) -> int
    :build() -- 生成最终签名定义

local api = ffi.createFromAssets("libmyapplication.so", specs) -- 从 assets 加载 libmyapplication.so 并创建调用实例

local sumResult = api:callInt("sum", Byte(7), Byte(5)) -- 调用 sum 计算 7 + 5
print("sum(7,5) =", sumResult) -- 输出 sum 的结果

local msg = api:callString("getMessage") -- 调用 getMessage 获取字符串
print("getMessage() =", msg) -- 输出返回的消息

local msgLen = api:callInt("getMessageLength", msg) -- 把字符串传回 native 计算长度
print("getMessageLength(msg) =", msgLen) -- 输出返回的长度

对应C/C++实现示例

以下方法实现位于 libmyapplication 动态库中,对应上面的 ffi.createFromAssets("libmyapplication.so", ...) 调用。

cpp
#ifdef __cplusplus
extern "C" {
#endif

#include <jni.h>
#include <cstring>
#include <cstdint>

typedef struct ComplexChild {
    jdouble score;
    jint level;
} ComplexChild;

typedef struct ComplexUser {
    jint id;
    const char* name;
    ComplexChild* child;
    jdouble weight;
    jint name_len;
    jdouble total;
} ComplexUser;

JNIEXPORT jint JNICALL process_complex(ComplexUser* data) {
    if (data == nullptr || data->name == nullptr || data->child == nullptr) {
        return -1;
    }
    data->name_len = static_cast<jint>(std::strlen(data->name));
    data->total = data->weight
        + data->child->score
        + static_cast<jdouble>(data->child->level)
        + static_cast<jdouble>(data->name_len)
        + static_cast<jdouble>(data->id);
    return 0;
}

typedef struct FixedCharData {
    char tag[16];
    jint value;
    jint tag_len;
} FixedCharData;

JNIEXPORT jint JNICALL process_fixed_char(FixedCharData* data) {
    if (data == nullptr) {
        return -1;
    }
    jint len = 0;
    while (len < 16 && data->tag[len] != '\0') {
        len++;
    }
    data->tag_len = len;
    data->value = data->value + len;
    return 0;
}

JNIEXPORT int JNICALL sum(int8_t a, int8_t b) {
    return a + b;
}

JNIEXPORT const char* JNICALL getMessage() {
    static const char* msg = "Hello from C++ JNA";
    return msg;
}

JNIEXPORT jint JNICALL getMessageLength(const char* msg) {
    if (msg == nullptr) {
        return -1;
    }
    return static_cast<jint>(std::strlen(msg));
}

#ifdef __cplusplus
}
#endif

Released under the GPL-3.0 License.