luajit开发文档wiki中文版(二) LuaJIT 扩展

2022年6月9日09:39:53

luajit开发文档中文版(一)下载和安装

luajit开发文档中文版(二)LuaJIT扩展

luajit开发文档中文版(三)FAQ 常见问题

 

luajit开发文档wiki中文版(一) 总目录

luajit开发文档wiki中文版(二) LuaJIT 扩展

luajit开发文档wiki中文版(三)性能调优和测试

luajit开发文档wiki中文版(四) LuaJIT 内部结构

luajit开发文档wiki中文版(五) 系统集成

luajit开发文档wiki中文版(六) LuaJIT 开发

 

可用 FFI 绑定列表

请注意,大多数开发人员更喜欢只绑定到他们实际需要的少数库调用。例如,rmdir()绑定只是与 LuaJIT FFI 的一行。与 eg 的简单绑定zlib只需要两打行,请参阅FFI 教程你可以在网上找到很多这样的例子。

以下列表仅显示较大或较全面的库绑定:

核心操作系统库

数据库

图形

音频视频

消息和事件

联网

编码和解码

加密货币

压缩

游戏引擎

跨语言 RPC

跨语言桥梁

统一码

杂项

  • ufo:为 OSX/Windows 和其他平台预编译的二进制文件。绑定:Cairo、Pixman、SDL 1.3、zlib、libpng、libbz2、ZeroMQ、Expat、Freetype、GLFW、APR、AntTweakBar、OpenGL、OpenCL。
  • luapower/hunspell : hunspell(拼写检查)
  • luajit-tcc : 微型 C 编译器。
  • luapower/luastate : 绑定到 Lua C API 以创建独立的 Lua 状态
  • luapower/dynasm : 使用Lua 的DynASM
  • luapower/cbframe:低级 FFI 回调帧(解决 FFI ABI 限制的方法)
  • luajit-gmp:GNU 多精度库。
  • lua-clang : C 接口到 Clang 的绑定。

 

可用软件包列表

由于 Lua 是一种非常简洁的语言,具有最少的预安装库,因此 Lua 程序员可用的大部分功能来自对外部库执行互操作调用。特别是 LuaJIT,为调用外部库提供了极好的支持。如果您想要访问目标系统上的预编译库,那么您需要使用FFI 绑定库。

如果您想要的是 100% 用 Lua 编写的代码,没有额外的库依赖项,那么您可能想要获取此处列出的包之一。

标准是:

  • 100% LuaJIT 代码
  • 不依赖外部库

您的用例是否通过使用本机包而得到极大改进取决于您的标准。一个关键的好处可能只是你不必携带和维护一个单独的库,它是用与 Lua 不同的语言编写的。

编码和解码

加密

  • lrc4 : RC4 流密码

标准功能

  • LAPHLibs:各种函数,包括utf-8、crc、md5、streams、xml
  • LPegLJ:基于 PEG(解析表达式)和简化 RegEx(正则表达式)al la LPeg的字符串模式匹配。
  • luapower/utf8 : utf-8 基础
  • Lua Fun : 为跟踪 JIT 优化的函数式编程库

系统库

数值库

  • Rclient:通过 Rserve 将 LuaJIT 与 R 语言接口

日期时间库

  • 时间:根据公历的日期和期间

数据结构

图形

束包

  • UFO - 包括 OpenGL 和 glu、OpenCL、SDL、ZeroMQ、GLFW

 

 

FFI参数化类型

参数化类型是 LuaJIT FFI 的一个特性。它使人们能够动态地指定 FFI C 声明。它对泛型编程很有用,类似于使用C++ 模板Java 泛型的原因。

此功能于 2012 年 6 月推出,在 LuaJIT 2.0.1 及更高版本中可用。

要使用此功能,您可以将字符放在由and$使用的 C 类型声明中当你使用这个声明时,你会传递参数。这些参数可以是ctype / cdata字符串数字ffi.cdefffi.type

以下是一些示例(单独来看并不是很有趣):

