Chinaunix首页 | 论坛 | 博客
  • 博客访问: 654238
  • 博文数量: 63
  • 博客积分: 1265
  • 博客等级: 中尉
  • 技术积分: 789
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-06 21:54
文章分类

全部博文(63)

文章存档

2017年(1)

2016年(3)

2015年(2)

2013年(5)

2012年(20)

2011年(32)

分类: C/C++

2016-05-16 20:10:34

上一讲我把Lua基本的栈规则讲了一下,然后完善了一下我的CLuaFn类。让它可以支持任意参数数量和函数名称的传值。当然,这些功能是为了今天这篇文章而铺路的。
看了七猫的回帖,呵呵,确实应该说一下SWIG这个工具,说真的,我对这个工具理解不深,因为没有怎么用过,读过一些关于它的文章,似乎是帮你把C++的功能封装成一个Lua基本库的东西,但是后来研究,他可以很轻松帮你把公用函数封装成一个Lua的基本库(类似C++的dll),但是对于我的需求而言,可能不太一样。因为我大量的是需要在C++里面进行数据传输以及变量的交互,所以为了紧贴C++,我需要很多关联数据的处理。
我是一名C++程序员,所以在很多时候,不想过多的使用Lua的特性,因为个人感觉,Lua的语法要比C++的更加灵活。而我更希望,在函数调用的某些习惯上,遵循一些C++的规则。
好了,废话少说,我们先来看一个类(头文件)。假设我们要把这个对象,传输给Lua进行调用。


  1. #ifndef _TEST_H  
  2.   
  3. #define _TEST_H  
  4.   
  5.   
  6.   
  7. class CTest  
  8.   
  9. {  
  10.   
  11. public:  
  12.   
  13.         CTest(void);  
  14.   
  15.         ~CTest(void);  
  16.   
  17.   
  18.   
  19.         char* GetData();  
  20.   
  21.         void SetData(const char* pData);  
  22.   
  23.   
  24.   
  25. private:  
  26.   
  27.         char m_szData[200];  
  28.   
  29. };  
  30.   
  31. #endif  


这个类里面有两个函数,一个是GetData(),一个是SetData(),之所以这么写,我要让Lua不仅能使用我的类,还可以给这个类使用参数。
那么,cpp文件,我们姑且这样写。(当然,你可以进行修改,按照你喜欢的方式写一个方法,呵呵)

  1. char* CTest::GetData()  
  2.   
  3. {  
  4.   
  5.         printf("[CTest::GetData]%s./n", m_szData);  
  6.   
  7.         return m_szData;  
  8.   
  9. }  
  10.   
  11.   
  12.   
  13. void CTest::SetData(const char* pData)  
  14.   
  15. {  
  16.   
  17.         sprintf(m_szData, "%s", pData);   
  18.   
  19. }  



