例五,与Lua交换自定义数据
由于Lua中的数据类型远不能满足C语言的需要,为此Lua提供了userdata,一个userdata提供了一个在Lua中没有预定义操作的raw内 存区域。在例四的函数库代码中我们已经使用过lightuserdata,它是userdata的一个特例:一个表示C指针的值(也就是一个void *类型的值)。
下面的例子我们使用userdata来给Lua提供一个窗体类用于建立,显示窗体。为了简化窗体控制代码,在C函数中我们使用了C++Builder的 VCL库,所以下面的代码要在C++Builder下编译才能通过。当然,稍微修改一下也可以使用MFC,QT,wxWidget等来代替。
- //---------------------------------------------------------------------------
- #include
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include
- #pragma hdrstop
- //---------------------------------------------------------------------------
- #pragma argsused
- typedef TWinControl* PWinControl;
- //创建窗体,输入父窗体(或nil),类型,标题
- //输出创建后的窗体
- int newCtrl(lua_State *L)
- {
- //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
- TWinControl *Parent = NULL;
- //从userdata中取得TWinControl*
- if(lua_isuserdata(L,1))
- Parent = *(PWinControl*)lua_touserdata(L,1);
- String Type = UpperCase(luaL_checkstring(L, 2));
- String Text = lua_tostring(L, 3);
- TWinControl *R = NULL;
- if(Type == "FORM")
- {
- R = new TForm(Application);
- }
- else if(Type == "BUTTON")
- {
- R = new TButton(Application);
- }
- else if(Type == "EDIT")
- {
- R = new TEdit(Application);
- }
- else
- {
- luaL_error(L, "unknow type!");
- }
- if(Parent)
- R->Parent = Parent;
- if(!Text.IsEmpty())
- ::SetWindowText(R->Handle, Text.c_str());
- //新建userdata,大小为sizeof(PWinControl),用于存放上面生成的窗体指针
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
- return 1;
- }
- //显示窗体
- int showCtrl(lua_State *L)
- {
- //input: TWinControl*, for TForm, use ShowModal
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- TForm *fm = dynamic_cast
(Ctrl); - if(fm)
- fm->ShowModal();
- else
- Ctrl->Show();
- return 0;
- }
- //定位窗体,输入窗体,左,上,右,下
- int posCtrl(lua_State *L)
- {
- //input: TWinControl*, Left, Top, Right, Bottom
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- Ctrl->BoundsRect = TRect(
- luaL_checkint(L, 2),
- luaL_checkint(L, 3),
- luaL_checkint(L, 4),
- luaL_checkint(L, 5));
- return 0;
- }
- //删除窗体
- int delCtrl(lua_State *L)
- {
- //input: TWinControl*
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- delete Ctrl;
- return 0;
- }
- //把这些函数作为VCL函数库提供给Lua
- static const struct luaL_reg lib_VCL [] = {
- {"new", newCtrl},
- {"del", delCtrl},
- {"pos", posCtrl},
- {"show", showCtrl},
- {NULL, NULL}
- };
- int luaopen_VCL (lua_State *L) {
- luaL_register(L, "VCL", lib_VCL);
- return 1;
- }
- int main(int argc, char* argv[])
- {
- char* szLua_code=
- "fm = VCL.new(nil,'Form','Lua Demo'); " //新建主窗体fm
- "VCL.pos(fm, 200, 200, 500, 300); " //定位
- "edt = VCL.new(fm, 'Edit', 'Hello World'); " //在fm上建立一个编辑框edt
- "VCL.pos(edt, 5, 5, 280, 28); "
- "btn = VCL.new(fm, 'Button', 'Haha'); " //在fm上建立一个按钮btn
- "VCL.pos(btn, 100, 40, 150, 63); "
- "VCL.show(edt); "
- "VCL.show(btn); "
- "VCL.show(fm); " //显示
- "VCL.del(fm);"; //删除
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- luaopen_VCL(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- std::cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
- //---------------------------------------------------------------------------
使用metatable提供面向对象调用方式
上面的VCL代码库为Lua提供了GUI的支持,但是看那些Lua代码,还处于面向过程时期。如何能把VCL.show(edt)之类的代码改成edt: show()这样的形式呢?还是先看代码:- //---------------------------------------------------------------------------
- #include
- extern "C" {
- #include "lua.h"
- #include "lualib.h"
- #include "lauxlib.h"
- }
- #include
- #pragma hdrstop
- //---------------------------------------------------------------------------
- #pragma argsused
- typedef TWinControl* PWinControl;
- //创建窗体,输入父窗体(或nil),类型,标题
- //输出创建后的窗体
- int newCtrl(lua_State *L)
- {
- //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
- TWinControl *Parent = NULL;
- if(lua_isuserdata(L,1))
- Parent = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- String Type = UpperCase(luaL_checkstring(L, 2));
- String Text = lua_tostring(L, 3);
- TWinControl *R = NULL;
- if(Type == "FORM")
- R = new TForm(Application);
- else if(Type == "BUTTON")
- R = new TButton(Application);
- else if(Type == "EDIT")
- R = new TEdit(Application);
- else
- luaL_error(L, "unknow type!");
- if(Parent)
- R->Parent = Parent;
- if(!Text.IsEmpty())
- ::SetWindowText(R->Handle, Text.c_str());
- //output TWinControl*
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
- //关联metatable
- luaL_getmetatable(L, "My_VCL");
- lua_setmetatable(L, -2);
- return 1;
- }
- //显示窗体
- int showCtrl(lua_State *L)
- {
- //input: TWinControl*, for TForm, use ShowModal
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- TForm *fm = dynamic_cast
(Ctrl); - if(fm)
- fm->ShowModal();
- else
- Ctrl->Show();
- return 0;
- }
- //定位窗体,输入窗体,左,上,右,下
- int posCtrl(lua_State *L)
- {
- //input: TWinControl*, Left, Top, Right, Bottom
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- Ctrl->BoundsRect = TRect(
- luaL_checkint(L, 2),
- luaL_checkint(L, 3),
- luaL_checkint(L, 4),
- luaL_checkint(L, 5));
- return 0;
- }
- //删除窗体
- int delCtrl(lua_State *L)
- {
- //input: TWinControl*
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- delete Ctrl;
- return 0;
- }
- //把这些函数作为VCL函数库提供给Lua
- static const struct luaL_reg lib_VCL [] = {
- {"new", newCtrl},
- {"del", delCtrl},
- {"pos", posCtrl},
- {"show", showCtrl},
- {NULL, NULL}
- };
- int luaopen_VCL (lua_State *L) {
- //建立metatable
- luaL_newmetatable(L, "My_VCL");
- //查找索引,把它指向metatable自身(因为稍后我们会在metatable里加入一些成员)
- lua_pushvalue(L, -1);
- lua_setfield(L,-2,"__index");
- //pos方法
- lua_pushcfunction(L, posCtrl);
- lua_setfield(L,-2,"pos");
- //show方法
- lua_pushcfunction(L, showCtrl);
- lua_setfield(L,-2,"show");
- //析构,如果表里有__gc,Lua的垃圾回收机制会调用它。
- lua_pushcfunction(L, delCtrl);
- lua_setfield(L,-2,"__gc");
- luaL_register(L, "VCL", lib_VCL);
- return 1;
- }
- int main(int argc, char* argv[])
- {
- char* szLua_code=
- "local fm = VCL.new(nil,'Form','Lua Demo'); " //新建主窗体fm
- "fm:pos(200, 200, 500, 300); " //定位
- "local edt = VCL.new(fm, 'Edit', 'Hello World'); " //在fm上建立一个编辑框edt
- "edt:pos(5, 5, 280, 28); "
- "local btn = VCL.new(fm, 'Button', 'Haha'); " //在fm上建立一个按钮btn
- "btn:pos(100, 40, 150, 63); "
- "edt:show(); "
- "btn:show(); "
- "fm:show(); "; //显示
- //"VCL.del(fm);"; //不再需要删除了,Lua的垃圾回收在回收userdata地会调用metatable.__gc。
- lua_State *L = luaL_newstate();
- luaL_openlibs(L);
- luaopen_VCL(L);
- bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
- if(err)
- {
- std::cerr << lua_tostring(L, -1);
- lua_pop(L, 1);
- }
- lua_close(L);
- return 0;
- }
- //---------------------------------------------------------------------------
我们这儿用到的辅助函数有:
int luaL_newmetatable (lua_State *L, const char *tname);
创建一个新表(用于metatable),将新表放到栈顶并在注册表中建立一个类型名与之联系。
void luaL_getmetatable (lua_State *L, const char *tname);
获取注册表中tname对应的metatable。
int lua_setmetatable (lua_State *L, int objindex);
把一个table弹出堆栈,并将其设为给定索引处的值的 metatable。
void *luaL_checkudata (lua_State *L, int index, const char *tname);
检查在栈中指定位置的对象是否为带有给定名字的metatable的userdata。
我们只改动了luaopen_VCL和newCtrl函数。
在luaopen_VCL里,我们建立了一个metatable,然后让它的__index成员指向自身,并加入了pos,show函数成员和__gc函 数成员。
在newCtrl里,我们把luaopen_VCL里建立的metatable和新建的userdata关联,于是:
- 对userdata的索引操作就会转向metatable.__index
- 因为metatable.__index是metatable自身,所以就在这个metatable里查找
- 这样,对userdata的pos、show索引转到metatable里的pos和show上,它们指向的是我们的C函数posCtrl和 posShow。
- 最后,当Lua回收这些userdata前,会调用metatable.__gc(如果有的话),我们已经把metatable.__gc指向了C函数 delCtrl。
关于metatable的知识已超出本文讨论范围,请参考Lua官方手册。