一、Lua加入工程

  • 1)官方下载源码,解压;
  • 2)使用VS或XCode或其他IDE新建一个“hello worde”的C++工程;
  • 3)将lua源码加入工程目录中,我的测试目录结构如下,
1
2
3
4
5
6
7
├── lua
│   ├── Makefile
│   ├── README
│   ├── doc
│   └── src
├── main.cpp
└── main.lua

./lua即为lua源码包解压出来的东西,lua的源码文件在lua/src目录下,将lua/src下的文件除Makefile、lua.c(有一个main函数,这是lua的解释器)、luac.c(lua编译器,编译后的lua脚本可被更高效率的使用)外的其他源码文件都加入到helloworld的工程中,当然,可以在工程中增加一个”luasrc”的文件筛选器,将要加入的lua源码文件都放在该文件筛选器下,便于工程结构的分类管理。
此时在main.cpp中,还未加入lua的引用,先编译一次,应该可以编译通过main.cpp及lua的源码文件。

  • 4)将lua引入C++

示例main.cpp,再编译一次,应该可以编译成功,示例程序MyLua可用来执行指定的lua脚本的文件。
mian.cpp code

二、变量

全局(虚拟机对多文件全局)

1
x = 12345

就理解为是lua_State的全局变量x,对所有文件都通用。

本地(虚拟机对单文件局部)(local关键字修饰)

1
local y = 54321

就理解为是lua_State在某个文件里的变量y,仅在那个文件中可用。

三、变量与类型

Lua是动态类型语言,变量不要类型定义,只需要为变量赋值,给变量赋值了什么类型,变量就是什么类型。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
--变量类型
local boolean bv = 0.0
bv = x > 250
if bv then
print("bv is ", bv, " true")
else
print("bv is ", bv, " false")
end

math.randomseed(os.time())
bv = math.random(500)

if bv > 250 then
print("bv is ", bv, " > 250")
else
print("bv is ", bv, " <= 250")
end

bv = "bv -> string"
print("bv is ", bv)
--bv = bv + 1 --非法的操作,无法将不能转化为数值的字符串用于算术计算
--print("bv is ", bv)

bv = "0xEF"
print("bv is ", bv)
print(type(bv)) --string

bv = bv + 1 --在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
print("bv is ", bv)
print(type(bv)) --number

bv = "0xEF"
print("bv is ", bv)
bv = string.format("%s%s", bv, "kkkl")
print("bv is ", bv)
print(type(bv)) --string

bv = "0xEF"
print("bv is ", bv)
print(type(bv)) --string

bv = string.format("%d%s", bv, "kkkl")
print("bv is ", bv)
print(type(bv)) --string

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bv is 	false	 false
bv is 231 <= 250
bv is bv -> string
bv is 0xEF
string
bv is 240.0
number
bv is 0xEF
bv is 0xEFkkkl
string
bv is 0xEF
string
bv is 239kkkl
string

四、控制语句

if判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
math.randomseed(os.time())  
x = math.random(500)
print("x is", x)

if x < 100 then
print("x ∈ [0, 100)")
elseif x < 200 then
print("x ∈ [100, 200)")
elseif x == 250 then
print("中彩票了,250")
elseif x < 300 then
print("x ∈ [200, 250)U(250, 300)")
elseif x < 400 then
print("x ∈ [300, 400)")
elseif x < 500 then
print("x ∈ [400, 500)")
elseif x == 500 then
print("x == 500")
else
print("x is ", x)
end

for循环

1
2
3
for i = 0, 1, 0.1 do
print("for: i is", i)
end

while循环

1
2
3
4
5
i = 1.1
while i < 2 do
print("while: i is ", i)
i = i + 0.1
end

repeat循环

1
2
3
4
5
i = 2.0
repeat
print("repeat: i is ", i)
i = i + 0.1
until i > 3

五、表、元表、元方法

万能的表,表之于LUA的地位应该就相当于类之于C++了吧,之前在理解元表与元方法上还有许多模糊的地方。看视频教材,也没有说的很清楚,遂决定抛弃视频教材,还是看看官方的手册比较靠谱点。