这是一个标准的类,我需要这个类在Lua里面可以创造出来,并赋予数值,甚至我可以把CTest作为一个Lua函数参数,传给Lua函数让它去给我处理。让我们来看看怎么做。如果使用标准的Lua语法,有点多,所以我就借用一下上次提到的tolua来做到这一切,我一句句的解释。姑且我们把这些代码放在LuaFn.cpp里面。


  1. static int tolua_new_CTest(lua_State* pState)  
  2.   
  3. {  
  4.   
  5.         CTest* pTest = new CTest();  
  6.   
  7.         tolua_pushusertype(pState, pTest, "CTest");  
  8.   
  9.         return 1;  
  10.   
  11. }  
  12.   
  13.   
  14.   
  15. static int tolua_delete_CTest(lua_State* pState)  
  16.   
  17. {  
  18.   
  19.         CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);  
  20.   
  21.         if(NULL != pTest)  
  22.   
  23.         {  
  24.   
  25.                 delete pTest;  
  26.   
  27.         }  
  28.   
  29.         return 1;  
  30.   
  31. }  
  32.   
  33.   
  34.   
  35. static int tolua_SetData_CTest(lua_State* pState)  
  36.   
  37. {  
  38.   
  39.         CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);  
  40.   
  41.         const char* pData = tolua_tostring(pState, 2, 0);  
  42.   
  43.   
  44.   
  45.         if(pData != NULL && pTest != NULL)  
  46.   
  47.         {  
  48.   
  49.                 pTest->SetData(pData);  
  50.   
  51.         }  
  52.   
  53.   
  54.   
  55.         return 1;  
  56.   
  57. }  
  58.   
  59.   
  60.   
  61. static int tolua_GetData_CTest(lua_State* pState)  
  62.   
  63. {  
  64.   
  65.         CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);  
  66.   
  67.   
  68.   
  69.         if(pTest != NULL)  
  70.   
  71.         {  
  72.   
  73.                 char* pData = pTest->GetData();  
  74.   
  75.                 tolua_pushstring(pState, pData);  
  76.   
  77.         }  
  78.   
  79.   
  80.   
  81.         return 1;  
  82.   
  83. }  


看看这几个静态函数在干什么。
我要在Lua里面使用CTest,必须让Lua里这个CTest对象能够顺利的创造和销毁。tolua_new_CTest()和tolua_delete_CTest()就是干这个的。
tolua_pushusertype(pState, pTest, "CTest"); 这句话的意思是,将一个已经在Lua注册的"CTest"对象指针,压入数据栈。
同理,CTest* pTest = (CTest* )tolua_tousertype(pState, 1, 0);是将数据栈下的对象以(CTest* )的指针形式弹出来。
tolua_SetData_CTest()函数和tolua_GetData_CTest分别对应CTest的SetData方法和GetData()方法。因为我们的SetData方法里面存在变量,那么同样,我们需要使用const char* pData = tolua_tostring(pState, 2, 0);将参数弹出来,然后输入到pTest->SetData(pData);对象中去,当然,你可以有更多若干个参数。随你的喜好。这里只做一个举例。
好了,你一定会问,这么多的静态函数,用在哪里?呵呵,当然是给Lua注册,当你把这些数据注册到Lua里面,你就可以轻松的在Lua中使用它们。
让我们看看,注册是怎么做到的。
还是在CLuaFn类里面,我们增加一个函数。比如叫做bool InitClass();

  1. bool CLuaFn::InitClass()  
  2.   
  3. {  
  4.   
  5.         if(NULL == m_pState)  
  6.   
  7.         {  
  8.   
  9.                 printf("[CLuaFn::InitClass]m_pState is NULL./n");  
  10.   
  11.                 return false;  
  12.   
  13.         }  
  14.   
  15.   
  16.   
  17.         tolua_open(m_pState);  
  18.   
  19.         tolua_module(m_pState, NULL, 0);  
  20.   
  21.         tolua_beginmodule(m_pState, NULL);  
  22.   
  23.   
  24.   
  25.         tolua_usertype(m_pState, "CTest");  
  26.   
  27.         tolua_cclass(m_pState, "CTest""CTest""", tolua_delete_CTest);  
  28.   
  29.   
  30.   
  31.         tolua_beginmodule(m_pState, "CTest");  
  32.   
  33.         tolua_function(m_pState, "new", tolua_new_CTest);  
  34.   
  35.         tolua_function(m_pState, "SetData", tolua_SetData_CTest);  
  36.   
  37.         tolua_function(m_pState, "GetData", tolua_GetData_CTest);  
  38.   
  39.         tolua_endmodule(m_pState);  
  40.   
  41.   
  42.   
  43.         tolua_endmodule(m_pState);  
  44.   
  45.   
  46.   
  47.         return true;  
  48.   
  49. }  