ffi.typeof("$ *", foo_t)
ffi.typeof("struct { $ k; $ v; }[?]", foo_t, bar_t)
ffi.typeof("uint8_t[$][$]", width, height)

邮件列表中的文档

以下内容发布在 LuaJIT 邮件列表中。

$字符是在 C 类型声明中被参数替换的标记:

  • ctypecdata参数被解释为匿名 typedef。您可以使用它来构造派生类型,例如 ffi.typeof("$*", ct)是指向 的指针ct,或者"$[10]"是 10 元素数组。与简单的字符串连接不同,这适用于所有情况(例如指向函数指针的指针)。

  • 字符串参数被视为标识符或关键字,除了字符串不会再次解析或拆分为单词(这不是简单的文本替换)。您可以将其用于字段名称、函数名称、参数名称等。

    [我正在考虑是否应该删除关键字并在字符串上键入查找。这将允许您对字段使用任意名称,即使它们与关键字或 typedef 冲突。我可能会在编写文档之前更改它。]

  • 数字参数被视为整数。您可以使用它来构造固定大小的数组。在某些情况下,您可能更喜欢 VLA。它也适用于多维类型。例如:

  local matrix_t = ffi.typeof("uint8_t[$][$]", width, height)
  ...
  local m = matrix_t()

解析参数化类型ffi.typeof()ffi.cdef()其他函数,例如ffi.new(),不这样做,因为它们已经接受了其他参数(例如初始化器)。我想混合两种论点会太混乱。

无论如何,参数化类型是一个很好的工具,但你会想谨慎使用它!

您会想到在 C++ 中使用模板的典型场景:例如,构建任意类型的堆栈或队列。这些都需要创建一个元素类型的数组,现在真的很容易做到。[贾斯汀的用例]

另一个例子是匿名结构的派生类型。这避免了结构命名空间的污染。[亨克的用例]

堆栈示例

Mike在 LuaJIT 邮件列表上发布了这个示例:

local ffi = require("ffi")

local function stack_iter(stack)
  local top = stack.top
  if top > 0 then
    stack.top = top-1
    return stack.slot[top-1]
  end
end

local stack_mt = {
  __new = function(tp, max)
    return ffi.new(tp, max, 0, max)
  end,
  __index = {
    push = function(stack, val)
      local top = stack.top
      if top >= stack.max then error("stack overflow") end
      stack.top = top + 1
      stack.slot[top] = val
    end,
    pop = function(stack)
      local top = stack.top
      if top <= 0 then error("stack underflow") end
      stack.top = top-1
      return stack.slot[top-1]
    end,
    iter = function(stack)
      return stack_iter, stack
    end,
  }
}

local function makestack(ct)
  local tp = ffi.typeof("struct { int top, max; $ slot[?]; }", ct)
  return ffi.metatype(tp, stack_mt)
end

local stack_t = makestack(ffi.typeof("double"))

local stack = stack_t(100)
for i=1,100 do stack:push(i) end
for x in stack:iter() do io.write(x, " ") end
io.write("\n")

功能库

以下库使用参数化类型:

 

FFI 回调限制:遇到的案例

虽然绑定到需要回调的 C API 在大多数情况下都很好,但偶尔您会遇到一种罕见的情况,即 API 需要FFI 尚不支持的功能- 特别是带有可变参数或按值传递聚合的回调签名。

请在此处列出您遇到的示例。

Win32 Drag-and-Drop

实现IDropTarget COM 接口需要需要按值传递POINTL结构参数的方法。

Chipmunk空间查询

cpSpaceNearestPointQuerycpSpaceSegmentQuery需要带有按值传递的cpVect结构参数的回调。

libuv uv_read_cb

uv_read_start需要带有传递值uv_buf_t结构参数的uv_read_cb回调。有一个公开讨论,并提出了改变它的建议。

libclang AST 访问者回调

clang_visitChildren需要回调以按值接收CXCursor

请参阅此示例以了解如何克服此问题:FFI Callbacks with pass-by-value structs

libretro可变参数日志记录功能

retro_log_printf_t将日志级别、格式和可变参数呈现给例如 vsnprintf。