表、元表、元方法的关系简单理解应该就是,元表中定义了元方法,或者说元方法就是元表的元素,表使用setmetatable关联元表,表就可以调用元表中的元方法了,而元表的本质也还是表,所以任意表都可以做元表,包括表做自己的元表,可见元表应该是一个相对的概念。

剩下需要重点学习一下的就是元方法了。

操作符”重载”的元方法

1
2
3
4
5
6
7
8
9
10
11
12
__add	-- function (a, b)		--对应 + 操作符, a + b
__sub -- function (a, b) --对应 - 操作符, a - b
__mul -- function (a, b) --对应 * 操作符, a * b
__div -- function (a, b) --对应 / 操作符, a / b
__unm -- function (a) --对应 -(相反数)操作符, -a
__mod -- function (a, b) --对应 % 取模操作符, a % b
__pow -- function (a, b) --对应 ^ 乘幂操作符, a ^ b
__call -- function (tbl, ...) --对应 () 操作符, tbl(...)

__eq -- function (a, b) --对应 == 操作符, t1 == t2
__lt -- function (a, b) --对应 < 操作符, t1 < t2
__le -- function (a, b) --对应 <= 操作符 t1 <= t2

库定义的元方法

1
2
__tostring		-- function (tbl) --例如print(tbl) 时,若tbl的元表中定义了__tostring元方法,则自动调用该元方法取自定义的tostring结果(与java中tostring目的相同)
__metatable -- 给__metatable赋值后,再对主表setmetatable其他元表时会报错,保护主表的元表不被改变,getmetatable会返回__metatable字段值,隐藏元表

table访问/赋值的元方法

1
2
__index		-- table变量或function (tbl, key)		-- tbl[key]不存在时,将调用__index获取tbl[key]的返回值(mt.__index = mt,结果即为tbl[key]不存在时就取mt[key]做tbl[key]的值)
__newindex -- table变量或function (tbl, key, val) -- 赋值val给tbl[key]时,若tbl[key]不存在,则会调用__newindex(tbl, key, val)处理替代默认的创建tbl[key]并赋值val的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Set = {}

local mt = {}
mt.name = "defaultName"

function Set.new(l)
local set = {}
setmetatable(set, mt)
for k, v in ipairs(l) do
set[v] = true --将集合的元素作为key保存,避免查重检验
end

return set
end

--求并集
function Set.union(a, b)
local res = Set.new{}

for k in pairs(a) do
res[k] = true
end

for k in pairs(b) do
res[k] = true
end

return res
end

--求交集
function Set.intersection(a, b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k] --只有a[k]、b[k]都不是nil时,res[k]才不是nil
end

return res
end

function Set.tostring(set)
local l = {}