上面的代码,就是我把上面的几个静态函数,绑定到Lua的基础对象中去。
tolua_beginmodule(m_pState, "CTest");是只注册一个模块,比如,我们管CTest叫做"CTest",保持和C++的名称一样。这样在Lua的对象库中就会多了一个CTest的对象描述,等同于string,number等等基本类型,同理,你也可以用同样的方法,注册你的MFC类。是不是有点明白了?这里要注意,tolua_beginmodule()和tolua_endmodule()对象必须成对出现,如果出现不成对的,你注册的C++类型将会失败。
tolua_function(m_pState, "SetData", tolua_SetData_CTest);指的是将Lua里面CTest对象的"SetData"绑定到你的tolua_SetData_CTest()函数中去。

好的,让我们来点激动人心的东西。还记得我们的Simple.lua的文件么。我们来改一下它。

  1. function func_Add(x, y)  
  2.   
  3.   local test = CTest:new();  
  4.   
  5.   test:SetData("I'm freeeyes!");  
  6.   
  7.   test:GetData();  
  8.   
  9.     
  10.   
  11.   return x..y;  
  12.   
  13. end  


我在这个函数里面,New了一个CTest对象,并进行赋值操作,最后把结果打印在屏幕上。你或许会问,最后一句不是x+y么,怎么变成了x..y,呵呵,在Lua中,..表示联合的意思,就好比在C++里面, string strName += "freeeyes"。原来觉得x+y有点土,索性返回一个两个字符串的联合吧。
好了,我们已经把我们的这个CTest类注册到了Lua里面,让我们来调用一下吧。修改一下Main函数。变成以下的样子。


  1. int _tmain(int argc, _TCHAR* argv[])  
  2.   
  3. {  
  4.   
  5.         CLuaFn LuaFn;  
  6.   
  7.   
  8.   
  9.         LuaFn.InitClass();  
  10.   
  11.   
  12.   
  13.         LuaFn.LoadLuaFile("Sample.lua");  
  14.   
  15.   
  16.   
  17.         CParamGroup ParamIn;  
  18.   
  19.         CParamGroup ParamOut;  
  20.   
  21.   
  22.   
  23.         char szData1[20] = {'/0'};  
  24.   
  25.         sprintf(szData1, "[freeeyes]");  
  26.   
  27.         _ParamData* pParam1 = new _ParamData(szData1, "string", (int)strlen(szData1));  
  28.   
  29.         ParamIn.Push(pParam1);  
  30.   
  31.   
  32.   
  33.         char szData2[20] = {'/0'};  
  34.   
  35.         sprintf(szData2, "[shiqiang]");  
  36.   
  37.         _ParamData* pParam2 = new _ParamData(szData2, "string", (int)strlen(szData2));  
  38.   
  39.         ParamIn.Push(pParam2);  
  40.   
  41.           
  42.   
  43.         char szData3[40] = {'/0'};  
  44.   
  45.         _ParamData* pParam3 = new _ParamData(szData3, "string", 40);  
  46.   
  47.         ParamOut.Push(pParam3);  
  48.   
  49.   
  50.   
  51.         LuaFn.CallFileFn("func_Add", ParamIn, ParamOut);  
  52.   
  53.   
  54.   
  55.         char* pData = (char* )ParamOut.GetParam(0)->GetParam();  
  56.   
  57.         printf("[Main]Sum = %s./n", pData);  
  58.   
  59.   
  60.   
  61.   
  62.   
  63.         getchar();  
  64.   
  65.   
  66.   
  67.         return 0;  
  68.   
  69. }  


如果你完全按照我的,你就可以编译你的工程了,运行一下,看看是啥结果?

[CTest::GetData]I'm freeeyes!.
[Main]Sum = [freeeyes][shiqiang].
看看,是不是和我输出的一样?