Vararg 回调不起作用,因此无法从 luajit (?) 设置此函数。

Cocoa

NSView.drawRect:

NSWindow.windowShouldZoom:toFrame:

NSWindow.windowWillResize:toSize:

 

 

具有传值结构的 FFI 回调

当你有一个 C 回调对结构使用按值传递语义时,LuaJIT FFI 不能直接处理这种情况,但它可以间接处理。当调用具有按值传递结构的函数时,结构被放置在堆栈上,并且函数接收结构的堆栈地址。考虑到这一点,可以重新定义函数,使函数的参数是指向结构的指针,然后在回调中取消引用指针以获取结构值。请记住,此数据在堆栈上,并且将在函数结束时超出范围。

在 Clang 库中,clang_visitChildren 函数接受定义为的回调:

typedef enum CXChildVisitResult (*CXCursorVisitor)(CXCursor cursor, CXCursor parent, CXClientData client_data);

请注意,CXCursor 参数是按值传递的,而不是按地址传递的(CXClientData 是 void* 的 typedef)。如果我们将函数重新定义为:

typedef enum CXChildVisitResult (*CXCursorVisitor_WithPointers)(CXCursor* cursor, CXCursor* parent, CXClientData client_data);

这允许您编写一个与 luaJIT 的 FFI 一起使用的 lua 函数,该函数将允许我们接收指向这些结构的指针。例如,我们想编写一个 lua 函数,它返回一个包含给定游标的所有子项的表。一种方法是使用函数生成器,它允许我们设置一个在回调运行之间保持一致的上值。

function createCursorVisitor(storage)
   local ret = storage

   return function(cursorPtr, parentPtr, usr)
      local c = ffi.new("CXCursor", cursorPtr[0]) --create a copy from the passed in pointer
      local cursor = createCursor(c) --create a lua version
      table.insert(ret, cursor) --add to the list children to be returned at the end
      
      return lib.CXChildVisit_Continue
   end
end

这个 lua 函数接受一个表并将其设置为返回的匿名函数的上值。这个返回的函数是我们将用于回调的函数。在这个函数中,我们获取指向子光标的指针,然后创建它的副本并将其保存在我们的 upvalue 表中以供以后使用。我们必须有一个副本,因为当这个函数返回时, cursorPtr 指向的值将超出范围。

要查看我们如何使用此回调,我们需要创建包含子项的表,然后使用函数创建者创建回调函数。然后我们将 lua 函数转换为回调,并使用我们的回调调用 clang_visitChildren 函数。

function clang_cursor.children(self)
   local cptr = self.cursor 
   local ret = {} --create a table to hold all the children
   local cursorVisitor = createCursorVisitor(ret) --generate a function that uses our tables an upvalue
   
   local cb = ffi.cast("CXCursorVisitor_WithPointers", cursorVisitor) --this will use our redefined function definition
   lib.clang_visitChildren(cptr, cb, nil)
   cb:free() --release the callback object
   
   return ret
end

当 clang_visitChildren 返回时,我们的表将包含传入游标的所有子项。然后我们清理我们的回调并返回我们的子游标表。

 

FFI知识

 

将 cdata 指针转换为 Lua 数

将 cdata 指针转换为 Lua 数字的规范方法是:

tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr)))

这会返回一个签名的结果,当 JIT 编译时,它处理起来更便宜并且变成一个无操作。如果你真的需要一个无符号地址(而不仅仅是一个抽象标识符),那么使用 uintptr_t。 资源

常数

local ffi = require("ffi")
ffi.cdef[[
	struct whatever {
	    static const int FOO = 42;
	    static const int BAR = 99;
	};
	]]
local W = ffi.new("struct whatever")
print(W.FOO, W.BAR)

是的,这些被编译为实际的常量,就像枚举常量(与 Lua 表字段不同)。除了枚举常量在 C 中是全局的,即使在结构中定义。请注意,使用虚拟结构 (W) 的实例作为命名空间比使用 ctype 更有效。 资源

posted on 2022-06-10 14:33  zh7314  阅读(807)  评论(0编辑  收藏  举报