for k in pairs(set) do
l[#l + 1] = k
end

return "{" .. table.concat(l, ", ") .. "}"
end

function Set.print(s)
print(Set.tostring(s))
end

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(mt)
print("s1的元表", getmetatable(s1))
print("s2的元表", getmetatable(s2))
Set.print(s1)
Set.print(s2)

--算术类元方法
mt.__add = Set.union
Set.print(s1 + s2)

mt.__mul = Set.intersection
Set.print((s1+s2)*s1)

--关系类元方法
-- <=
mt.__le = function (a, b)
for k in pairs(a) do
if not b[k] then
return false --如果a中有,b中没有就返回false
end
end

return true --a中有的,b中都有,则认为a <= b
end

-- <
mt.__lt = function (a, b)
return a <= b and not(b <= a)
end

-- ==
mt.__eq = function (a, b)
return a <= b and b <= a
end

print(s1 < s2, s2 > s1)
print(s1 <= s2, s2 >= s1)
print(s1 == s2)
print(s1*s2 <= s1+s2)
print(s1*s2 < s1+s2)

--库定义的元方法
mt.__tostring = Set.tostring
print(s1, s2, s1+s2, s1*s2)

mt.__metatable = "not your business"
print(getmetatable(s1)) --设置了__metatable后,getmetatable会返回该字段值,隐藏元表
--setmetatable(s1) --设置了__metatable后,setmetatable会报错,保护元表

--table访问的元方法
mt.__index = mt
--mt.__index = function (table, key)
-- return mt[key]
--end

s1.name = "s1 table"
s2.name = "s2 table"

print(s1.name, s2.name)
print(s2.name, s1.name)

六、类

实际lua中应该没有类的概念,而是使用原型的概念来组织对象间的共享行为。原型也是一种常规对象,当其他对象遇到未知操作时就在这个对象的原型对象中寻找这个未知操作的定义。这样元表的作用就和原型的作用很类似,所以就可以使用元表来实现lua的面向对象编程,原型也就成了元表在lua面向对象编程时的代名词。

继承和多重继承

使用setmetatable(Super, Base)Base.__index = Base来模拟继承的特性。

使用setmetatable(Super, {__index = function (t, k) return search(k, {Base1, Base2}); end})来模拟多重继承的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
--lua面向對象編程的demo

--原型定義
Account = {balance = 0};

function Account:new(o)
o = o or {}; --如果用戶沒有提供table就創建一個
setmetatable(o, self);
self.__index = self;
return o;
end

function Account:deposit(v) --存
self.balance = self.balance + v;
print("deposit", self.balance);
end

function Account:withdraw(v) --取
if v > self.balance then
print("insufficient funds");
end

--如果self中原先不存在balance,就会先用Account里的balance-v,然后将结果保存到新建的self.balance元素里
self.balance = self.balance - v;
print("withdraw", self.balance);
end

--继承
SpecialAccount = Account:new();

function SpecialAccount:getLimit()
return self.limit or 0;
end

function SpecialAccount:withdraw(v)
if self.balance - v < -self:getLimit() then
print("insufficient funds");
end

self.balance = self.balance - v;
print("withdraw", self.balance);
end

a3 = Account:new{balance = 0};
a4 = Account:new();
s1 = SpecialAccount:new{limit = 1000.00};
s2 = SpecialAccount:new();

print(Account, a1, a2, a3, a4, getmetatable(a4), getmetatable(s1));
print(Account, a1, a2, a3, a4, getmetatable(a4), getmetatable(s1));

a3:withdraw(100);
a4:withdraw(100);
s1:withdraw(100);
s2:withdraw(100);

--多重继承
Named = {}

function Named:getName()
return self.name;
end

function Named:setName(n)
self.name = n;
end


local function search(k, plist)
for i, z in pairs(plist) do
local v = plist[i][k]
if v then return v end
end
end

function createClass(a, b) --多重继承
local c = {}
local parents = {a, b}

setmetatable(c, {__index = function (t, k)
return search(k, parents);
end})

c.__index = c;
function c:new(o)
o = o or {};
setmetatable(o, self);
return o;
end

return c;
end

NamedAccount = createClass(Account, Named);
SpecialNamedAccount = createClass(SpecialAccount, Named);

acnt1 = NamedAccount:new{name = "Paul"};
acnt2 = SpecialNamedAccount:new{name = "Ann"};
print(acnt1:getName(), acnt1.balance);
acnt2:deposit(200);
print(acnt2:getName(), acnt2.balance, acnt2:getLimit());

私密性

虽然可以模拟,但对lua的设计目的而言,私密性似乎不非常重要,暂时一瞥掠过。

七、与C/C++交互

C程序可以使用lua库来执行lua代码,lua代码也可以调用在lua环境中注册了的用C语言实现的函数。
lua的C API包含读写lua全局变量、调用lua函数、运行一段lua代码、注册C函数供lua调用等。
lua.h(lua.hpp)声明了Lua提供的基础函数,包括创建Lua环境、调用Lua函数、读写Lua环境中的全局变量以及注册供lua调用的函数等,函数都以lua_开头;
luaxlib.h声明了辅助库(auxiliary library,auxlib)提供的函数,都以luaL_开头。辅助库并没有直接访问Lua的内部,而是都以官方基础API来完成所有工作。
Lua库中没有定义任何全局变量,而是将所有的状态信息都保存在动态结构lua_State中,所有lua C API的调用都要传入这个动态结构的指针。如果简单的将lua_State看作栈的话,这个栈有可以看作有两套引索序号,一套是从栈底到栈顶依次是1到N递增(绝对引索),另一套是从栈顶到栈底的引索序号-1到-N的递减(相对引索)。所以可以直接使用引索值-1和1分别访问栈顶和栈底元素。

环境创建

lua_State是C程序与lua交互编程中最常用的数据结构,要C程序读取Lua中的变量,也是先调用lua的CAPI,将lua中的数据压入lua_State栈中,然后C程序再通过lua的CAPI读取lua_State栈中的元素,如此间接读取到lua的变量值。

1
2
3
LUALIB_API lua_State *(luaL_newstate) (void);
LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, const luaL_Reg *l, int nup);
LUALIB_API void (luaL_openlibs) (lua_State *L); // open all previous libraries

