您的位置:首页 > 编程语言 > Lua

使用tolua++实现C++与LUA相互调用

2017-10-23 17:16 585 查看
LUA是一种目前很流行的高效精简的脚本语言。LUA一个特点是比较方便的与C通讯。

然而要在脚本中使用C++类使用基本的LUA方法还是比较麻烦,纯手工暴露一个类的接口到LUA工作量还是很大的,而且都是一些简单的重复劳动。

好在有tolua++这个工具,可以让程序员从简单的重复劳动解脱出来。

tolua++包含两个部分,一个EXE,一个LIB,EXE用来通过package文件生成C函数的胶水函数,而LIB则用来为生成的胶水函数中用到的辅助函数提供实现。

 

在lua中要调用一个已有的C函数,大体上可以包含2步:

1、在C程序中实现一个胶水函数,该函数只有一个参数lua_State *。在这个胶水函数中,从lua的参数栈中逐个取出参数,再调用原有的C函数,最后将C函数的返回结果通过LUA的栈传回给LUA环境。

2、将该胶水函数注册到LUA的全局表中。

虽然这两个过程都很简单,但是当要暴露的是C++类大量的成员函数时,为每个函数编写胶水函数的工作可想而知。

有了tolua++,这个工作就很简单了,只需要做很少的工作就可以让tolua++自动为每一个待暴露的函数实现胶水函数。

如下面这样一个C++类,类的实现在一个命名空间TstNS中,类中包含有同一函数名的不同重载及自定义的结构体参数:

//file:export.h

 #pragma once

#include <string>

using namespace std;

namespace TstNS

{

struct RECT

{

int left,top,right,bottom;

};

class CExport

{

public:

CExport(void);

~CExport(void);

 

int foo(int a,int b);

int StrLen(const string & str);

int StrLen(const char *pstr);

int area(RECT rc);

i
4000
nt area(RECT *prc);

RECT setrect(int l,int t,int r,int b);

};

}

为了使用tolua++导出这个类到lua中,我们需要写一个package文件,在该文件中定义哪里函数需要导出,我这里的实现如下:

//file:export.pkg

 

$#include "export.h"

 

namespace TstNS

{

struct RECT

{

int left,top,right,bottom;

};

class CExport

{

CExport();

~CExport();

int foo(int a,int b);

int StrLen(const string & str);

int StrLen @ StrLen2(const char *pstr);

int area(RECT rc);

int area @ area2(RECT *prc); //将对area(void * prect)的调用重命名为area2

RECT setrect(int l,int t,int r,int b);

 

};

}

整 体上export.pkg中的内容基本与export.h文件类似,需要注意的是第一行:$#include "export.h"。tolua++根据这个文件来为其中的每个函数生成胶水函数于一个C文件中,由于胶水函数需要调用原来的函数,所以需要在生成的C 文件中需要包含export.h这个文件。第一行就是说在生成的C文件中插入一行#include “export.h"

第二个需要注意的地方在于函数的重载,LUA并不能根据参考类型自动选择调用哪一个重载函数,为此我们可以为不同的重载重命名。”@“正是为这一目的设计的。通过重命名,在LUA脚本中我们就可以显示的为不同的参数调用不同的重载函数。

我们需要使用tolua++的EXE来生成胶水函数代码。它是一个命令行程序,可以多个参数:

..\..\tolua++-1.0.93\bin\tolua++ -n export -o ..\lua_export.cpp export.pkg

这一行的意思是说调用tolua++.exe根据export.pkg生成一个..\lua_export.cpp的C++文件。-n export是说文件中一个需要被外面调用的C接口为:

int tolua_export_open (lua_State* tolua_S);

 

 

下一步,我们看一下在lua脚本中如何调用我们的导出的函数。

--file:test.lua

exp=TstNS.CExport:new();--首先我实例化一个CExport的全局对象。

 

function foo(a,b)

return exp:foo(a,b);

end

 

function StrLen(str)

return exp:StrLen(str);

end

 

function StrLen2(str)

return exp:StrLen2(str);

end

 

function area(rc)

return exp:area(rc);

end

 

function area2(prc)

--指针参数使用lua_call.hpp将以void*方式传入,需要做类型转换后才能调用C++中的代码。

return exp:area2(tolua.cast(prc,"TstNS::RECT"));

end

 

function setrect(l,t,r,b)

return exp:setrect(l,t,r,b);

end

 

在这个test.lua中我们实现了几个函数,在每一个函数中会调用CExport对应的接口。

到此lua调用C++的基本框架就已经实现了。

 

下面再看这个demo如何工作。

找到代码中的luatest目录,并打开luatest.sln这个VC工程,上面提到的代码在工程中都可以找到。

直接编译可以查看每一个函数是如何被调用的。

这里要推荐一个我改写的在C++中调用LUA函数的辅助模板类:lua_call.hpp

采用这个辅助类调用LUA函数就和调用普通的C函数一样简单。

下面是luatest.cpp中的代码:

 

// luatest.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include "Export.h"
#include "tolua++.h"
 
extern int  tolua_export_open (lua_State* tolua_S);
 
#include "lua_call.hpp"//使用模板技术对C++调用lua函数进行包装,使用方法参见_tmain
//对于自定义的数据类型,需要特化push_value,及value_extractor两个接口。
template<>
void lua_function_base::push_value(TstNS::RECT rc)
{
void* tolua_obj = Mtolua_new((TstNS::RECT)(rc));
tolua_pushusertype(m_vm,tolua_obj,"TstNS::RECT");
tolua_register_gc(m_vm,lua_gettop(m_vm));
}
 
template <>
TstNS::RECT lua_function_base::value_extractor()
{
TstNS::RECT * val = (TstNS::RECT *) tolua_tousertype(m_vm, -1,0);
lua_pop(m_vm, 1);
return *val;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
lua_State *L=lua_open();
int nret=0;
luaL_openlibs(L);

nret=tolua_export_open(L);
nret=luaL_dofile(L,"lua/test.lua");
 
{
lua_function<int> lua_area(L,"area");
TstNS::RECT rc={0,0,10,20};
int a1=lua_area(rc);
 
lua_function<int> lua_area2(L,"area2");
TstNS::RECT rc2={0,0,100,20};
int a2=lua_area2(&rc2);
 
lua_function<TstNS::RECT> lua_setarea(L,"setrect");
rc=lua_setarea(5,6,7,8);
 
lua_function<int> lus_strlen(L,"StrLen");
int nsize1=lus_strlen(std::string("abcdefghij"));
 
lua_function<int> lus_strlen2(L,"StrLen2");
int nsize2=lus_strlen("abcdefg");
 
lua_function<int> foo(L,"foo");
int sum=foo(5,6);
}
 
lua_close(L);
return 0;
}
 
 
在_main()中,首先当然是实例化lua环境。
调用tolua_export_open(L);注册tolua++实现的胶水函数到LUA。
调用luaL_dofile(L,"lua/test.lua");执行test.lua脚本。
 
调用lua脚本中实现的函数。
 
最后释放LUA环境:lua_close(L);

 

这其中lua脚本中实现的函数是比较取巧的。利用lua_call.hpp,我们只需要两行代码就可以实现:

第一步,定义一个lua_function对象,找到LUA中的函数。

第二步,为函数传入参数。

 

需要注意的是,当为CExport新增加一个需要导出的函数时,要记得将新的函数名加到export.pkg文件中,并调用tolua++重新生成胶水函数。在demo中有一个build.bat文件,每次更新后执行一次即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: