Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1216548
  • 博文数量: 129
  • 博客积分: 1449
  • 博客等级: 上尉
  • 技术积分: 3048
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-24 18:36
文章分类

全部博文(129)

文章存档

2015年(3)

2014年(20)

2013年(65)

2012年(41)

分类: C/C++

2013-01-28 19:16:24

原文地址:与Lua交换自定义数据 作者:wwm


例五,与Lua交换自定义数据

    由于Lua中的数据类型远不能满足C语言的需要,为此Lua提供了userdata,一个userdata提供了一个在Lua中没有预定义操作的raw内 存区域。
    在例四的函数库代码中我们已经使用过lightuserdata,它是userdata的一个特例:一个表示C指针的值(也就是一个void *类型的值)。
    下面的例子我们使用userdata来给Lua提供一个窗体类用于建立,显示窗体。为了简化窗体控制代码,在C函数中我们使用了C++Builder的 VCL库,所以下面的代码要在C++Builder下编译才能通过。当然,稍微修改一下也可以使用MFC,QT,wxWidget等来代替。
  1. //---------------------------------------------------------------------------
  2. #include 
  3. extern "C" {
  4. #include "lua.h"
  5. #include "lualib.h"
  6. #include "lauxlib.h"
  7. }
  8.  
  9. #include 
  10. #pragma hdrstop
  11. //---------------------------------------------------------------------------
  12. #pragma argsused
  13.  
  14. typedef TWinControl* PWinControl;
  15. //创建窗体,输入父窗体(或nil),类型,标题
  16. //输出创建后的窗体
  17. int newCtrl(lua_State *L)
  18. {
  19.     //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
  20.     TWinControl *Parent = NULL;
  21.     //从userdata中取得TWinControl*
  22.     if(lua_isuserdata(L,1))
  23.         Parent = *(PWinControl*)lua_touserdata(L,1);
  24.     String Type = UpperCase(luaL_checkstring(L, 2));
  25.     String Text = lua_tostring(L, 3);
  26.  
  27.     TWinControl *R = NULL;
  28.  
  29.     if(Type == "FORM")
  30.     {
  31.         R = new TForm(Application);
  32.     }
  33.     else if(Type == "BUTTON")
  34.     {
  35.         R = new TButton(Application);
  36.     }
  37.     else if(Type == "EDIT")
  38.     {
  39.         R = new TEdit(Application);
  40.     }
  41.     else
  42.     {
  43.         luaL_error(L, "unknow type!");
  44.     }
  45.  
  46.     if(Parent)
  47.         R->Parent = Parent;
  48.  
  49.     if(!Text.IsEmpty())
  50.         ::SetWindowText(R->Handle, Text.c_str());
  51.  
  52.     //新建userdata,大小为sizeof(PWinControl),用于存放上面生成的窗体指针
  53.     PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
  54.     *pCtrl = R;
  55.     return 1;
  56. }
  57.  
  58. //显示窗体
  59. int showCtrl(lua_State *L)
  60. {
  61.     //input: TWinControl*, for TForm, use ShowModal
  62.     TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
  63.     TForm *fm = dynamic_cast(Ctrl);
  64.     if(fm)
  65.         fm->ShowModal();
  66.     else
  67.         Ctrl->Show();
  68.     return 0;
  69. }
  70.  
  71. //定位窗体,输入窗体,左,上,右,下
  72. int posCtrl(lua_State *L)
  73. {
  74.     //input: TWinControl*, Left, Top, Right, Bottom
  75.     TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
  76.     Ctrl->BoundsRect = TRect(
  77.         luaL_checkint(L, 2),
  78.         luaL_checkint(L, 3),
  79.         luaL_checkint(L, 4),
  80.         luaL_checkint(L, 5));
  81.  
  82.     return 0;
  83. }
  84.  
  85. //删除窗体
  86. int delCtrl(lua_State *L)
  87. {
  88.     //input: TWinControl*
  89.     TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);
  90.     delete Ctrl;
  91.     return 0;
  92. }
  93.  
  94. //把这些函数作为VCL函数库提供给Lua
  95. static const struct luaL_reg lib_VCL [] = {
  96.     {"new", newCtrl},
  97.     {"del", delCtrl},
  98.     {"pos", posCtrl},
  99.     {"show", showCtrl},
  100.     {NULL, NULL}
  101. };
  102.  
  103. int luaopen_VCL (lua_State *L) {
  104.     luaL_register(L, "VCL", lib_VCL);
  105.     return 1;
  106. }
  107.  
  108. int main(int argc, char* argv[])
  109. {
  110.     char* szLua_code=
  111.         "fm = VCL.new(nil,'Form','Lua Demo'); "    //新建主窗体fm
  112.         "VCL.pos(fm, 200, 200, 500, 300); "        //定位
  113.         "edt = VCL.new(fm, 'Edit', 'Hello World'); "  //在fm上建立一个编辑框edt
  114.         "VCL.pos(edt, 5, 5, 280, 28); "
  115.         "btn = VCL.new(fm, 'Button', 'Haha'); "    //在fm上建立一个按钮btn
  116.         "VCL.pos(btn, 100, 40, 150, 63); "
  117.         "VCL.show(edt); "
  118.         "VCL.show(btn); "
  119.         "VCL.show(fm); "                       //显示
  120.         "VCL.del(fm);";                         //删除
  121.  
  122.     lua_State *L = luaL_newstate();
  123.     luaL_openlibs(L);
  124.     luaopen_VCL(L);
  125.  
  126.     bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  127.     if(err)
  128.     {
  129.         std::cerr << lua_tostring(L, -1);
  130.         lua_pop(L, 1);
  131.     }   
  132.  
  133.     lua_close(L);
  134.     return 0;
  135. }
  136. //---------------------------------------------------------------------------