压元素入栈(增)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* push functions (C -> stack) */
LUA_API void (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); // s字符串中间可任意包含'\0'或不以'\0'结尾
LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); // s字符串中间不可包含'\0'且必须以'\'结尾
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);

#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
#define lua_pushliteral(L, s) lua_pushstring(L, "" s)
#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) // ?

// 检查栈大小,调用参数很多的函数时可以检查一下栈空间是否还足够
LUA_API int (lua_checkstack)(lua_State *L, int n);

访问栈元素(查)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* access functions (stack -> C) */
// 判断L中序号为idx的元素能否转为函数所指的类型,及判断栈中的元素的类型
LUA_API int (lua_isnumber) (lua_State *L, int idx);
LUA_API int (lua_isstring) (lua_State *L, int idx);
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
LUA_API int (lua_isinteger) (lua_State *L, int idx);
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
LUA_API int (lua_type) (lua_State *L, int idx);
LUA_API const char *(lua_typename) (lua_State *L, int tp);

#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)

// 返回L中序号为idx的元素返回为函数指定类型的结果
LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_rawlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);

#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL)
#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL)

其他栈元素操作

1
2
3
4
5
6
7
LUA_API int   (lua_gettop) (lua_State *L);			// 获取栈顶元素的绝对引索值,即获取栈中元素的个数
LUA_API void (lua_settop) (lua_State *L, int idx); // 设置的位置比栈顶的位置大,则增长的元素值为nil,若比栈顶的位置小,则多余的元素被丢弃.lua_settop(L, 0)会清空栈L。
LUA_API void (lua_pushvalue) (lua_State *L, int idx); // 将idx上的栈元素的副本压入到栈顶
#define lua_pop(L,n) lua_settop(L, -(n)-1) // 栈弹出(清除)n个元素
#define lua_insert(L,idx) lua_rotate(L, (idx), 1) // idx上的元素到栈顶的元素的一个循环位移
#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) // 将栈顶到idx的元素一个循环位移,然后将栈顶元素弹出,相当于删除了原先idx的元素
#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) // 拷贝栈顶的元素到指定idx的元素上,然后将栈顶元素弹出

执行lua程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 编译用户输入的每行内容,并将编译后的程序块压入栈中,返回0表示没有错误。
LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode);
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)

/* 'load' and 'call' functions (load and run Lua code) */
// 将程序块从栈中弹出,并在保护模式下运行弹出的程序块,返回0表示没有错误,若发生错误,则向栈中也入一条错误信息,用lua_tostring可以获取这条信息,获取之后使用lua_pop将这条错误信息从栈中删除。
LUA_API void (lua_callk)(lua_State *L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k);
LUA_API int (lua_pcallk)(lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k);
#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL)
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)

LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip);

#define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_dostring(L, s) (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))

lua扩展应用程序

参数配置

lua中定义一些全局变量,应用程序调用api将lua中的全局变量值压到L栈中,再通过api从L栈中将该值读取出来,即到读取配置参数的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void luaStackDump(lua_State *L)
{
for (int i = lua_gettop(L); i > 0; i--)
{
int iType = lua_type(L, i);
switch (iType)
{
case LUA_TSTRING:
case LUA_TBOOLEAN:
case LUA_TNUMBER:
case LUA_TNIL:
{
cout << "(" << lua_typename(L, iType) << ")" << lua_tostring(L, i) << endl;
}
break;
default:
{
cout << "(" << lua_typename(L, iType) << ")" << endl;
}
break;
}
}
}