呵呵,有意思吧,你已经可以在Lua里面用C++的函数了,那么咱们再增加一点难度,比如,我有一个CTest对象,要作为一个参数,传输给func_Add()执行,怎么办?
很简单,如果你对上面的代码仔细阅读,你会发现下面的代码一样简洁。为了支持刚才要说的需求,我们需要把Sample.lua再做一点修改。

  1. function func_Add(x, y, f)  
  2.   
  3.   f:SetData("I'm freeeyes!");  
  4.   
  5.   f:GetData();  
  6.   
  7.   return x..y;  
  8.   
  9. end  


假设就是我们要传入的CTest对象。我们要在Lua里面使用它。(我们的CLuaFn都不用改,把main函数稍微改一下即可,来看看怎么写。)

// LuaSample.cpp : 定义控制台应用程序的入口点。
//

  1. #include "stdafx.h"  
  2.   
  3. #include "LuaFn.h"  
  4.   
  5.   
  6.   
  7. int _tmain(int argc, _TCHAR* argv[])  
  8.   
  9. {  
  10.   
  11.         CLuaFn LuaFn;  
  12.   
  13.   
  14.   
  15.         LuaFn.InitClass();  
  16.   
  17.   
  18.   
  19.         LuaFn.LoadLuaFile("Sample.lua");  
  20.   
  21.   
  22.   
  23.         CParamGroup ParamIn;  
  24.   
  25.         CParamGroup ParamOut;  
  26.   
  27.   
  28.   
  29.         char szData1[20] = {'/0'};  
  30.   
  31.         sprintf(szData1, "[freeeyes]");  
  32.   
  33.         _ParamData* pParam1 = new _ParamData(szData1, "string", (int)strlen(szData1));  
  34.   
  35.         ParamIn.Push(pParam1);  
  36.   
  37.   
  38.   
  39.         char szData2[20] = {'/0'};  
  40.   
  41.         sprintf(szData2, "[shiqiang]");  
  42.   
  43.         _ParamData* pParam2 = new _ParamData(szData2, "string", (int)strlen(szData2));  
  44.   
  45.         ParamIn.Push(pParam2);  
  46.   
  47.   
  48.   
  49.         //只追加了这里  
  50.   
  51.         CTest* pTest = new CTest();  
  52.   
  53.         _ParamData* pParam3 = new _ParamData(pTest, "CTest"sizeof(CTest));  
  54.   
  55.         ParamIn.Push(pParam3);  
  56.   
  57.        //追加结束  
  58.   
  59.           
  60.   
  61.         char szData4[40] = {'/0'};  
  62.   
  63.         _ParamData* pParam4 = new _ParamData(szData4, "string", 40);  
  64.   
  65.         ParamOut.Push(pParam4);  
  66.   
  67.   
  68.   
  69.         LuaFn.CallFileFn("func_Add", ParamIn, ParamOut);  
  70.   
  71.   
  72.   
  73.         char* pData = (char* )ParamOut.GetParam(0)->GetParam();  
  74.   
  75.         printf("[Main]Sum = %s./n", pData);  
  76.   
  77.   
  78.   
  79.   
  80.   
  81.         getchar();  
  82.   
  83.   
  84.   
  85.         return 0;  
  86.   
  87. }  


好了,就这么点代码,改好了,我们再Build一下,然后点击运行。看看输出结果,是不是和以前的一样?
恩,是不是有点兴奋了?你成功的让Lua开始调用你的C++对象了!并且按照你要的方式执行!还记得我曾在第一篇文章里面许诺过,我会让你画出一个MFC窗体么?呵呵,如果你到现在依然觉得很清晰的话,说明你的距离已经不远了。

