使用metatable提供面向对象调用方式
上面的VCL代码库为Lua提供了GUI的支持,但是看那些Lua代码,还处于面向过程时期。如何能把VCL.show(edt)之类的代码改成edt: show()这样的形式呢?还是先看代码:
-
-
#include <vcl.h>
-
extern "C" {
-
#include "lua.h"
-
#include "lualib.h"
-
#include "lauxlib.h"
-
}
-
-
#include <iostream>
-
#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<TForm*>(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 *<span class="KSFIND_CLASS" id="4KSFindDIV" style="text-align: center; -webkit-box-shadow: rgba(0, 0, 0, 0.74902) 0px 1px 3px; bottom: 0px !important; left: 0px !important; right: 0px !important; top: 0px !important; height: inherit !important; width: inherit !important; float: none !important; color: black !important; padding: 2px 0px 1px !important; display: inline !important; position: relative !important; z-index: 3000000 !important; background: none 0px 0px repeat scroll rgb(255, 244, 151) !important;">luaL_checkudata</span> (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官方手册。