参考如下命令,编译并安装Lua解释器:
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz tar zxf lua-5.3.4.tar.gz cd lua-5.3.4 sudo apt-get install libreadline-dev make linux make install INSTALL_TOP=/home/alex/Lua/sdk/5.3.4
要声明一个变量,使用如下语法:varname = value
任何变量,除非显式添加local限定符,否则均为全局变量,不管它在何处声明:
globalVar = true local localVar = nil
Lua支持块级作用域,局部变量的作用域在所在块内部。
一般的,应当严格避免使用自己定义的全局变量。
Lua在一个名为_G的表格中管理所有全局变量:
_G.globalVar _G["globalVar"]
类似于Go语言的空白标识符。如果多返回值中有你不需要的,可以将其赋值给_
需要注意Lua不支持++、--、+=之类的操作符。
Lua中的逻辑或与非操作符为or、and、not,不等于操作符为~=
Lua中有一个特殊操作符#,它可以获取对象的长度,实际上是调用元方法__len()
Lua中的数字均为双精度浮点型,你不需要担心运算精度或者速度问题。示例:
-- 语句尾部的分号是可选的 num = 4096 num = 6.12 num = 6.02E23 num = 0xff
可以使用单引号、双引号包围,支持多行字符串,支持C风格的转义字符。示例:
str = "Hello World" str = 'Hello\n World' str = [[Hello World]]
访问一个未定义变量时,其值为空,使用关键字nil表示。
有true和false两个值。类型转换时,仅nil转换为false,其余的例如数字0、空串都转换为true。
Lua不区分数组和映射,以整数作为键的表格,可以作为数组来使用:
-- 如果不显示指定键,则自动从1开始为每个元素赋予键
users = { "Alex", "Meng", "Cai", "Dang" }
-- 数组的第一个元素的索引为1,而不是0
print(users[1]) -- Alex
-- 可以使用复数索引
users[-1] = 'Nobody'
print(users[-1]) -- Nobody
以字符串作为键的表格,则相当于其它编程语言中的映射或字典:
user = {
-- 注意语法,使用=号指定键值对
name = "Alex Wong",
age = 32,
gender = 'M',
-- 也可以使用方括号语法指定键值对
['nickname'] = 'Alex'
}
-- 方括号语法、点号导航都支持
user.name = "汪震"
user.name = nil
user.nickname = '震'
要获取表格的元素个数,可以使用#操作符:
users = { [1] = 'Alex', [2] = 'Meng', [3] = 'Cai', [4] = 'Dang' }
-- #users 可以获取表格users的长度
for i = 1, #users do
print(users[i])
end
要遍历表格的键值对,可以:
for idx, user in pairs( users ) do
print( idx, user )
end
array = {
['Z'] = 'z',
[0] = 'a',
[1] = 'b',
[10] = 'b'
};
-- pairs打印所有键值对
for i, v in pairs(array) do print(i .. '=' .. v) end
-- ipairs只能打印从索引从1开始的,而且不能有中断
for i, v in ipairs(array) do print(i .. '=' .. v) end -- 只打印1=b
注意:不要在表格中使用 nil 值,如果一个元素要删除,直接 remove,不要用 nil 去代替:
users = { 1, 2, 3 }
table.remove(users, 1)
print(table.concat(users, ' ')) -- 2 3
-- 单行注释 --[[ 多行注释 --]]
n, i, factorial = 10, 1, 1
-- while循环
while i <= n do
factorial = factorial * i
i = i + 1
end
print(factorial) -- 3628800
-- for循环
for i = 1, n do
factorial = factorial / i
end
print(factorial) -- 1.0
-- until循环
i = 1
repeat
factorial = factorial * i
i = i + 1
until i > n
if len > 100 then
print("L")
elseif len <= 100 and len > 50 then
print("M")
elseif len ~= 0 then
print("S")
else
end
Lua中函数必须在先定义,然后再使用。也就是说,在脚本中函数定义出现在前面。
function getUserName(id)
return "Alex"
end
-- 支持返回多个值
function getUser(id)
return id, "Alex", 32
end
id, name, age = getUser(1)
-- 如果左边变量不足,多余的返回值自动丢弃
-- 如果左边变量过多,后面的全部置为nil
-- 函数调用出现在列表表达式中时,除非函数调用在列表尾部,否则仅仅返回其第一个值
local function twonums() return 1, 2 end
-- 注意下面两个列表表达式的区别
local x, y, z = twonums(), 3;
print(x, y, z) -- 1 3 nil
local x, y, z = 3, twonums();
print(x, y, z) -- 3 1 2
-- print的实参也是列表表达式
print(twonums(), 3) -- 1 3
print(3, twonums()) -- 3 1 2
-- 要强制仅仅返回第一个值,可以用()包围函数调用
print(3, (twonums())) -- 3 1
-- 支持闭包
function counter()
local count = 0
return function()
count = count + 1
return count
end
end
c = counter()
print(c(), c()) -- 1 2
-- 函数可以递归调用自身
function fib(n)
if n < 2 then return 1 end
return fib(n - 2) + fib(n - 1)
end
-- 具有局部作用域的函数
local function f()
end
-- 函数可以赋值给变量
local f = function() return 0 end
f()
-- 变长参数
local function join(...)
local args = { ... } -- 把变长参数填充到表格
print(table.concat(args, "."))
end
-- LuaJIT 2尚不能JIT编译这种变长参数的用法,只能解释执行。所以对性能敏感的代码,应当避免使用此种形式
join('cc', 'gmem', 'study') -- cc.gmem.study
-- 当表格作为参数时,传递的是引用
local function printUser(user)
print(user.name, user.age)
end
printUser({ name = 'Alex', age = 32 })
-- 调用回调
local function call(func, ...)
local args = { ... } or {}
-- unpack,解包表格
func(...)
end
local function add(x, y, z) print(x + y + z) end
call(add, 1, 2, 3) -- 6
作为表格成员的函数,即为方法:
Person = {
name = "Unknown"
}
-- 方法可以用TableName.MethodName的形式定义,此时第一个形参必须为self
-- 代表当前对象
function Person.getName(self)
return self.name
end
-- 方法也可以用TableName:MethodName的形式定义,此时隐式的自动定义self局部变量
function Person:getName()
return self.name
end
print(Person:getName()) -- Unknown
MetaTable可以用来实现操作符重载:
-- 元表格
user_ops = {}
-- __add是一种元方法(MetaMethod)
function user_ops.__add(u1, u2)
return 'child of ' .. u1.name .. ' and ' .. u2.name
end
alex = { name = 'Alex' }
meng = { name = 'Meng' }
-- 可以为任何表格设置元表格
setmetatable(alex, user_ops)
setmetatable(meng, user_ops)
print(alex + meng) -- child of Alex and Meng
所有可用的元方法如下表:
| 元方法 | 说明 |
| __add(a, b) | a + b |
| __sub(a, b) | a - b |
| __mul(a, b) | a * b |
| __div(a, b) | a / b |
| __mod(a, b) | a % b |
| __pow(a, b) | a ^ b |
| __unm(a) | -a |
| __concat(a, b) | a .. b |
| __len(a) | #a |
| __eq(a, b) | a == b |
| __lt(a, b) | a < b |
| __le(a, b) | a <= b |
| __index(a, b) | a.b |
| __newindex(a, b, c) | a.b = c |
| __call(a, ...) | a(...) |
利用元方法__index,可以实现基于原型的继承机制,类似于JavaScript:
Flower = {}
print(Flower) -- 0x1cc1400
-- 定义构造器
function Flower:new(f)
-- self指向调用者本身 0x1cc1400 也就是Flower这个表格
-- 下面这个表达式将Flower变为元表格
self.__index = self
print(self)
-- 调用setmetatable(a, {__index = b}),即让b作为a的原型
-- 在这里,就是让self成为f的原型
return setmetatable(f, self)
end
-- 定义普通方法
function Flower:getColor()
return self.color
end
-- 也可以使用点形式定义,但是self需要作为第一个形参
function Flower.getColor(self)
return self.color
end
-- 实例化新对象
jasmine = Flower:new({ color = 'White' })
print(jasmine.color) -- White
-- 调用方法,注意使用冒号而非点号
print(jasmine:getColor()) -- White
-- 如果用点号调用,则必须把self作为第一个参数传入
print(jasmine.getColor(jasmine)) -- White
-- 继承
Jasmine = Flower:new({ color = 'White' })
function Jasmine:new(j)
self.__index = self
return setmetatable(j, self)
end
function Jasmine:getSmell()
return self.smell
end
function Jasmine:toString()
-- 可以在方法中定义方法
return self:getColor() .. ' Jasmine Smells ' .. self.smell
end
jasmine = Jasmine:new({ smell = 'Nice' })
print(jasmine:toString()) -- White Jasmine Smells Nice
你可以使用require('XX')语句来引入其它模块,也就是XX.lua文件。require时目标.lua文件会自动被执行。注意:
local xxModule = loadfile("XX")
-- ...
-- 下面的调用导致目标模块被执行
xxModule()
一个长方形模块的示例:
-- 此局部变量,一个表格,作为模块成员的容器
local _M = {}
-- 此属性,通常作为模块版本
_M._VERSION = '1.0'
local mt = { __index = _M }
function _M.new(self, width, height)
return setmetatable({ width=width, height=height }, mt)
end
function _M.get_rectangle(self)
return self.width * self.height
end
function _M.get_circumference(self)
return (self.width + self.height) * 2
end
-- 返回模块容器
return _M
使用此模块:
local rectangle = require "rectangle" local s = rectangle:new(1, 2) print(s:get_rectangle()) -- 2 print(s:get_circumference()) -- 6
要在 Lua 中处理错误,必须使用函数 pcall(protected call)来包装需要执行的代码。 pcall接受以下参数:
pcall的返回值有两个:是否调用成功,被调用函数的返回值。
示例:
function json_decode( str )
-- 安全的解码JSON
local ok, t = pcall(_json_decode, str)
if not ok then
return nil
end
return t
end
-- string.byte(s [, i [, j ]])
-- 获取字符串s从i到j这些字符的ASCII码,返回列表
a, b, c = string.byte("abc", 1, 3)
print(a, b, c) -- 97 98 99
-- string.char (...)
-- 从列表ASCII码构建字符串
print(string.char(97, 98, 99)) -- abc
-- 大小写转换
print(string.upper("Hello Lua")) -- HELLO LUA
print(string.lower("Hello Lua")) -- hello lua
-- 重复字符串
print(string.rep("abc", 3)) -- abcabcabc
-- 返回子串 string.sub(s, i [, j])
print(string.sub(123456,3,5)) -- 345
-- 替换子串 string.gsub(s, p, r [, n])
-- 替换s中的p为r,n表示替换的最大次数
-- 返回两个值:替换后的字符串,替换发生的次数
print(string.gsub("Lua Lua Lua", "Lua", "hello")) -- hello hello hello 3
-- 获得字符串长度
str = "hello lua"
print(string.len(str))
print(#str) -- 推荐使用这种方式获得长度,获取字节数
-- string.find(s, p [, init [, plain]])
-- 子串查找,从s中勋撰p,可以指定搜索起始位置init。返回子串的索引
sidx, eidx = string.find('123456789', '45')
print(sidx, eidx) -- 4 5
-- C风格的字符串格式化 string.format(formatstring, ...)
print(string.format("%.4f", 3.1415926)) -- 3.1416
print(string.format("%d %x %o", 31, 31, 31)) -- 31 1f 37
print(string.format("%02d/%02d/%d", 1, 1, 2018)) -- 01/01/2018
-- 模式匹配,不支持JIT,考虑使用ngx.re.match代替
-- string.match(s, p [, init]) 从s中搜索模式p,返回匹配的子串
print(string.match("Linux Zircon 3.13.0-83-generic", "%d.%d+.%d%-%d+")) -- 3.13.0-83
-- 全局模式匹配,返回一个迭代器,此迭代器依次返回每个匹配的子串
for word in string.gmatch("quick fox jumps over the lazy dog", "%w+") do
print(word)
end
此库将表格作为数组来操作。
-- table.getn(t) 获取表格长度
-- 注意:获取到的是最后一个数字下标
print(table.getn({ 1, 2, 3 })) -- 3
print(table.getn({ 1, two = 2, 3 })) -- 2 最后一个索引从1开始顺延
print(table.getn({ 1, 2, nil, 4, 5, 6 })) -- 6 如果最后是非nil,其索引被使用
print(table.getn({ 1, 2, nil, 4, 5, nil })) -- 2 如果最后是nil,则第一个nil之前的索引被使用
-- table.maxn (table) 返回最大的索引值
print(table.maxn({ [1000] = 0 })) -- 1000
-- table.concat (table [, sep [, i [, j ] ] ]) 连接数组元素为字符串
-- sep没认为空串,i为起始元素,j为结束元素
print(table.concat({'Alex','Meng'})) -- AlexMeng
-- table.insert (table, [pos ,] value) 插入元素到数组,pos默认为表长度+1,即默认为附加元素
array = { 1, 2, 3 }
table.insert(array, 4)
print(array[4]) -- 4
-- table.remove (table [, pos]) 删除指定位置上的元素,默认从尾部删除
table.remove(array)
print(#array) -- 3
-- table.sort (table [, comp]) 根据传入的比较器来排序
local function compare(x, y)
--从大到小排序
return x > y
end
table.sort(array) -- 默认从小到大
table.sort(array,compare) -- 从大到小
LuaJIT 2.1 新增加 table.new 和 table.clear。前者用来预分配 Lua table 空间,后者用来高效的释放 table 空间,都可以被 JIT 编译。
os模块的一些函数,提供了日期、时间相关的功能,但是比较重量级,不支持被LuaJIT编译。使用OpenResty时推荐使用ngx_lua模块提供的接口。
-- os.time ([table]) 返回当前时间,或者table所指定的时间(从UNIX纪元开始流逝的秒数
print(os.time()) -- 当前时间
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
print(os.time(a)) -- 10,因为当前是东八区
-- os.difftime (t2, t1) 返回两个时间的差值(t2-t1) 单位秒
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
b = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 15 }
print(os.difftime(os.time(a), os.time(b))) -- -5
-- os.date ([format [, time]]),返回一个描述时间的表格
-- format 格式说明,time时间点(秒)
local tab1 = os.date("*t")
local now = ""
for k, v in pairs(tab1) do --把tab1转换成一个字符串
now = string.format("%s %s = %s ", now, k, tostring(v))
end
print(now) -- hour = 18 min = 3 wday = 2 day = 26 month = 2 year = 2018 sec = 52 yday = 57 isdst = false
-- 下面的占位符用于格式化日期
-- %a 一星期中天数的简写(例如:Wed)
-- %A 一星期中天数的全称(例如:Wednesday)
-- %b 月份的简写(例如:Sep)
-- %B 月份的全称(例如:September)
-- %c 日期和时间(例如:07/30/15 16:57:24)
-- %d 一个月中的第几天[01 ~ 31]
-- %H 24小时制中的小时数[00 ~ 23]
-- %I 12小时制中的小时数[01 ~ 12]
-- %j 一年中的第几天[001 ~ 366]
-- %M 分钟数[00 ~ 59]
-- %m 月份数[01 ~ 12]
-- %p “上午(am)”或“下午(pm)”
-- %S 秒数[00 ~ 59]
-- %w 一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六]
-- %x 日期(例如:07/30/15)
-- %X 时间(例如:16:57:24)
-- %y 两位数的年份[00 ~ 99]
-- %Y 完整的年份(例如:2015)
-- %% 字符'%'
print(os.date('%A')) -- Monday
print(os.date('%Y-%m-%d')) -- 2018-02-26
math.max(x, ...) -- 返回参数中值最大的那个数 math.min(x, ...) -- 返回参数中值最小的那个数 math.abs(x) -- 返回x的绝对值 math.fmod(x, y) -- 返回 x对y取余数 math.pow(x, y) -- 返回x的y次方 math.sqrt(x) -- 返回x的算术平方根 math.exp(x) -- 返回自然数e的x次方 math.log(x) -- 返回x的自然对数 math.log10(x) -- 返回以10为底,x的对数 math.floor(x) -- 返回最大且不大于x的整数 math.ceil(x) -- 返回最小且不小于x的整数 math.rad(x) -- 角度x转换成弧度 math.deg(x) -- 弧度x转换成角度 math.sin(x) -- 求弧度x的正弦值 math.cos(x) -- 求弧度x的余弦值 math.tan(x) -- 求弧度x的正切值 math.asin(x) -- 求x的反正弦值 math.acos(x) -- 求x的反余弦值 math.atan(x) -- 求x的反正切值 -- 不传入参数时,返回 一个在区间[0,1)内均匀分布的伪随机实数 -- 只使用一个整数参数m时,返回一个在区间[1, m]内均匀分布的伪随机整数 -- 使用两个整数参数时,返回一个在区间[m, n]内均匀分布的伪随机整数 math.random (m,n) -- 为伪随机数生成器设置一个种子,设置后math.random产生的随机序列改变 math.randomseed (x) pi=math.pi -- 圆周率
Lua I/O提供的文件操作都是阻塞性的,不应该在OpenResty中和非阻塞的网络IO混合在一起。
path = [[/home/alex/Lua/projects/luna/logs/error.log]]
-- 设置输入输出默认针对的文件
file = io.input(path)
-- 逐行读取默认文件
repeat
line = io.read() -- 读取一行
if (line) then print(line) end
until (nil == line)
io.close(file) -- 关闭文件描述符
-- 以追加模式打开文件
file = io.open(path, "a+")
-- 所有可用的文件打开模式
-- 默认 说明 文件不存在时的行为
-- "r" 读模式 (默认) 返回nil加错误信息
-- "w" 写模式 创建文件
-- "a" 添加模式 创建文件
-- "r+" 更新模式,保存之前的数据 返回nil加错误信息
-- "w+" 更新模式,清除之前的数据 创建文件
-- "a+" 添加更新模式,保存之前的数据,在文件尾进行添加 创建文件
-- 设置默认输出文件
io.output(file)
io.write("\nHello Lua")
-- 调用file:***方法,可以针对特定文件(而不是默认文件)进行读写
file = io.open(path, "r")
-- file:lines()方法返回所有行的迭代器
for line in file:lines() do
print(line)
end
-- 其它方法:
-- file:read (...) 读取数据
-- file:write (...) 写入数据:
-- *n 读取一个数字
-- *a 从当前位置读取整个文件。若当前位置为文件尾,则返回空字符串
-- *l 读取下一行的内容。若为文件尾,则返回nil
-- 100 读取100字节
print(file:read(10))
-- file:seek ([whence] [, offset]) 设置和获取当前文件位置
-- offset 向前搜索的偏移量
-- whence 偏移量的相对位置:set相对文件开始;cur相对当前位置(默认);end相对文件结束
file:seek('cur', 100)
-- file:setvbuf (mode [, size]) 设置输出文件的缓冲模式
-- mode 模式:no无缓冲;full全缓冲(缓冲区满了后才输出);line最多缓冲一行
file:setvbuf('full', 2048)
-- file:flush () 刷出文件缓冲区
file:flush()
-- file:close () 关闭文件
file:close()
LuaJIT是Lua的一个即时(Just-In-Time)编译器,是最快的动态语言实现之一。通过编译,Lua的执行效率大大提高。LuaJIT被大量的用在游戏、网络等编程领域。LuaJIT仅仅是一个独立的可执行程序,其命令行选项和标准的Lua没有太大区别。
LuaJIT 的运行时环境包括一个用手写汇编实现的 Lua 解释器和一个可以直接生成机器代码的 JIT 编译器。
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz tar xzf LuaJIT-2.0.5.tar.gz && rm LuaJIT-2.0.5.tar.gz cd LuaJIT-2.0.5/ make install PREFIX=/home/alex/Lua/LuaJIT/2.0.5
C函数要能够作为Lua扩展库,其接口必须遵循typedef int (*lua_CFunction)(lua_State* L)的形式。此接口的入参是lua_State类型的指针,从Lua传递来的实参,必须通过该指针间接的读取。返回值int表示返回值的数量,返回值本身无法直接传递回去,必须使用虚拟栈来传递。开发Lua扩展库非常麻烦。
FFI是LuaJIT中最重要的一个扩展库。支持从纯Lua代码调用外部C函数,使用C数据结构。有了FFI,你就不再需要编写Lua扩展库,另外FFI的性能比扩展库更高。
| 名词 | 说明 |
| cdecl | 一个抽象的C类型的定义,表现为Lua字符串 |
| ctype | C类型对象,属于一种特殊的cdata,由 ffi.typeof()获得,你可以调用它,获得cdata对象 |
| cdata | C数据对象,和ctype类型匹配的值 |
| ct | C类型格式,可以指cdecl, cdata, ctype |
| cb | 回调对象,持有一个特殊的函数指针,从C代码调用此函数指针,会执行对应的Lua函数 |
| VLA | 变长数组,例如int[?]。数组元素的个数,必须在创建VLA时给出 |
| VLS | 变长结构,即最后一个成员是VLA的C结构体 |
用于声明(而不是定义)C函数或者C数据结构。所有使用到的函数都需要声明。
函数可以是 C 标准函数,第三方库函数,或自定义的函数。
据结构可以是结构体、枚举或者是联合体。
示例:
ffi.cdef [[
typedef struct foo { int a, b; } foo_t;
int printf(const char *fmt, ...);
]]
通过给定的名称来加载动态库,调用格式:ffi.load(name [,global])。
动态库可以指定全限定路径,如果仅仅指定basename则必须位于LD_LIBRARY_PATH,动态库扩展名可以不加:
ffi.load("z") -- 寻找libz.so,或者z.dll
如果global设置为true,则在POSIX系统中目标动态库中的符号被加载到命名空间ffi.C。例如:
ffi.load('z',true)
ffi.cdef[[
int z(int x, int y);
]]
local res = ffi.C.z(1, 2)
创建一个ctype对象。调用格式:ctype = ffi.typeof(ct)。示例:
local uintptr_t = ffi.typeof("uintptr_t")
local c_str_t = ffi.typeof("const char*")
local int_t = ffi.typeof("int")
local int_array_t = ffi.typeof("int[?]")
-- 示例:
local int_t = ffi.typeof("int")
local i = int_t(10)
print(i) -- cdata<int>: 0x401258e8
在堆内存中开辟空间,创建指定类型的ct。调用格式:
-- 从指定的ct创建cdata,对于VLA/VLS类型,需要指定元素个数(nelem) cdata = ffi.new(ct [,nelem] [,init...]) -- 等价方式: cdata = ctype([nelem,] [init...])
使用ffi.new分配的 cdata 对象指向的内存块是由垃圾回收器 LuaJIT GC 自动管理的,不需要用户去释放内存。
使用 ffi.C.malloc()分配的空间不使用 LuaJIT 自己的分配器,需要开发人员自己负责释放。但是ffi.C.malloc返回的cdata对象仍然由LuaJIT GC管理。
你可以通过ffi.gc()为ffi.C.malloc返回的cdata对象注册析构函数,并在此函数中调用ffi.C.free()。这样的话,当cdata对象被GC后,C内存也可以被释放。
ADM64上LuaJIT能够管理的内存量在2G这个级别,如果要分配非常大的内存,请使用ffi.C.malloc()
示例代码:
local int_array_t = ffi.typeof("int[?]")
local array = ffi.new(int_array_t, 100)
填充内存。调用格式:ffi.fill(dst, len [,c])。 类似于C函数memset(dst, c, len)
拷贝内存。调用格式:
-- src作为const void *看待 -- dst作为void *看待 ffi.copy(dst, src, len) ffi.copy(dst, str)
将指定的值强制转换为cdata。调用格式:cdata = ffi.cast(ct, init)
cdata的初始值从init得到,使用C类型转换约定。示例:
local c_str_t = ffi.typeof("const char*")
local c_str = ffi.cast(c_str_t, "Hello")
注册一个析构函数(finalizer)。调用格式:cdata = ffi.gc(cdata, finalizer)
示例:
-- 返回注册了析构函数之后的cdata local p = ffi.gc(ffi.C.malloc(n), ffi.C.free) p = nil -- 消除最后一个引用 -- 下一个GC周期中,会自动调用ffi.C.free(p)
获取上一次C函数调用的error。调用格式:err = ffi.errno([newerr])。如果指定newerr,则返回旧error,并将error设置为新值。
从指针创建Lua字符串。调用格式:str = ffi.string(ptr [,len])
其中ptr为C指针。如果:
每当Lua函数被转换为C函数指针时,LuaJIT FFI 都会自动创建特殊的callback。这种情况可以:
目前,仅仅特定的C函数类型可以作为用作回调。
FFI为回调提供了两个特殊方法。
释放和回调相关的资源,关联的Lua函数将可以被GC。对应的函数指针不再有效,不能再被调用。
将回调关联到一个新的Lua函数,此回调的C类型、回调的函数指针保持不变。
不管隐式创建的cdata,还是ffi.new(), ffi.cast()显式创建的cdata,都可以被垃圾回收。使用cdata时,你必须确保在Lua栈、upvalue或Lua table上保留对cdata的有效引用,一旦最后一个引用失效,则在下一个GC周期中cdata会被自动释放。
如果你要分配一个数组类型的cdata给一个指针的话,必须保证cdata有额外的引用:
ffi.cdef[[
typedef struct { int *a; } foo_t; // 包含指针成员的结构
]]
local s = ffi.new("foo_t", ffi.new("int[10]")) -- 错误
local a = ffi.new("int[10]") -- 正确,自己持有cdata
local s = ffi.new("foo_t", a) -- 然后传递给C指针
-- 直到不需要了,你才能销毁引用a
在LuaJIT中调用C函数非常的方便,例如:
local ffi = require("ffi")
ffi.cdef [[
int printf(const char *fmt, ...);
]]
-- 标准C库不需要ffi.load,可以直接使用
ffi.C.printf("Hello %s!", "world")
更加复杂的代码示例:
-- 调用zlib库的例子
local ffi = require("ffi")
ffi.cdef [[
unsigned long compressBound(unsigned long sourceLen);
int compress2(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen, int level);
int uncompress(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen);
]]
-- 不同操作系统中库的名称不一样
-- 将C共享库绑定到名字空间zlib
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")
-- 压缩缓冲区txt
local function compress(txt)
-- 将缓冲区长度传递给zlib
local n = zlib.compressBound(#txt)
-- 创建缓冲区cdata。uint8_t[?]为变长数组,其实际长度为n
local buf = ffi.new("uint8_t[?]", n)
-- C语言中可以传递变量地址(指针),但是Lua中没有对应的东西
-- 因此只能使用长度为1的数组代替之
local buflen = ffi.new("unsigned long[1]", n)
-- 执行压缩
-- buflen在C函数中是一个指针,实际使用buf的长度,需要通过buflen传递回来
local res = zlib.compress2(buf, buflen, txt, #txt, 9)
assert(res == 0)
-- 从指定的C缓冲区创建Lua字符串,长度为buflen[0]
return ffi.string(buf, buflen[0])
end
cdata用于保存任何C类型到Lua变量中。这种变量实际上指向一块原生的内存区域。除了赋值和相同性判断,LuaJIT没有为cdata预定义任何的操作。
你可以通过元表为cdata自定义一组操作,并调用这些操作:
local ffi = require("ffi")
ffi.cdef [[
// 定义一个C结构
typedef struct { double x, y; } point_t;
]]
-- 一个元表
local mt = {
__add = function(a, b) return point(a.x + b.x, a.y + b.y) end,
-- # 操作符支持
__len = function(a) return math.sqrt(a.x * a.x + a.y * a.y) end,
__index = {
-- :area方法
area = function(a) return a.x * a.x + a.y * a.y end,
},
}
-- 为ct设置元表,返回设置后的ctype
-- ctype = ffi.metatype(ct, metatable)
local point = ffi.metatype("point_t", mt)
local a = point(3, 4)
print(a.x, a.y) -- 3 4
print(#a) -- 5
print(a:area()) -- 25
local b = a + point(0.5, 8)
print(#b) -- 12.5
注意:元表与 C 类型的关联是永久的,而且不允许被修改,__index 元方法也是 。
| C | Lua |
| x = *p | x = p[0] |
| *p = y | p[0] = y |
| x = p[i] | x = p[i] |
| p[i+1] = y | p[i+1] = y |
| x = a[i] | x = a[i] |
| a[i+1] = y | a[i+1] = y |
| x = s.field | x = s.field |
| s.field = y | s.field = y |
| x = sp->field | x = sp.field |
| sp->field = y | s.field = y |
| y = p - i | y = p - i |
| x = p1 - p2 | x = p1 - p2 |
| x = &a[i] | x = a + i |
LuaRocks是一个包管理器,它可以创建、安装Lua模块,将其打包为自包含的包,这种包称为rocks。
wget https://luarocks.org/releases/luarocks-2.4.3.tar.gz
tar zxpf luarocks-2.4.3.tar.gz
cd luarocks-2.4.3
./configure --prefix=/home/alex/Lua/sdk/5.3.4
# 将LuaRock作为一个Rock安装
make bootstrap
# 查看环境变量,将其拷贝到~/.profile中
luarocks path
# 配合LuaJIT安装
export PATH=/home/alex/Lua/LuaJIT/2.0.5/bin:$PATH
./configure --prefix=/home/alex/Lua/LuaJIT/2.0.5 --lua-suffix=jit \
--with-lua-include=/home/alex/Lua/LuaJIT/2.0.5/include/luajit-2.0
示例:
luarocks install luasocket
Rock会被安装到:/home/alex/Lua/sdk/5.3.4下。
Leave a Reply to Yann Cancel reply