既然已经到了这里,我们索性再加点难度,如果我要把CTest作为一个对象返回回来怎么做?很简单,且看。

  1. int _tmain(int argc, _TCHAR* argv[])  
  2.   
  3. {  
  4.   
  5.         CLuaFn LuaFn;  
  6.   
  7.   
  8.   
  9.         LuaFn.InitClass();  
  10.   
  11.   
  12.   
  13.         LuaFn.LoadLuaFile("Sample.lua");  
  14.   
  15.   
  16.   
  17.         CParamGroup ParamIn;  
  18.   
  19.         CParamGroup ParamOut;  
  20.   
  21.   
  22.   
  23.         char szData1[20] = {'/0'};  
  24.   
  25.         sprintf(szData1, "[freeeyes]");  
  26.   
  27.         _ParamData* pParam1 = new _ParamData(szData1, "string", (int)strlen(szData1));  
  28.   
  29.         ParamIn.Push(pParam1);  
  30.   
  31.   
  32.   
  33.         char szData2[20] = {'/0'};  
  34.   
  35.         sprintf(szData2, "[shiqiang]");  
  36.   
  37.         _ParamData* pParam2 = new _ParamData(szData2, "string", (int)strlen(szData2));  
  38.   
  39.         ParamIn.Push(pParam2);  
  40.   
  41.   
  42.   
  43.         CTest* pTest = new CTest();  
  44.   
  45.         _ParamData* pParam3 = new _ParamData(pTest, "CTest"sizeof(CTest));  
  46.   
  47.         ParamIn.Push(pParam3);  
  48.   
  49.           
  50.   
  51.         CTest* pTestRsult = NULL;  
  52.   
  53.         _ParamData* pParam4 = new _ParamData(pTestRsult, "CTest"sizeof(pTestRsult));  
  54.   
  55.         ParamOut.Push(pParam4);  
  56.   
  57.   
  58.   
  59.         LuaFn.CallFileFn("func_Add", ParamIn, ParamOut);  
  60.   
  61.   
  62.   
  63.         //接受Lua返回参数为CTest类型,并调用其中的方法。  
  64.   
  65.         pTestRsult = (CTest* )ParamOut.GetParam(0)->GetParam();  
  66.   
  67.         pTestRsult->GetData();  
  68.   
  69.   
  70.   
  71.         getchar();  
  72.   
  73.   
  74.   
  75.         return 0;  
  76.   
  77. }  


好,编译,执行。呵呵,看到了吧。

看到这里,如果你能看的明白,说明你已经对Lua如何调用C++接口,以及C++如何调用Lua有了一定的理解。当然,我写的这个类也不是很完善,不过做一半的Lua开发,应该是够用了。以以上的方式,你可以使用Lua驾驭你的C++代码。
好了,咱们既然已经说到这里了,再深一步,如果我的类是继承的,怎么办?呵呵,很好的问题。
比如,我的CTest继承了一个CBase,我的CBase又继承了一个。。。
在Lua里面,一样简单,我拿MFC的例子来举例吧,想必大家更喜欢看。
比如 CCmdTarget继承自CObject。
那么我在注册的时候可以这么写。

tolua_cclass(tolua_S, "CCmdTarget",      "CCmdTarget",      "CObject",            NULL);
这个表示CCmdTarget继承自CObject对象。
当然,MFC里面还会有很多类型,比如常数,Lua一样能处理。
举个例子说。

tolua_constant(tolua_S, "ES_AUTOHSCROLL",   ES_AUTOHSCROLL);
这样注册,你就可以在 Lua里面使用ES_AUTOHSCROLL这个常数,它会自动绑定ES_AUTOHSCROLL这个C++常数对象。

呵呵,说了这么多,让我们来点实际的。我给大家一个我以前写的MFC封装类(由于代码太多,我变成附件给大家),你们可以调用,当然,如果你有兴趣,就用我的MFC类,来做一个你喜欢的窗体吧,当然,你必须要用Lua脚本把它画出来,作为最后的考验,呵呵。


附带全部工程(附带Lua及tolua++)
HelloLua_01_03.rar
阅读(1442) | 评论(0) | 转发(0) |
0

上一篇:Lua脚本学习2

下一篇: libcurl 如何支持https

给主人留下些什么吧!~~