void error(lua_State *L, const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);

luaStackDump(L);
lua_close(L);

printf("app exit.");
exit(1);
}

void luaGetGlobalInt(lua_State *L, const string &strVarName, int &iRet)
{
lua_getglobal(L, strVarName.c_str()); // 先读取全局变量值到栈

if (!lua_isnumber(L, -1))
{
error(L, "'%s' should be a number\n", strVarName.c_str());
}

iRet = (int)lua_tointeger(L, -1); // 然后再从栈中读取元素的值,间接读取lua全局变量的值
lua_pop(L, 1);
cout << strVarName << " is " << iRet << endl;
}

void luaGetTableFiledValue(lua_State *L, const string &strTblName, const string &strFldName, string &strValue)
{
lua_getglobal(L, strTblName.c_str());

if (!lua_istable(L, -1))
{
error(L, "%s is not a table\n", strTblName.c_str());
}

lua_pushstring(L, strFldName.c_str());
lua_gettable(L, -2);
strValue = lua_tostring(L, -1);
lua_pop(L, 2);
cout << strTblName << "[" << strFldName << "] is " << strValue << endl;
}

void luaGetTableFiledValue(lua_State *L, const string &strTblName, const vector<string> &vctstrFldsName, vector<string> &vctstrValues)
{
lua_getglobal(L, strTblName.c_str());

if (!lua_istable(L, -1))
{
error(L, "%s is not a table\n", strTblName.c_str());
}

for (unsigned int i = 0; i < vctstrFldsName.size(); i++)
{
lua_pushstring(L, vctstrFldsName[i].c_str());
lua_gettable(L, -2);
vctstrValues.push_back(lua_tostring(L, -1));
lua_pop(L, 1);
}

lua_pop(L, 1);
}

C/C++程序调用lua脚本

lua中定义好函数后,C代码中使用api获取lua的函数对象压入到L栈中,然后再向L栈中按顺序压入函数参数,然后调用lua_pcall指定函数调用的参数个数和返回值个数,lua_pcall调用后,将会将之前压入的L栈的函数对象与参数弹出,然后将函数执行的返回值或函数执行失败的错误信息压入L栈中。C代码通过API从栈中将返回值或错误信息读取出来,然后需要C代码调用调用lua_pop里显示清理栈,恢复L栈至函数调用前状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
void luaCallLuaFunc(lua_State *L, const string &strFuncName, const vector<string> &vctstrParams, vector<string> &vctstrRets)
{
lua_getglobal(L, strFuncName.c_str());

for (unsigned int i = 0; i < vctstrParams.size(); i++)
{
lua_pushstring(L, vctstrParams[i].c_str());
}

if (lua_pcall(L, vctstrParams.size(), vctstrRets.size(), NULL) != 0)
{
error(L, "error running function getSize: %s\n", lua_tostring(L, -1));
}

for (int i = vctstrRets.size() - 1; i >= 0; i--)
{
vctstrRets[i] = lua_tostring(L, -1);
lua_pop(L, 1);
}

cout << "call " << strFuncName << "(";

if (vctstrParams.size() > 0)
{
cout << "\"" << vctstrParams[0] << "\"";

for (unsigned int i = 1; i < vctstrParams.size(); i++)
{
cout << ", \"" << vctstrParams[i] << "\"";
}
}

cout << ") returns (";

if (vctstrRets.size() > 0)
{
cout << "\"" << vctstrRets[0] << "\"";

for (unsigned int i = 1; i < vctstrRets.size(); i++)
{
cout << ", \"" << vctstrRets[i] << "\"";
}
}

cout << ")" << endl;
}

