1.近来想研究tolua++,正好忙里抽闲,看些文章,并做了些总结。
一.环境设置2.下载lua5.1 并安装
3.下载tolua++
/Files/API/tolua-1.0.93.tar.bz2.zip (下载完成后,把.zip去掉)
4.新建工程lua++(生成静态库),将tolua++解压后的
tolua++-1.0.93\src\lib目录下的6个.c和.h文件。加
到该工程中。并设置附加包含目录:C:\Program Files (x86)\Lua\5.1\include
并编译成功后,生成lua++.lib5.打开tolua-1.0.93\tolua++-1.0.93\win32\vc7目录下的toluapp.sln,
设置附加包含目录:C:\Program Files (x86)\Lua\5.1\include
附加库目录:C:\Program Files (x86)\Lua\5.1\lib
加入附加依赖项:lua5.1.lib
最终生成到tolua-1.0.93\tolua++-1.0.93\bin\tolua++.exe
二.测试一(变量访问)
1.新建工程NewLua
2.Tarray.h如下:
#ifndef _TARRAY_H__
#define _TARRAY_H__
extern int g_Arr[10];
#endif/*_TARRAY_H__*/
3.编写pkg文件,内容如下:
$#include "tarray.h"
extern int g_Arr[10]@Arr;
4.输入命令:
>tolua++.exe -n tarray -o tarray.cpp tarray.pkg
-n tarray选项指定包的名字为tarray。如果不用-n显式指定,tolua++.exe会生成一个和pkg文件名一样的包名,同时生成tolua_**_open(lua_State*)入口函数。
5.将生成的tarray.cpp加入到该工程中。并且加入lua5.1.lib,lua++.lib
并附加包含lua的头文件目录
6.在main文件如下:
#include "stdafx.h"
#include "lua.hpp"
#include "tarray.h"
int tolua_tarray_open (lua_State* tolua_S);
int g_Arr[10]={0};
int _tmain(int argc, _TCHAR* argv[])
{
lua_State * L = lua_open();
int i=0;
for(i=0; i<10; i++) g_Arr[i] = i;
luaopen_base(L);
tolua_tarray_open(L);
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/array.lua");
printf("now in c++, re-show Arr:");
for(i=0; i<10; i++) printf("%d ", g_Arr[i]);
printf(" ");
lua_close(L);
return 0;
}
7.新建
array.lua文件如下:
print("now in lua script! try to print 'Arr' by name:")
print(Arr)
print("now print 'Arr':")
--print contents of Arr
for i=0,9 do print(Arr[i]) end
--change contents of Arr
print("now change the Arr.")
for i=0,9 do Arr[i] = i*2 end
8.打印结果如下:
now in lua script! try to print 'Arr' by name:
table: 008168E0
now print 'Arr':
0
1
2
3
4
5
6
7
8
9
now change the Arr.
now in c++, re-show Arr:0 2 4 6 8 10 12 14 16 18
二. 导出类
1.同样,新建tclass.h文件,内容如下:
#ifndef _TESTCLASS_H
#define _TESTCLASS_H
#include "stdafx.h"
#include <string.h>
class CNumber {
//tolua_export
public:
//tolua_begin
CNumber():m_nNum(0){ }
CNumber(int num):m_nNum(num){ }
~CNumber() { }
void SetNumber(int num) { m_nNum = num; }
int GetNumber() { return m_nNum; }
int Add(int num)
{
m_nNum += num;
return m_nNum;
}
//tolua_end
protected:
int m_nNum;
};
class CMessage
{
public:
CMessage()
{
strcpy(m_buff,"init message!");
}
~CMessage()
{
}
void SetMessage(char *msg)
{
strcpy(m_buff,msg);
}
char *GetMessage()
{
return m_buff;
}
void ShowMessage()
{
printf("%s\n",m_buff);
}
protected:
char m_buff[256];
};
//tolua_export //tolua_begin class
#endif
2.pkg文件内容如下:
$#include "tclass.h"
$#include "tarray.h"
extern int g_Arr[10]@Arr;
class CNumber {
public:
CNumber();
CNumber(int num);
~CNumber();
void SetNumber(int num);
int GetNumber();
int Add(int num);
};
class CMessage
{
public:
CMessage();
~CMessage();
void SetMessage(char *msg);
char *GetMessage();
void ShowMessage();
};
3. 在导出类的时候,构造函数被映射到lua中的new,析构函数被映射为delete。接下来,用tolua++.exe生成用于完成“导出类到lua"功能的cpp文件。如下:
tolua++.exe -n tarray -o tarray.cpp tarray.pkg
4.main.cpp代码如下:
#include "stdafx.h"
#include "lua.hpp"
#include "tarray.h"
int tolua_tarray_open (lua_State* tolua_S);
int g_Arr[10]={0};
int _tmain(int argc, _TCHAR* argv[])
{
lua_State * L = lua_open();
int i=0;
for(i=0; i<10; i++) g_Arr[i] = i;
luaopen_base(L);
tolua_tarray_open(L);
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/array.lua");
printf("now in c++, re-show Arr:");
for(i=0; i<10; i++) printf("%d ", g_Arr[i]);
printf(" \n\n");
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/classtest.lua");
lua_close(L);
return 0;
}
4.新建classtest.lua文件内容如下:
print("now in classtest.lua")
--new a CNumber Object
print("now allocate a CNumer object:")
num = CNumber:new()
num2=CNumber:new(222)
print("init,num:"..num:GetNumber())
print("init,num2:"..num2:GetNumber())
-- set number
print("now call SetNumber()")
num:SetNumber(6)
print("set number:"..num:GetNumber())
-- delete object
num:delete()
print("num is deleted,access GetNumber is invalid!")
print("invalidcall,result:"..num:GetNumber())
-- new a CMessage Object
msg=CMessage:new()
print("init msg:"..msg:GetMessage())
-- set message
print("msg:SetMessage('changed message')")
msg:SetMessage("changed message")
print("now msg:"..msg:GetMessage())
msg:ShowMessage()
msg:delete()
5.执行结果如下:
now in lua script! try to print 'Arr' by name:
table: 00157D48
now print 'Arr':
0
1
2
3
4
5
6
7
8
9
now change the Arr.
now in classtest.lua
now in c++, re-show Arr:0 2 4 6 8 10 12 14 16 18
now in classtest.lua
now allocate a CNumer object:
init,num:0
init,num2:222
now call SetNumber()
set number:6
num is deleted,access GetNumber is invalid!
invalidcall,result:-17891602
init msg:init message!
msg:SetMessage('changed message')
now msg:changed message
changed message
三. 导出聚合类
1. 聚合是最常见的构造新类的方式了,另一个是继承。tolua++支持单继承,后面会提到继承的例子。这里先看看怎么将利用了聚合的类导出到lua中。
我的目的是想在Lua中使用C++类的实例,而不是在lua中生成C++类实例,所以我在利用tolua++向lua导出类时一般不导出构造函数,这样就无法
在lua中生成类实例。 但是为了演示的方便,这个例子中用到的两个简单类CNumber和CMessage仍然导出了构造函数。
另外一个单件(singleton)CTestSystem的构造函数、拷贝构造函数、=操作符都被声明为protected,在向lua导出时只导出了 几个方法。你无法
在lua中生成它的实例,即便在C++中也不行,只能通过其静态成员函数GetSingleton()获取。
2. 新建classg.h如下:
#ifndef _CLASS_G_H__
#define _CLASS_G_H__
#include <string.h>
class CNumberEx
{
public:
CNumberEx():m_nNum(0){}
CNumberEx(int num):m_nNum(num){}
~CNumberEx(){}
void SetNumber(int num) {m_nNum = num;}
int GetNumber(){return m_nNum;}
int Add(int num){m_nNum += num;return m_nNum;}
protected:
int m_nNum;
};
class CMessageEx{
public:
CMessageEx(){strcpy(m_szMessage,"init message");}
CMessageEx(char *initMsg){if(initMsg) strcpy(m_szMessage,initMsg);}
~CMessageEx(){}
void SetMessage(char *initMsg){if(initMsg) strcpy(m_szMessage,initMsg);}
char *GetMessage(){return m_szMessage;}
void ShowMessage(){printf("msg:%s\n",m_szMessage);}
protected:
char m_szMessage[256];
};
class CTestSystem
{
public:
static CTestSystem &GetSingleton(){
static CTestSystem sys;
return sys;
}
CNumberEx& GetNumberObj(){return m_number;}
CMessageEx & GetMessageObj(){return m_message;}
protected:
CTestSystem(){}
CTestSystem(const CTestSystem &){}
~CTestSystem(){}
CTestSystem& operator=(const CTestSystem &rhs);
private:
CNumberEx m_number;
CMessageEx m_message;
};
#endif/*_CLASS_G_H__*/
3.在tarray.pkg如下
$#include "tclass.h"
$#include "tarray.h"
$#include "classg.h"
extern int g_Arr[10]@Arr;
class CNumber {
public:
CNumber();
CNumber(int num);
~CNumber();
void SetNumber(int num);
int GetNumber();
int Add(int num);
};
class CMessage
{
public:
CMessage();
~CMessage();
void SetMessage(char *msg);
char *GetMessage();
void ShowMessage();
};
class CNumberEx
{
public:
CNumberEx();
CNumberEx(int num);
~CNumberEx();
void SetNumber(int num);
int GetNumber();
int Add(int num);
};
class CMessageEx
{
public:
CMessageEx();
CMessageEx(char *initMsg);
~CMessageEx();
void SetMessage(char *initMsg);
char *GetMessage();
void ShowMessage();
};
class CTestSystem
{
public:
static CTestSystem &GetSingleton();
CNumberEx& GetNumberObj();
CMessageEx & GetMessageObj();
};
4.main如下:
#include "stdafx.h"
#include "lua.hpp"
#include "classg.h"
#include "tarray.h"
int tolua_tarray_open (lua_State* tolua_S);
int g_Arr[10]={0};
int _tmain(int argc, _TCHAR* argv[])
{
/*
在lua5.1中,用来生成lua状态对象的lua_open函数不再直接可用,替换为lua_newstate,
不过 lua_newstate要提供内存分配函数,lua扩展库提供了无参数的luaL_newstate,
用起来方便。同时为了向前兼容,还做了宏定 义#define lua_open luaL_newstate()。
所以你仍然可以用lua_open来或者lua_State,但是要注意这里只是个宏。
luaopen_base()打开基本的库。
tolua_classgroup_open是tolua++生成的函数,用来向lua导出你定义的类和其它变量及函数。
luaL_dofile也是宏定义,用来加载并执行一个脚本文件,在lauxlib.h中定义。
lua_close关闭之前打开的状态块。
*/
lua_State *L = luaL_newstate();
luaopen_base(L);
tolua_tarray_open(L);
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/classgroup.lua");
lua_close(L);
return 0;
}
5.同样执行命令(注意生成的tarray.cpp加入到工程编译)
>tolua++.exe -n tarray -o tarray.cpp tarray.pkg
6.lua脚本如下:
print("now in classgroup.lua!")
singleton = CTestSystem:GetSingleton()
print(singleton)
numobj = singleton:GetNumberObj()
print(numobj)
msgobj=singleton:GetMessageObj()
print(msgobj)
--access CNumber and CMessage
print("init obj's number:"..numobj:GetNumber())
numobj:SetNumber(100)
print("after call numobj:SetNumber(100),number:"..numobj:GetNumber())
print("init msgobj's message:"..msgobj:GetMessage())
msgobj:SetMessage("This is message is set in lua script")
print("new msg:"..msgobj:GetMessage())
msgobj:ShowMessage()
7.结果如下:
now in classgroup.lua!
userdata: 001C54D8
userdata: 001CD388
userdata: 001CD470
init obj's number:0
after call numobj:SetNumber(100),number:100
init msgobj's message:init message
new msg:This is message is set in lua script
msg:This is message is set in lua script
三. 导出单继承类
1. 新建
inheritance.h文件如下:
#ifndef _INHERITANCE_H__
#define _INHERITANCE_H__
#include <Windows.h>
#include <string>
typedef enum{
AUICSNormal = 0,
AUICSHover = 1,
AUICSPushed = 2,
AUICSDisabled = 3,
AUICSHide = 4,
AUICSFORCEDOWRD = 0xFFFFFFFF
}AUIControlState;
class CAUIControl
{
public:
CAUIControl():m_nID(-1),m_state(AUICSNormal),m_bEnable(true),m_strText(""){}
virtual ~CAUIControl(){}
public:
void SetID(int nID){m_nID = nID;}
int GetID(){return m_nID;}
void SetText(char *szText){m_strText = szText;}
const char* GetText(){return m_strText.c_str();}
void SetSize(SIZE sz){m_size = sz;}
SIZE GetSize(){return m_size;}
void SetEnabled(bool bEnable){m_bEnable = bEnable;}
bool IsEnabled(){return m_bEnable;}
void SetPosition(POINT pt){m_position = pt;}
POINT GetPosition(){return m_position;}
public:
virtual void Render() = 0;
virtual bool MsgProc(HWND hwnd,UINT uMsg,WPARAM wPram,LPARAM lParam){return false;}
protected:
int m_nID;
POINT m_position;
AUIControlState m_state;
std::string m_strText;
SIZE m_size;
bool m_bEnable;
};
class CAUIButton : public CAUIControl
{
public:
CAUIButton():m_pTexture(NULL){}
virtual ~CAUIButton(){}
public:
void SetTexture(char *szFile){}
void SetTextureRects(const RECT &rcNormal,const RECT &rcHover,const RECT &rcPushed,const RECT &rcDisabled){}
public:
void Render(){printf("CAUIButton::Render");}
bool MsgProc(HWND hwnd,UINT uMsg,WPARAM wPram,LPARAM lParam)
{
printf("CAUIButton::MsgProc ");
return false;
}
protected:
void *LoadTexture(char *szTextureFile){return NULL;}
void *m_pTexture;
RECT m_rects[4];
};
extern CAUIButton g_button;
#endif/*_INHERITANCE_H__*/
2.pkg文件如下:
$#include "inheritance.h"
class CAUIControl
{
public:
void SetID(int nID);
int GetID();
void SetText(char *szText);
const char* GetText();
void SetSize(SIZE sz);
SIZE GetSize();
void SetEnabled(bool bEnable);
bool IsEnabled();
void SetPosition(POINT pt);
POINT GetPosition();
};
class CAUIButton : public CAUIControl
{
public:
CAUIButton();
virtual ~CAUIButton();
public:
void SetTexture(char *szFile);
void SetTextureRects(const RECT &rcNormal,const RECT &rcHover,const RECT &rcPushed,const RECT &rcDisabled);
};
extern CAUIButton g_button@button;
3.main文件如下:
CAUIButton g_button;
int _tmain(int argc, _TCHAR* argv[])
{
lua_State *L = luaL_newstate();
luaopen_base(L);
tolua_tarray_open(L);
//luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/classgroup.lua");
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/inheritance.lua");
lua_close(L);
return 0;
}
4. lua脚本如下:
print("now in inheritance.lua!")
-- access global
button:SetID(100)
button:SetText("global button")
print(button:GetText())
newbutton = CAUIButton:new()
newbutton:SetID(100)
newbutton:SetText("new button")
print(newbutton:GetText())
5.输出结果:
now in inheritance.lua!
global button
new button
四.互调综合实例
1. 新建toluaclass.h如下:#ifndef _TOLUACLASS_H_
#define _TOLUACLASS_H_
class CBase
{
public:
CBase(){}
virtual ~CBase(){}
virtual void ShowMessage(){printf("BaseClass\n");}
static char *ClassName(){return "CBase\n";}
};
class CDerived1 : public CBase{
public:
CDerived1(){}
~CDerived1(){}
void ShowMessage(){printf("CDerived1Class\n");}
void ShowDerived1(){printf("show derived1\n");}
};
class CDerived2:public CBase{
public:
CDerived2(){m_nNumber = 0;}
~CDerived2(){}
void ShowMessage(){printf("Derived2Class\n");}
void ShowDerived2(){printf("show derived2\n");}
void SetNumber(int num){m_nNumber = num;}
int GetNumber(){return m_nNumber;}
protected:
int m_nNumber;
};
extern CDerived1 * toDerived1(void *p);
extern CDerived2 * toDerived2(void *p);
#endif/*_TOLUACLASS_H_*/
2.pkg文件如下:
$#include "toluaclass.h"
class CBase
{
public:
virtual void ShowMessage();
static char *ClassName();
};
class CDerived1 : public CBase{
public:
void ShowMessage();
void ShowDerived1();
};
class CDerived2:public CBase{
public:
void ShowMessage();
void ShowDerived2();
void SetNumber(int num);
int GetNumber();
};
extern CDerived1 * toDerived1(void *p);
extern CDerived2 * toDerived2(void *p);
$[
testHelper={}
function testHelper.toDerived3(e)
return tolua.cast(e,"CDerived2")
end
$]
tolua++提供了转换机制,tolua++生成了一些工具函数,在tolua为名的module中。其中tolua.cast就是用来做类型转换的。只需要改动tlclass.pkg文件
3. 驱动代码如下:
// NewLua.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "lua.hpp"
#include "classg.h"
#include "tarray.h"
#include "inheritance.h"
#include "toluaclass.h"
int tolua_tarray_open (lua_State* tolua_S);
CDerived1 *toDerived1(void *p)
{
return dynamic_cast<CDerived1*>((CBase*)p);
}
CDerived2 *toDerived2(void *p)
{
return dynamic_cast<CDerived2*>((CBase*)p);
}
int _tmain(int argc, _TCHAR* argv[])
{
lua_State *L = luaL_newstate();
luaopen_base(L);
tolua_tarray_open(L);
//luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/classgroup.lua");
luaL_dofile(L, "C:/Users/Administrator/Desktop/test/NewLua/NewLua/CallLuaFunc.lua");
CBase *p1 = new CDerived1();
CBase *p2 = new CDerived2();
//call Derived1Test
lua_getglobal(L,"Derived1Test");
lua_pushlightuserdata(L,p1);
if(lua_pcall(L,1,0,0)!=0)
{
fprintf(stderr,"Call Devied1Test failed:%s",lua_tostring(L,-1));
}
//call Derived2Test
lua_getglobal(L,"Derived2Test");
lua_pushlightuserdata(L,p2);
if(lua_pcall(L,1,0,0)!=0)
{
fprintf(stderr,"Derived2Test failed:%s",lua_tostring(L,-1));
}
//lua中转换测试
lua_getglobal(L,"Derived3Test");
lua_pushlightuserdata(L,p2);
if(lua_pcall(L,1,0,0)!=0)
{
fprintf(stderr,"Derived3Test failed:%s",lua_tostring(L,-1));
}
delete p1;
delete p2;
lua_close(L);
return 0;
}
在C++中调用lua脚本的函数大概分为三步:
a..找到函数并入栈;(这里是lua_getglobal(L, "Derived1Test");)
b..参数入栈;(这里是lua_pushlightuserdata(L, p1);)
c..调用lua_pcall进行实际调用
第一步不必说了;
第二步可以传递任意个任意类型的参数,lua_pushnumber,lua_pushstring, lua_pushboolean等 等可以调用;
第三步是调用lua_pcall,lua_pcall第一个参数是lua_State*,
这是我们的工作环境了。
第二参数是要传递的参数个 数,我们这里是1;
第三个参数是lua函数返回的结果个数,我们的lua函数不返回结果,设为0。
第四个参数是比较复杂,为0时指lua_pcall会在 调用失败时把原始错误信息放到栈上;
其它值代表栈的索引,该索引处放了一个错误处理函数,lua_pcall失败时会根据这个索引去调用该函数。 调用失败的时候我只是简单地打印一条出错信息,这个错误码放在栈顶,我们用lua_tostring(L,-1)访问并转换为字符串。可以修改下驱动代 码,比如把第二次调用传入p1,这样就可以看见错误信息。 最后我还是在C++代码中打印了下CDerived2对象的值,以验证lua和C++中访问的是同一个对象。 Lua和C++的交互都是通过栈,所以要写交互部分的代码就要不停的出栈入栈,烦死个人。不过这也是Lua灵活的地方。
4.lua脚本如下:
print ("now in CallLuaFunc.lua!")
-- lua function to test CDerived1, CDerived2, they'll be called from C++
function Derived1Test(e)
d1 = toDerived1(e)
if d1 then
d1:ShowMessage()
d1:ShowDerived1()
else
print("invalid d1(nil)")
end
end
function Derived2Test(e)
d2 = toDerived2(e)
if d2 then
d2:ShowMessage();
d2:ShowDerived2();
d2:SetNumber(180);
print(d2:GetNumber())
else
print("invalid d2(nil)")
end
end
-- 利用pkg中的tolua.cast转换
function Derived3Test(e)
d3 = testHelper.toDerived3(e)
if d3 then
d3:ShowMessage();
d3:ShowDerived2();
d3:SetNumber(380);
print(d3:GetNumber())
else
print("invalid d3(nil)")
end
end