使用metatable提供面向对象调用方式

    上面的VCL代码库为Lua提供了GUI的支持,但是看那些Lua代码,还处于面向过程时期。如何能把VCL.show(edt)之类的代码改成edt: show()这样的形式呢?还是先看代码:
  1. //---------------------------------------------------------------------------
  2. #include 
  3. extern "C" {
  4. #include "lua.h"
  5. #include "lualib.h"
  6. #include "lauxlib.h"
  7. }
  8.  
  9. #include 
  10. #pragma hdrstop
  11. //---------------------------------------------------------------------------
  12. #pragma argsused
  13.  
  14. typedef TWinControl* PWinControl;
  15. //创建窗体,输入父窗体(或nil),类型,标题
  16. //输出创建后的窗体
  17. int newCtrl(lua_State *L)
  18. {
  19.     //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)
  20.     TWinControl *Parent = NULL;
  21.  
  22.     if(lua_isuserdata(L,1))
  23.         Parent = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
  24.     String Type = UpperCase(luaL_checkstring(L, 2));
  25.     String Text = lua_tostring(L, 3);
  26.  
  27.     TWinControl *R = NULL;
  28.  
  29.     if(Type == "FORM")
  30.         R = new TForm(Application);
  31.     else if(Type == "BUTTON")
  32.         R = new TButton(Application);
  33.     else if(Type == "EDIT")
  34.         R = new TEdit(Application);
  35.     else
  36.         luaL_error(L, "unknow type!");
  37.  
  38.     if(Parent)
  39.         R->Parent = Parent;
  40.  
  41.     if(!Text.IsEmpty())
  42.         ::SetWindowText(R->Handle, Text.c_str());
  43.  
  44.     //output TWinControl*
  45.     PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));
  46.     *pCtrl = R;
  47.     //关联metatable
  48.     luaL_getmetatable(L, "My_VCL");
  49.     lua_setmetatable(L, -2);
  50.     return 1;
  51. }
  52.  
  53. //显示窗体
  54. int showCtrl(lua_State *L)
  55. {
  56.     //input: TWinControl*, for TForm, use ShowModal
  57.     TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
  58.     TForm *fm = dynamic_cast(Ctrl);
  59.     if(fm)
  60.         fm->ShowModal();
  61.     else
  62.         Ctrl->Show();
  63.     return 0;
  64. }
  65.  
  66. //定位窗体,输入窗体,左,上,右,下
  67. int posCtrl(lua_State *L)
  68. {
  69.     //input: TWinControl*, Left, Top, Right, Bottom
  70.     TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
  71.     Ctrl->BoundsRect = TRect(
  72.         luaL_checkint(L, 2),
  73.         luaL_checkint(L, 3),
  74.         luaL_checkint(L, 4),
  75.         luaL_checkint(L, 5));
  76.  
  77.     return 0;
  78. }
  79.  
  80. //删除窗体
  81. int delCtrl(lua_State *L)
  82. {
  83.     //input: TWinControl*
  84.     TWinControl* Ctrl = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");
  85.     delete Ctrl;
  86.     return 0;
  87. }
  88.  
  89. //把这些函数作为VCL函数库提供给Lua
  90. static const struct luaL_reg lib_VCL [] = {
  91.     {"new", newCtrl},
  92.     {"del", delCtrl},
  93.     {"pos", posCtrl},
  94.     {"show", showCtrl},
  95.     {NULL, NULL}
  96. };
  97.  
  98. int luaopen_VCL (lua_State *L) {
  99.     //建立metatable
  100.     luaL_newmetatable(L, "My_VCL");
  101.  
  102.     //查找索引,把它指向metatable自身(因为稍后我们会在metatable里加入一些成员)
  103.     lua_pushvalue(L, -1);
  104.     lua_setfield(L,-2,"__index");
  105.  
  106.     //pos方法
  107.     lua_pushcfunction(L, posCtrl);
  108.     lua_setfield(L,-2,"pos");
  109.  
  110.     //show方法
  111.     lua_pushcfunction(L, showCtrl);
  112.     lua_setfield(L,-2,"show");
  113.  
  114.     //析构,如果表里有__gc,Lua的垃圾回收机制会调用它。
  115.     lua_pushcfunction(L, delCtrl);
  116.     lua_setfield(L,-2,"__gc");
  117.  
  118.     luaL_register(L, "VCL", lib_VCL);
  119.     return 1;
  120. }
  121.  
  122. int main(int argc, char* argv[])
  123. {
  124.     char* szLua_code=
  125.         "local fm = VCL.new(nil,'Form','Lua Demo'); "    //新建主窗体fm
  126.         "fm:pos(200, 200, 500, 300); "        //定位
  127.         "local edt = VCL.new(fm, 'Edit', 'Hello World'); "  //在fm上建立一个编辑框edt
  128.         "edt:pos(5, 5, 280, 28); "
  129.         "local btn = VCL.new(fm, 'Button', 'Haha'); "    //在fm上建立一个按钮btn
  130.         "btn:pos(100, 40, 150, 63); "
  131.         "edt:show(); "
  132.         "btn:show(); "
  133.         "fm:show(); ";                       //显示
  134.         //"VCL.del(fm);";   //不再需要删除了,Lua的垃圾回收在回收userdata地会调用metatable.__gc。
  135.  
  136.     lua_State *L = luaL_newstate();
  137.     luaL_openlibs(L);
  138.     luaopen_VCL(L);
  139.  
  140.     bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  141.     if(err)
  142.     {
  143.         std::cerr << lua_tostring(L, -1);
  144.         lua_pop(L, 1);
  145.     }   
  146.  
  147.     lua_close(L);
  148.     return 0;
  149. }
  150. //---------------------------------------------------------------------------

我们这儿用到的辅助函数有:
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_VCLnewCtrl函数。
    在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官方手册。
阅读(1750) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~