例五,与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;
- int newCtrl(lua_State *L)
- {
-
- TWinControl *Parent = NULL;
-
- 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());
-
-
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
- return 1;
- }
-
- int showCtrl(lua_State *L)
- {
-
- 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)
- {
-
- 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)
- {
-
- TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
- delete Ctrl;
- return 0;
- }
-
- 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'); "
- "VCL.pos(fm, 200, 200, 500, 300); "
- "edt = VCL.new(fm, 'Edit', 'Hello World'); "
- "VCL.pos(edt, 5, 5, 280, 28); "
- "btn = VCL.new(fm, 'Button', 'Haha'); "
- "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;
- int newCtrl(lua_State *L)
- {
-
- 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());
-
-
- PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
- *pCtrl = R;
-
- luaL_getmetatable(L, "My_VCL");
- lua_setmetatable(L, -2);
- return 1;
- }
-
- int showCtrl(lua_State *L)
- {
-
- 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)
- {
-
- 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)
- {
-
- TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
- delete Ctrl;
- return 0;
- }
-
- static const struct luaL_reg lib_VCL [] = {
- {"new", newCtrl},
- {"del", delCtrl},
- {"pos", posCtrl},
- {"show", showCtrl},
- {NULL, NULL}
- };
-
- int luaopen_VCL (lua_State *L) {
-
- luaL_newmetatable(L, "My_VCL");
-
-
- lua_pushvalue(L, -1);
- lua_setfield(L,-2,"__index");
-
-
- lua_pushcfunction(L, posCtrl);
- lua_setfield(L,-2,"pos");
-
-
- lua_pushcfunction(L, showCtrl);
- lua_setfield(L,-2,"show");
-
-
- 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:pos(200, 200, 500, 300); "
- "local edt = VCL.new(fm, 'Edit', 'Hello World'); "
- "edt:pos(5, 5, 280, 28); "
- "local btn = VCL.new(fm, 'Button', 'Haha'); "
- "btn:pos(100, 40, 150, 63); "
- "edt:show(); "
- "btn:show(); "
- "fm:show(); ";
-
-
- 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后,我们还得到了额外的好处:可以区分不同的
userdata以保证类型安全,我们把所以的
lua_touserdata改成了
luaL_checkudata。
关于
metatable的知识已超出本文讨论范围,请参考Lua官方手册。
阅读(1040) | 评论(0) | 转发(0) |