void luaCallLuaFunc2(lua_State *L, const char *cfunc, const char *cfrm, ...)
{
va_list vl;
int narg = 0;
int nres = 0;
va_start(vl, cfrm);

// 压入函数
lua_getglobal(L, cfunc);

// 压入参数
for (narg = 0; *cfrm != '\0'; narg++)
{
luaL_checkstack(L, 1, "too many arguments");
switch (*cfrm++)
{
case 'f':
{
lua_pushnumber(L, va_arg(vl, const double));
}
break;
case 'd':
{
lua_pushinteger(L, va_arg(vl, const int));
}
break;
case 's':
{
lua_pushstring(L, va_arg(vl, const char *));
}
break;
case '>':
{
goto endargs;
}
break;
default:
{
error(L, "invalid option (%c)", *(cfrm - 1));
}
break;
}
}

endargs:
// 调用函数
int irescnt = nres = strlen(cfrm); // 期望的结果数量

if (lua_pcall(L, narg, nres, 0) != 0)
{
error(L, "error calling %s:%s", cfunc, lua_tostring(L, -1));
}

// 获取返回值
nres = -nres; // 第一个返回结果的栈位置

while (*cfrm)
{
switch (*cfrm++)
{
case 'f':
{
if (!lua_isnumber(L, nres))
{
error(L, "wrong result type");
}

*va_arg(vl, double *) = lua_tonumber(L, nres);
}
break;
case 'd':
{
if (!lua_isinteger(L, nres))
{
error(L, "wrong result type");
}

*va_arg(vl, int *) = (int)lua_tointeger(L, nres);
}
break;
case 's':
{
if (!lua_isstring(L, nres))
{
error(L, "wrong result type");
}

*va_arg(vl, const char **) = lua_tostring(L, nres);
}
break;
default:
{
error(L, "invalid option (%c)", *(cfrm-1));
}
break;
}

nres++;
}

lua_pop(L, irescnt);
va_end(vl);
}

lua脚本调用C/C++

lua脚本调用C/C++函数

所有注册到lua中的函数都具有相同的类型,或者可以说只有一种特定类型的函数才能注册到lua中,
该函数类型lua.h中定义如下

1
typedef int (*lua_CFunction) (lua_State *L);	// Type for C functions registered with Lua

通过使用lua_pushcfunctionlua_setglobal两个方法就可以将一个函数注册到lua中,然后就可以在lua中通过使用lua_setglobal设置的globalname来调用到这个C函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void registCFuncToLua(lua_State *L, lua_CFunction pfunc, const string &strGlobalName)
{
lua_pushcfunction(L, pfunc);
lua_setglobal(L, strGlobalName.c_str());
}

// 示例代码
static int l_sin(lua_State *L)
{
double dbl = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(dbl));
return 1;
}

int main()
{
...
registCFuncToLua(L, l_sin, "c_sin");
// lua代码中就可以使用c_sin来调用到l_sin函数了
...
}

使用C/C++定义LUA模块

以下LUA调用C/C++的方法应该可以理解成require "libname"语句的实现原理吧,简单理解就是在将C/C++代码封装成动态库时,若需要生成的动态库可以被lua require后使用,就需要在该动态库中定义一个接口(一个数组+一个函数);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* lauxlib.h中luaL_Reg和luaL_register的定义

typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;

#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0))
*/

// 模块中函数的定义
static int l_f1(lua_State *L)
{
...
}

static int l_f2(lua_State *L)
{
...
}

...

// 指定被lua脚本require时需要被导出的函数
static const luaL_Reg mylib[] = {
{"f1", l_f1},
{"f2", l_f2},
...
{NULL, NULL} // 固定结尾
};

int luaopen_mylib(lua_State *L) // lua中`require "mylib"`时该函数会被注册与调用
{
luaL_register(L, "mylib", mylib); // 根据"mylib"名称创建(或复用)一个table,并用数组mylib中的元素填充这个table,并将这个table压入到L栈中
return 1; // 使lua脚本知道栈中已压入table
}

// lua文件中require "mylib"后,就可以通过f1、f2调用到l_f1、l_f2了,可见这也是一种动态库函数不需要被声明导出就可以被外部调用的方法,而且参数个数与类型可变,非常灵活,应该可以实现类似javascript的"函数重载"。