C++调用lua
本文转自: http://www.cnblogs.com/osyun/archive/2012/02/01/2334192.html
本节我将一步一步带领大家完成c++调用lua函数并接受lua的返回值,通过分析调用的方式来封装一个类,最终封装完成的类并不是最优的,但应该能够满足一般的项目中对lua调用的功能。不足之处欢迎大家给予指正。
1 基本概念
1.1 栈
c++调用lua是通过一个抽象的栈来实现数据的交换的。C++调用lua时,首先需要把lua函数需要的参数压入这个抽象的栈中,如果c++想要从lua中获取数据,则lua需要先把数据压入栈中,然后c++从栈中取得需要的数据。Lua是以严格的LIFO规则来操作栈的,即后进先出原则,而c++则可以操作栈上的任何一个元素。
2 常用函数
- void lua_pushnil (lua_State *L);
往栈中压入空值
- void lua_pushboolean (lua_State *L, int bool);
往栈中压入布尔型值
- void lua_pushnumber (lua_State *L, double n);
往栈中压入double型数值
- void lua_pushlstring (lua_State *L, const char *s, size_t length);
往栈中压入字符串,但是该字符串中可以包含'\0',字符串的长度为length
- void lua_pushstring (lua_State *L, const char *s);
往栈中压入c风格的字符串,以'\0'结尾
- int lua_is... (lua_State *L, int index);
用来检查栈上的一个元素是否指定的类型
- int lua_type (lua_State *L, int index);
返回栈中元素的类型
在lua.h中定义了各个元素对应的常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD
- int lua_toboolean (lua_State *L, int index);
将元素值转换为布尔型
- double lua_tonumber (lua_State *L, int index);
将元素值转换为数值型
- const char * lua_tostring (lua_State *L, int index);
该函数返回一个指向lua栈内元素的指针,该指针是不允许修改的
切记这里的指针是指向栈上元素的,如果lua清空了栈内元素,则该指针就无效了。
- int lua_gettop (lua_State *L);
返回堆栈中的元素个数,它也是栈顶元素的索引
- void lua_settop (lua_State *L, int index);
设置栈顶为一个指定的值
如果原栈顶大于新栈顶,则顶部的值被丢弃,如果原栈顶小于新栈顶,则将多出的元素赋nil。lua_settop(L,0)清空堆栈。
你也可以用负数索引作为调用lua_settop的参数;那将会设置栈顶到指定的索引。利用这种技巧,API提供了下面这个宏,它从堆栈中弹出n个元素:
#define lua_pop(L,n) lua_settop(L, -(n)-1)
Ø void lua_pushvalue (lua_State *L, int index);
将指定元素的一个备份拷贝到栈顶
Ø void lua_replace (lua_State *L, int index);
移除指定元素,其上的其它元素依次下移
Ø void lua_insert (lua_State *L, int index);
将栈顶的元素移动到指定的索引处,其它元素依次上移
Ø void lua_replace (lua_State *L, int index);
将栈顶的值替换指定索引的元素,其它元素不变
3 一个简单的例子
该例子最终是通过c++代码调用lua脚本实现加法的操作并把返回值打印到控制台上。以下是代码:
test.cpp
#include <iostream> #include "lua.hpp" using namespace std; int main() { int iRet = 0; double iValue = 0; lua_State * L = luaL_newstate();// 创建lua状态 if (NULL == L) { cout << "luaL_newstate()没有足够的内存分配\n" << endl; return 0; } iRet = luaL_dofile(L, "test.lua");// 加载并运行lua脚本 if (0 != iRet) { cout << "luaL_dofile() failed\n" << endl; return 0; } lua_getglobal(L, "add"); // 把函数名压入堆栈 lua_pushnumber(L, 2); lua_pushnumber(L, 3); iRet = lua_pcall(L, 2, 1, 0); // 调用函数 if (0 != iRet) { printf("lua_pcall failed:%s\n",lua_tostring(L,-1)); return 0; } if (lua_isnumber(L, -1) == 1) { iValue = lua_tonumber(L, -1); } cout << iValue << endl; lua_close(L); return 0; }
test.lua
function add (x,y) return x+y end
编译:g++ -o test test.cpp -ltolua++
运行结果:5
以下是对该例子中用到的函数的解释:
- lua_State *luaL_newstate (void);
该函数用于创建一个新的lua状态,当内存分配错误的时候返回NULL
- int luaL_loadfile (lua_State *L, const char *filename);
该函数加载lua文件,但不运行。
- int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);
该函数用于调用lua的函数,nargs是入参个数,nresults是出参个数,如果errfunc为0,表示把出错的信息放入堆栈中,如果不为0,则该值代表一个错误处理函数在lua堆栈中的索引。函数成功时返回0,错误时返回如下三个值:
LUA_ERRRUN:运行时错误;
LUA_ERRMEM:内存分配错误。对于这样的错误,Lua不调用错误处理函数;
LUA_ERRERR:运行错误处理函数的错误。
- int luaL_dofile (lua_State *L, const char *filename);
加载并运行指定的文件,正确的时候返回0,错误的时候返回1。该函数在lua中被定义为一个宏:
(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))
- void lua_getglobal (lua_State *L, const char *name);
将函数名压入到堆栈中,它被定义为如下宏:
#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s)
- void lua_close (lua_State *L);
该函数销毁lua状态的所有对象,如果是守护进程或web服务器,则需要在使用完后尽快释放,以避免过大。
如果想直接验证lua脚本可以按如下步骤操作:
/home/tolua/test#lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> dofile("test.lua")
> print (add(1,4))
5
4 封装
让我们仔细研究下这个简单的例子,是不是发现了很多共同点呢?我们完全可以利用 c++的特性对其进行封装,这样我们再调用lua的时候,就可以直接调用我们封装好的类啦。
CToLua.h
#ifndef CTOLUA_H_ #define CTOLUA_H_ #include "lua.hpp" #include <iostream> using namespace std; class CToLua { public: CToLua(); ~CToLua(); bool loadLuaFile(const char* pFileName); //加载指定的Lua文件 double callFileFn(const char* pFunctionName, const char* format, ...); //执行指定Lua文件中的函数 lua_State* getState(); private: int parseParameter(const char* format, va_list& arg_ptr); lua_State* m_pState; }; #endif // CTOLUA_H_
#include "CToLua.h" extern "C" { #include "tolua++.h" } CToLua::CToLua() { m_pState = luaL_newstate(); if (NULL == m_pState) { printf("luaL_newstate()没有足够的内存分配\n"); } else { luaopen_base(m_pState); } } CToLua::~CToLua() { if (NULL != m_pState) { lua_close(m_pState); m_pState = NULL; } } bool CToLua::loadLuaFile(const char* pFileName) { int iRet = 0; if (NULL == m_pState) { printf("[CToLua::LoadLuaFile]m_pState is NULL.\n"); return false; } iRet = luaL_dofile(m_pState, pFileName); if (iRet != 0) { printf("[CToLua::LoadLuaFile]luaL_dofile(%s) is error(%d)(%s).\n", pFileName, iRet, lua_tostring(m_pState, -1)); lua_pop(m_pState, 1); // 及时清理堆栈 return false; } return true; } double CToLua::callFileFn(const char* pFunctionName, const char* format, ...) { int iRet = 0; double iValue = 0; int iTop = lua_gettop(m_pState); lua_pop(m_pState, iTop); // 清栈 if(NULL == m_pState) { printf("[CLuaFn::CallFileFn]m_pState is NULL.\n"); return 0; } lua_getglobal(m_pState, pFunctionName); va_list arg_ptr; va_start(arg_ptr, format); iRet = parseParameter(format, arg_ptr); va_end(arg_ptr); iRet = lua_pcall(m_pState, iRet, 1, 0); if (iRet != 0) { printf("[CLuaFn::CallFileFn]call function(%s) error(%d).\n", pFunctionName, iRet); return 0; } if (lua_isnumber(m_pState, -1) == 1) { iValue = lua_tonumber(m_pState, -1); } return iValue; } int CToLua::parseParameter(const char* format, va_list& arg_ptr) { int iRet = 0; char* pFormat = (char*) format; while (*pFormat != '\0') { if ('%' == *pFormat) { ++pFormat; switch (*pFormat) { case 'f': lua_pushnumber(m_pState, va_arg( arg_ptr, double)); break; case 'd': case 'i': lua_pushnumber(m_pState, va_arg( arg_ptr, int)); break; case 's': lua_pushstring(m_pState, va_arg( arg_ptr, char*)); break; case 'z': lua_pushlightuserdata(m_pState, va_arg( arg_ptr, void*)); break; default: break; } ++iRet; } ++pFormat; } return iRet; } lua_State* CToLua::getState() { return m_pState; }
main.cpp
#include "CToLua.h" int main() { CToLua tolua; tolua.loadLuaFile("C:\\c++\\lua\\test.lua"); double iValue = tolua.callFileFn("add", "%d%f", 20, 3.69); // 23.69 cout << iValue << endl; iValue = tolua.callFileFn("sub", "%f%i", 23.69,20); // 3.69 cout << iValue << endl; return 0; }
test.lua
function add (x,y) return x+y end function sub (x,y) return x-y end
callFileFn方法使用类似printf,%d或%i代表整数,%f代表浮点数,%s代表字符串,%z代表自定义类型。
由于lua可以支持多返回值,所以原先的设计思想是返回一个vector,里边包含一个结构体,有两个成员,一个是string type,一个是void* pValue。但当我实现了后,发现虽然支持了多个返回值了,但是使用起来却不是那么方便。所以索性就不实现这种返回多个返回值的接口了。函数的返回值固定,只返回double类型的值,如果想要多个返回值,可以传自定义参数来实现。
在这里推荐一下linux下的eclipse c++开发环境,本次代码编写就是在该环境下进行的,使用起来还算比较舒服。搭建方式参考http://www.cnblogs.com/osyun/archive/2011/12/05/2276412.html