必须先下载lua 1.5,下载好后,把/src目录的除开lua.c,luac.c的文件全部放到一个新建的静态链接库的工程项目里,然后编译出一个静态链接库lua.lib。
去tolua++官网下载一个tolua++的包,解压出来,再次新建一个静态链接库的项目,把tolua++的/src/lib里的.h .c的文件拷贝到静态链接库的项目里,然后编译出一个tolua++.lib 的库。
再次新建一个控制台程序的项目,把编译好的lua静态库和tolua++的静态库放在项目里附加,然后把tolua++的/src/bin/里的tolua.c,luabind.h,luabind.c文件放在项目里,然后把tolua++的/include里的tolua++.h和lua1.5的目录里的/src的所有头文件放到项目里,然后编译生成tolua++.exe
#define FALSE 0
#define TRUE 1
enum {
POINT = 100,
LINE,
POLYGON
}
Object* createObejct (int type);
void drawObject (Object* obj, double red, double green, double blue);
int isSelected (Object* obj);
就会自动创建一个绑定上面代码到lua的C文件。因此,在lua代码里, 我们可以访问C代码。举个例子:
$pfile "XX.pkg" //$pfile是包含对应的pkg文件
$hfile "XXX.h" //$hfile是包含对应的.h头文件
$lfile "XXX.lua" //包含对应的.lua文件
生成命令 (tolua -h显示当前可接受的选项)
1)要解析一个名为test.pkg生成一个名为tolua_test.c的绑定代码,我们需要输入:
tolua -o test.c test.pkg
2)或生成一个不同的名字给pkg
tolua -n pkgname -o myfile.c myfile.pkg
调用流程展示
...
myLine = createObject(LINE) --
...
if isSelected(myLine) == TRUE then
drawObject(myLine, 1.0, 0.0, 0.0);
else
drawObject(myLine, 1.0, 1.0, 1.0);
end
...
#define FALSE 0
#define TRUE 1
class Shape
{
void draw (void);
void draw (double red, double green, double blue);
int isSelected (void);
};
class Line : public Shape
{
Line (double x1, double y1, double x2, double y2);
~Line (void);
};
如果tolua输入加载该文件,就会自动生成一个C++文件,例如tolua,从而为我们提供lua层访问C++层所需要的对应的代码。因此,以下的的lua代码是有效的:
...
myLine = Line:new (0,0,1,1) --new表示创建一个类的新对象,这个对象是myLine
...
if myLine:isSelected() == TRUE then --对象::对应函数表示调用对应函数
myLine:draw(1.0,0.0,0.0)
else
myLine:draw() --基类的函数也是直接调用就行
end
...
myLine:delete() --delete就是析构函数
...
(1)package还应当被明确初始化。为了从C/C++代码中初始化package,我们需要声明和调用初始化函数。初始化函数被定义为:
int tolua_pkgname_open (lua_State*); //这就是C++的函数,pkgname填写对应pkg的名字
(2)如果我们使用的是C++,我们可以选择自动初始化
tolua -a -n pkgname -o myfile.c myfile.pkg
不使用自动初始化是(下面的命令):
tolua -n pkgname -o tolua_myfile.c myfile.pkg //生成tolua_myfile.c文件
在这种情况下,初始化函数会被自动调用。然而,如果我们计划使用多个Lua State,自动初始化就行不通了,因为静态变量初始化的顺序在C++里没有定义。
库: lua.lib 和 tolua.lib
头文件:lua.h、lualib.h、lauxlib.h、luaconf.h
//main.cpp
#include
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}
using namespace std;
int main()
{
int tolua_mylib_open(lua_State*);
lua_State* state = luaL_newstate();
luaL_openlibs(state);
if (luaL_dostring(state, "print([[hello world]])") != 0)
{
cout << "excute lua file failed!" << endl;
}
lua_close(state);
system("pause");
return 0;
}
如果正确打印出 “hello world”,则说明 lua 环境没有问题,否则就检查一下头文件和库文件是否正确引入了
//.h文件
//mylib.h
class Test
{
public:
Test(int a, int b);
~Test();
void sayHello();
int add();
int getA();
private:
int a;
int b;
};
//.cpp文件
//mylib.cpp
#include "mylib.h"
#include
Test::Test(int a, int b)
{
this->a = a;
this->b = b;
}
Test::~Test()
{
}
void Test::sayHello()
{
std::cout << "hello world" << std::endl;
}
int Test::add()
{
return this->a + this->b;
}
int Test::getA()
{
return this->a;
}
编译指令:tolua -n mylib -o tolua.cpp mylib.pkg
$#include "mylib.h"
class Test
{
Test(int, int);
~Test();
void sayHello();
int add();
int getA();
};
//类名是Test
//垃圾回收
static int tolua_collect_Test (lua_State* tolua_S){}
//注册类名
static void tolua_reg_types (lua_State* tolua_S){}
//对应构造函数
static int tolua_mylib_Test_new00(lua_State* tolua_S){}
//对应析构函数
static int tolua_mylib_Test_delete00(lua_State* tolua_S){}
//对应sayHello函数
static int tolua_mylib_Test_sayHello00(lua_State* tolua_S){}
//对应add函数
static int tolua_mylib_Test_add00(lua_State* tolua_S){}
//getA
static int tolua_mylib_Test_getA00(lua_State* tolua_S){}
//luaopen_pkgname用于打开库文件
LUALIB_API int luaopen_mylib (lua_State* tolua_S){}
//luaopen_pkgname_open于打开 tolua,然后lua就可以使用C++的函数了
TOLUA_API int tolua_mylib_open (lua_State* tolua_S){}
local test = Test:new(1, 2)
test:sayHello()
print("a = " .. test:getA())
print("a + b = " .. test:add())
#include
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}
//Test类的源文件mylib.h
#include "mylib.h"
using namespace std;
int main()
{
lua_State* state = luaL_newstate(); //创建lua虚拟机
luaL_openlibs(state); //打开lua的库
tolua_mylib_open(state); //打开tolua的入口,让lua可以调用mylib的C++类接口
if (luaL_dofile(state, "scripts/test.lua") != 0)//加载test.lua文件
{
cout << "excute lua file failed!" << endl;
lua_close(state);
return 1;
}
lua_close(state);
system("pause");
return 0;
}
这种方式其实和在 c++ 程序中使用 tolua 没什么区别,只是我们测试的项目是 c++ 项目还是 lua 项目而已。要在纯 lua 程序中使用 tolua,首先得导出一个 c++ 模块。新建一个 c++ 动态链接库项目,同样地将 lua 和 tolua 的头文件和库文件包含进来,还有 mylib.h、mylib.cpp 和 tolua.cpp 这三个文件也添加进项目来。
导出 c++ 模块,在模块内注册一个函数,在这个函数里打开 tolua,然后 tolua 绑定的所有 c++ 函数就可以在 lua 中使用了。我们创建一个 lib.h 和 lib.cpp 文件,用于导出 c++ 模块
//lib.h
#pragma once
#include "lua.h"
extern "C"
{
__declspec(dllexport) int luaopen_test(lua_State* L);
}
//lib.cpp
#include "mylib.h"
extern "C"
{
#include "lib.h"
#include "lualib.h"
#include "lauxlib.h"
}
static int export_tolua_test(lua_State* L)
{
int tolua_mylib_open(lua_State* L);
tolua_mylib_open(L);
return 1;
}
static const luaL_Reg toluaTest[] =
{
{ "export_tolua_test",export_tolua_test },
{ NULL,NULL }
};
extern "C"
{
__declspec(dllexport) int luaopen_test(lua_State* L)
{
luaL_newlibtable(L, toluaTest);
luaL_setfuncs(L, toluaTest, 0);
return 1;
}
}
编译之后得到一个 lib.dll 文件,在 lua 代码中加载这个动态链接库文件,得到一个模块,这个模块里面有 export_tolua_test 这个函数,调用这个函数就会执行 tolua_mylib_open(L);,从而打开 tolua,则在 mylib 中导出的所有 c++ 东西都可以在 lua 中使用了
-- main.lua
-- 加载 c++ 模块
local test = require("test")
-- 调用 c++ 模块注册的函数,这个函数会打开 tolua
test.export_tolua_test()
-- 调用 tolua 导出的类、函数等
local t = Test:new(10, 20)
t:sayHello()
print(t:getA())
print(t:add())
使用 lua 解释器运行该代码,看到下面的执行结果
函数作用
1)传入 c++ 对象的 tolua++ 函数是 tolua_pushusertype。一般情况下,第一次使用这个函数将一个 c++ 对象 push 到 lua 堆栈上时,才会新建 userdata。tolua++ 会以 c++ 对象地址为键,userdata 为值,将键值对存储在 tolua_ubox 表里。下次推入同样的 c++ 对象时,从这个表里取出 userdata 推入堆栈即可
2)将c++对象指针作为key, 类型作为value存储于tolua_ubox表(当然这个表里还有userdata)。若key不存在直接插入即可;若存在则根据继承关系判断是否满足条件,若满足则不做任何处理直接返回此userdata对象,若不满足则修订value类型。
目的
构造一个给lua用的userdata对象
使用举例
//函数定义
void tolua_pushsertype(lua_State* L,void* value , const char* type)
//pRole是一个CRole的指针,指向一个CRole的对象
tolua_pushusertype(pScript->get_lua_state(),pRole,"CRole");
作用
压入nil类型数据
函数
void lua_pushnil(lua_State *L);
lua_pushnil(L);
dump_stack(L);
//输出
---- { dump_stack ----
nil
---- dump_stack } ----
void lua_setglobal( (lua_State *L, const char* name);
lua_setglobal(pScript->get_lua_state(),"role");
返回栈顶元素的索引,因为索引从1开始,所以这个结果也等于堆栈中元素的数量(0表示空的堆栈)
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
//可以封装为
#define lua_pushcfunction(L,f) lua_pushcclosure(L,(f),0)
void lua_getglobal (lua_State *L, const char *name);
作用
从全局变量中获取一个name参数描述的变量,放到栈顶
使用举例
name可以是一个lua函数(function)的名字,然后用lua_isfunction确保-1这个栈顶是位置的元素是函数
效果
将获得的变量,放置到栈顶
lua_pushnil(lua_State *L, xxx)
lua_pushstring(lua_State *L, xxx)
lua_pushnumber(lua_State *L, xxx)
lua_pushinteger(lua_State *L, xxx)
lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
参数说明
nargs指明了参数的个数
nresults指明了返回结果的个数
errfunc指明了发生错误的处理函数
注意
(注:调用lua_pcall之前,我们应该先:放置一个函数到栈中(可用:lua_getglobal),然后压入要传递的参数(可用:lua_push*))
对栈的操作
调用lua_pcall之后,它会首先将栈中的:函数,参数全部弹出 ; 然后:将结果一次压入栈中。
作用:
将index指明位置上的元素转换为对应类型返回
对栈的操作
不会改变栈的大小,内容.
void lua_settop (lua_State *L, int index);
作用
如果新传入的index比原来的栈顶大,那么超出的元素被填入nil. 可以传入0,那么栈将会被清空
对栈的操作
会修改栈的大小
int lua_load (lua_State *L,
lua_Reader reader,
void *data,
const char *chunkname,
const char *mode);
local f ,msg= load(cmd_func); //cmd_func是个字符串,msg是个返回信息,可以返回错误信息
f(); //执行lua函数
作用
加载一段 Lua 代码块,但不运行它。 如果没有错误, lua_load 把一个编译好的代码块作为一个 Lua 函数压到栈顶。 否则,压入错误消息。
返回值
lua_load 的返回值可以是:
LUA_OK: 没有错误;
LUA_ERRSYNTAX: 在预编译时碰到语法错误;
LUA_ERRMEM: 内存分配错误;
LUA_ERRGCMM: 在运行 __gc 元方法时出错了。 (这个错误和代码块加载过程无关,它是由垃圾收集器引发的。)
参数说明
1)使用一个用户提供的 reader 函数来读取代码块(参见 lua_Reader )。 data 参数会被传入 reader 函数。
源码
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
const char *chunkname, const char *mode) {
ZIO z;
int status;
lua_lock(L);
if (!chunkname) chunkname = "?";
luaZ_init(L, &z, reader, data);
status = luaD_protectedparser(L, &z, chunkname, mode);
if (status == LUA_OK) { /* no errors? */
LClosure *f = clLvalue(s2v(L->top - 1)); /* get newly created function */
if (f->nupvalues >= 1) { /* does it have an upvalue? */
/* get global table from registry */
const TValue *gt = getGtable(L);
/* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
setobj(L, f->upvals[0]->v, gt);
luaC_barrier(L, f->upvals[0], gt);
}
}
lua_unlock(L);
return status;
}
LUA_TNIL 0 空,代表什么也没有,可以与C的NULL类比,但它不是空指针.
LUA_TBOOLEAN 1 只有"true"和"false"两个值.
LUA_TLITHTUSERDATA 2 用户(非脚本用户)定义的C数据结构.脚本用户只能使用它,不能定义
LUA_TNUMBER 3 内部以double表示.
LUA_TSTRING 4 总是以零结尾,但可以包含任意字符(包括零),因此并不等价于C字符串,而是其超集.
LUA_TTABLE 5 异构的Hash表.Lua的关键概念之一.
LUA_TFUNCTION 6 Lua的关键概念之一.不简单等同于C的函数或函数指针.
LUA_TUSERDATA 7 用户(非脚本用户)定义的C数据结构.脚本用户只能使用它,不能定义.
LUA_TTHREAD 8 Lua协作线程(coroutine),与一般操作系统的抢占式线程不一样.
LUA_NUMTAGS 9
1)保证栈顶是个table
2)压栈一个键值对的key值,比如
lua_pushnumber(L, 1); --或字符串
3)table一开始是在栈顶,即-1处的,但上面的语句压入了一个值,栈顶变-2了。
4)lua_gettable的作用就是以栈顶的值作为key来访问-2位置上的table。
lua_gettable(L, -2);
5)这时table中的第1个元素的值就放到栈顶了
6)补充:对于字符串索引
可以用lua_getfield(Lua_state,index,key)来直接获取,
如:lua_getfield(stack, -1, "loaded");等价于 lua_pushstring(L,"loaded") lua_gettable(L,-2);
int size = lua_objlen(L,-1);//相关于#table
for(int i = 1; i <= size; i++)
{
lua_pushnumber(L, i);
lua_gettable(L, -2);
//这时table[i]的值在栈顶了
lua_pop(L, 1);//把栈顶的值移出栈,保证栈顶是table以便遍历。
};
2)如果table中的key是任意值呢?可以用下面的方法:
lua_pushnill(L);
while(lua_next(L, -2))
{
//这时值在-1(栈顶)处,key在-2处。
lua_pop(L, 1);//把栈顶的值移出栈,让key成为栈顶以便继续遍历
}
void lua_settable (lua_State *L, int index);