暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

C++与Lua交互 一套完善的框架

林元皓 2021-10-21
1343

工程介绍

通过《C++ 与 Lua 交互 使用模板特化封装》,已经对 C++ 与 Lua 交互有了了解。

现在的这套 C++ 与 Lua 相互调用的框架,是之前单位项目中提取出来的,在已上线的端游中使用。

这里使用 lua 5.1.5、tolua++ 1.0.92、boost 1.68.0、zlib。

开发环境:vs 2017 、lua 调试插件 BabeLua。

弊端就是,使用的是 Lua 5.1 老版本,由于 tolua++ 的原因 ...

先贴图,介绍一下工程目录。


lua_project_framework


lua 工程;lua代码 —— 编译lua.libtolua 工程:tolua++代码 —— 编译 tolua.lib 和 tolua.exeLuaProject 工程:lua和c++相互交互工程。LuaScript 工程:插件BabeLua 创建,lua脚本编写、调试使用。

lua 工程

lua 工程,是 lua 静态链接库工程。

已经在《C++ 与 Lua 交互 使用模板特化封装》中,讲过了,这里略过。

tolua 工程

创建空工程、添加tolua代码文件。

这里注意的是 tolua\src 目录下有 bin 和 lib 路径代码。分别控制编译 exe 和 lib文件。

我这里为了方便,将bin和lib代码同时加入tolua工程。

然后控制 配置类型来生成 应用程序(.exe) 和 静态库(.lib)

修改工程配置

附加包含目录:..\include;....\lua\src;

附加依赖项:lua.lib

预处理器定义:_CRT_SECURE_NO_WARNINGS

编译生成 tolua.lib 和 tolua.exe

LuaProject 工程

看一下工程代码如图:


lua_project_code


还挺多,不要慌,这里主要实现的读取的文件可以是zip文件(压缩以后的lua文件)。

主要类也就是 ScriptVmArgs类 和 ScriptVM类 来实现的 lua与c++ 的交互。而且读取文件也提供了一个已经剥离的zip文件的函数。

之前使用的是模板封装,这里使用的是递归+元组封装。

这里贴出代码如下:

ScriptVmArgs.h

/*************************************************
vic.MINg 2018/09/18
************************************************/


#pragma once
#include <string>
// "boost/config/user.hpp" 取消 BOOST_ALL_NO_LIB 注释
#define BOOST_ALL_NO_LIB
#include "boost/tuple/tuple.hpp"






struct lua_State;


class ScriptVmArgs
{
friend class ScriptVM;


class Args
{
public:
virtual int Size()const = 0;
};


class ArgsInput
:public Args
{
public:
virtual void Push(lua_State& lua)const = 0;
};


class ArgsOutput
:public Args
{
public:
virtual void Pop(lua_State& lua) = 0;
};


template<typename T>
class ArgsInputImp
:public ArgsInput
{
public:
ArgsInputImp(const T& args) :m_args(args) {}
virtual void Push(lua_State& lua)const
{
Push(lua, m_args);
}
virtual int Size()const
{
return boost::tuples::length<T>::value;
}


private:
static void Push(lua_State& lua, const boost::tuples::null_type& args) {}
template<typename Head, typename Tail>
static void Push(lua_State& lua, const boost::tuples::cons<Head, Tail>& args)
{
// 这里巧妙的使用了 boost::tuple 的 args.get_head() 和 args.get_tail() 方法,两行代码实现了 自身的递归调用。
DoPush(lua, args.get_head());
Push(lua, args.get_tail());
}


const T& m_args;
};


template<typename T>
class ArgsOutputImp
:public ArgsOutput
{
public:
ArgsOutputImp(T& args) :m_args(args) {}
virtual void Pop(lua_State& lua)
{
Pop(lua, m_args, 0);
}
virtual int Size()const
{
return boost::tuples::length<T>::value;
}


private:
static void Pop(lua_State& lua, const boost::tuples::null_type& args, int idx) {}
template<typename Head, typename Tail>
static void Pop(lua_State& lua, const boost::tuples::cons<Head, Tail>& args, int idx)
{
//if (args.get_head())
DoPop(lua, args.get_head(), idx - boost::tuples::length<T>::value);
Pop(lua, args.get_tail(), idx + 1);
}


T& m_args;
};


static void DoPush(lua_State& lua, const char* pcArg);
static void DoPush(lua_State& lua, bool bArg);
static void DoPush(lua_State& lua, float fArg);
static void DoPush(lua_State& lua, int nArg);
static void DoPush(lua_State& lua, unsigned uArg);
static void DoPop(lua_State& lua, std::string& sArg, int idx);
static void DoPop(lua_State& lua, bool& bArg, int idx);
static void DoPop(lua_State& lua, float& fArg, int idx);
static void DoPop(lua_State& lua, int& nArg, int idx);
static void DoPop(lua_State& lua, unsigned& uArg, int idx);
};

ScriptVM.h

/*************************************************
vic.MINg 2018/09/18
************************************************/


#pragma once


#include <vector>
#include <map>
#include <string>


#include "ScriptVmArgs.h"


struct lua_State;


struct lua_Debug;
typedef void(*lua_Hook) (lua_State *L, lua_Debug *ar);
typedef int(*lua_CFunction) (lua_State *L);




class ScriptVM
{
public:


ScriptVM(void);
virtual ~ScriptVM(void);


bool Init(void);
void Destroy(void);
void ExecuteScriptFile(const char * sScriptFileName, bool bForceReload = false, bool bAssertOnError = true);
void ExecuteScript(const char * sScript, bool bAssertOnError = true);
bool ExecuteScriptFunc(const std::vector<const char *>& modules, const char * func, bool bAllowNonexist, const char * sig = "", ...);
template<typename InTuple, typename OutTuple>
void ExecuteFunc(const char* func, bool bAllowNonexist, const InTuple& inTuple, OutTuple& outTuple, const std::vector<const char*>* pModuls = 0);
void ExposeGlobalUserdata(void * va, const char * name, const char * type);
void * GetGlobalUserdata(const char * name, const char * verify_type = NULL);
void * GetUserdata(const std::vector<const char *>& modules, const char * name, const char * verify_type = NULL);
double GetGlobalNumber(const char * name);
// 是否存在某变量
bool ExistVariable(const std::vector<const char *>& modules, const char * name);


// 得到某个Lua变量
bool GetNumber(const std::vector<const char *>& modules, const char * name, double& dValue);


//void OnEndofUpdate(); //call back function on end of game update
void * CreateObjectByTypeName(const char * sTypeName);//由tolua注册过的类名创建对象
double GetGlobalTableNumber(const char *sTableName, const char* key);//Wang Hongliang:


double ComputingFormula(const char* sFormula, const std::map<std::string, double>& kParams); // 计算数学公式
bool ComparingCondition(const char* sCondition, const std::map<std::string, int>& kParams); // 判断条件语句


lua_State * GetLuaState() { return m_pLua; }


// 设置脚本钩子,只在调试时可用。
void SetHook(lua_Hook func, int nMask, int nCount);
void RegistGlobalFunction(const char* sFunctionName, lua_CFunction pFunction);


// 这里通过 自己写的io流来处理的 文件读写,把文件内容读到了buff 中
// 我这里支撑压缩文件的读写,你可以自己实现一下文件读写方法 重写该方法即可
static bool LoadFileToBuffer_IO(const char * filename, unsigned char *& buff, int& size, bool bAssertOnError);
// 我实现了一下文件读写方法,剥离了压缩文件的读写
static bool LoadFileToBuffer(const char * filename, char *& buff, int& size, bool bAssertOnError);


std::string GetScriptReturnString(std::string sScript);




//static sigslot::signal1<const char*> LuaPrinted;


// 获取脚本当前所占用的内存
int GetConsumeMemoryCount();


// 打印所有全局变量
void PrintGlobalVariables();


protected:
void DoExecuteFunc(const char* func, bool bAllowNonexist, const ScriptVmArgs::ArgsInput& input, ScriptVmArgs::ArgsOutput& output, const std::vector<const char*>* pModuls);


lua_State* m_pLua;
};


template<typename InTuple, typename OutTuple>
inline void ScriptVM::ExecuteFunc(const char* func, bool bAllowNonexist, const InTuple& inTuple, OutTuple& outTuple, const std::vector<const char*>* pModuls /* = 0 */)
{
// Pass as ArgsInput and ArgsOutput, not ArgsInputImp and ArgsOutputImp. So that the body of DoExecuteFunc() can be placed in .cpp rather than .h.
DoExecuteFunc(
func,
bAllowNonexist,
ScriptVmArgs::ArgsInputImp<InTuple>(inTuple),
(ScriptVmArgs::ArgsOutput&)ScriptVmArgs::ArgsOutputImp<OutTuple>(outTuple), // 必须强制转换一下 ScriptVmArgs::ArgsOutput& 上面的代码就不用转换
pModuls
);
}

ScriptVM.cpp

#include "ScriptVM.h"
#include "IOServer.h"
#include "FileStream.h"


extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include "tolua++.h"


#include<stdio.h>
#include <assert.h>
#include <sstream>
#include <iostream>
#include <fstream>


#define DEBUG_STACK 0


//end of tolua bind declare
extern void tolua_open_binding(lua_State * pLua);
static void stackDump(lua_State *pLua);


//sigslot::signal1<const char*> ScriptVM::LuaPrinted;


//error_msg handling function
static void error_msg(bool bUseAssert, const char * pacFmt, ...)
{
//#if defined(_DEBUG)
char acTemp[2048];


va_list args;
va_start(args, pacFmt);
vsprintf_s(acTemp, pacFmt, args);
va_end(args);
if (bUseAssert)
{
assert( acTemp);
}
//#endif
}


void report_last_error(lua_State *pLua, bool bUseAssert)
{
lua_getglobal(pLua, "_ALERT");
error_msg(bUseAssert, "%s\n", lua_tostring(pLua, -2));
error_msg(bUseAssert, "%s\n", lua_tostring(pLua, -1));
lua_pop(pLua, 2); /* remove error_msg message and _ALERT */
}




std::string & std_string_format(std::string & _str, const char * _Format, ...) {
std::string tmp;


va_list marker = NULL;
va_start(marker, _Format);


size_t num_of_chars = _vscprintf(_Format, marker);


if (num_of_chars > tmp.capacity()) {
tmp.resize(num_of_chars + 1);
}


vsprintf_s((char *)tmp.data(), tmp.capacity(), _Format, marker);


va_end(marker);


_str = tmp.c_str();
return _str;
}




ScriptVM::ScriptVM(void)
{
}


ScriptVM::~ScriptVM(void)
{
}


static int PrintStringList(lua_State * pLua) {


int n = lua_gettop(pLua); /* number of arguments */
int i;
lua_getglobal(pLua, "tostring");
std::string out;
for (i = 1; i <= n; i++) {
const char *s;
lua_pushvalue(pLua, -1); /* function to be called */
lua_pushvalue(pLua, i); /* value to print */
lua_call(pLua, 1, 1);
s = lua_tostring(pLua, -1); /* get result */
if (s == NULL)
return luaL_error(pLua, "`tostring' must return a string to `print'");
if (i > 1)
{
out += "\t";
out += s;
}
else
out += s;


lua_pop(pLua, 1); /* pop result */
}
out += "\n";


return 0;
}


bool ScriptVM::Init(void)
{
m_pLua = luaL_newstate();


lua_cpcall(m_pLua, luaopen_base, 0);
lua_cpcall(m_pLua, luaopen_io, 0);
lua_cpcall(m_pLua, luaopen_string, 0);
lua_cpcall(m_pLua, luaopen_table, 0);
lua_cpcall(m_pLua, luaopen_math, 0);
lua_cpcall(m_pLua, luaopen_debug, 0);
lua_cpcall(m_pLua, luaopen_os, 0);
lua_cpcall(m_pLua, luaopen_package, 0);


//luaopen_base(m_pLua);
//luaopen_io(m_pLua);
//luaopen_table(m_pLua);
//luaopen_math(m_pLua);
//luaopen_string(m_pLua);
//luaopen_debug(m_pLua);


//tolua_open_binding(m_pLua);


//lua_setgcthreshold(m_pLua, 200); //200k garbage collection threshold
lua_register(m_pLua, "print", PrintStringList);
//lua_register(m_pLua, "dofile", OverrideDofile);


return true;
}


void ScriptVM::Destroy(void)
{
lua_close(m_pLua);
}


void ScriptVM::ExecuteScriptFile(const char * sScriptName, bool bForceReload /* = false*/, bool bAssertOnError /*= true*/)
{
int nSize1 = lua_gettop(m_pLua);
//get chunk name as modified script name
std::string sChunkName(sScriptName);
for (unsigned int i = 0; i < sChunkName.length(); i++)
{
if (sChunkName[i] == '/' || sChunkName[i] == '.')
sChunkName[i] = '_';
}


//get the chunk global
lua_getglobal(m_pLua, sChunkName.c_str());
if (bForceReload || !lua_isfunction(m_pLua, -1))//if force reload or not found
{
//load it first
char * pBuff;
int nSize;
if (LoadFileToBuffer(sScriptName, pBuff, nSize, bAssertOnError))
{
luaL_loadbuffer(m_pLua, (char *)pBuff, nSize, sScriptName);


delete[] pBuff;


// luaL_loadfile(m_pLua, sScriptName);
lua_setglobal(m_pLua, sChunkName.c_str());
lua_getglobal(m_pLua, sChunkName.c_str());
}
else
goto failed;
}


if (lua_pcall(m_pLua, 0, 0, 0) != 0)
{
error_msg(bAssertOnError, "error executing script file %s: ", sScriptName);
report_last_error(m_pLua, bAssertOnError);
}


failed:
lua_settop(m_pLua, nSize1);
}


void ScriptVM::ExecuteScript(const char * sScript, bool bAssertOnError)
{
int status = luaL_loadbuffer(m_pLua, sScript, strlen(sScript), sScript);
if (status)
{
report_last_error(m_pLua, bAssertOnError);
}
else
{
status = lua_pcall(m_pLua, 0, LUA_MULTRET, 0); /* call main */
if (status)
report_last_error(m_pLua, bAssertOnError);
}
}


/*
* Execute Script Function func in the script. copy/pasted from the book "programming in LUA"
*/
bool ScriptVM::ExecuteScriptFunc(const std::vector<const char *>&modules, const char * func, bool bAllowNonexist, const char * sig, ...)
{
bool bIsSuccess = false;


//PROFILE("ExecuteScriptFunc");
int nSize1 = lua_gettop(m_pLua);


//debug
#if DEBUG_STACK
printf("debug lua: stack size before ExecuteScriptFunc = %d\n", nSize1);
#endif


va_list vl;
int narg, nres; /* number of arguments and results */
va_start(vl, sig);


//get the actual function
if (modules.empty()) //func is global
{
lua_getglobal(m_pLua, func);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = modules.begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}


for (++it; it != modules.end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}
}
//get the func
lua_pushstring(m_pLua, func);
lua_gettable(m_pLua, -2);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}


/* push arguments */
narg = 0;
while (*sig) { /* push arguments */
switch (*sig++) {
case 'd': /* double argument */
case 'f': /* float argument */ // NieXu: Treat float as double, same as printf()
lua_pushnumber(m_pLua, va_arg(vl, double));
break;
case 'i': /* int argument */
lua_pushnumber(m_pLua, va_arg(vl, int));
break;
case 's': /* string argument */
lua_pushstring(m_pLua, va_arg(vl, char *));
break;
case 'b': /* boolean argument */
lua_pushboolean(m_pLua, va_arg(vl, bool));
break;
case 'u': /* light user data */
lua_pushlightuserdata(m_pLua, va_arg(vl, void *));
break;
case 't': /* type user data */
{
void* pData = va_arg(vl, void *);
const char* sType = va_arg(vl, const char*);
tolua_pushusertype(m_pLua, pData, sType);
break;
}


case '>':
goto endwhile;
default:
error_msg(true, "invalid option (%c)\n", *(sig - 1));
goto failed;
}
narg++;
luaL_checkstack(m_pLua, 1, "too many arguments");
}endwhile:
/* do the call */
nres = strlen(sig); /* number of expected results */
if (lua_pcall(m_pLua, narg, nres, 0) != 0) /* do the call */
{
report_last_error(m_pLua, true);
goto failed;
}
/* retrieve results */
nres = -nres; /* stack index of first result */
while (*sig)
{ /* get results */
switch (*sig++)
{
case 'd': /* double result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, double *) = lua_tonumber(m_pLua, nres);
break;
case 'f': /* float result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, float*) = (float)lua_tonumber(m_pLua, nres);
break;
case 'i': /* int result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, int *) = (int)lua_tonumber(m_pLua, nres);
break;
case 's': /* string result */
if (!lua_isstring(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, std::string*) = lua_tostring(m_pLua, nres);
break;
case 'b': /* boolean argument */
if (!lua_isboolean(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, bool *) = (0 != lua_toboolean(m_pLua, nres));
break;
case 'u': /* light user data */
if (!lua_isuserdata(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, void **) = lua_touserdata(m_pLua, nres);
break;
default:
error_msg(true, "invalid option (%c)\n", *(sig - 1));
}
nres++;
}


bIsSuccess = true;
failed:
va_end(vl);
//clear the stack
lua_settop(m_pLua, nSize1);


#if DEBUG_STACK
//debug
int nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after ExecuteScriptFunc = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif


return bIsSuccess;
}


void ScriptVM::ExposeGlobalUserdata(void * va, const char * name, const char * type)
{
int nSize1 = lua_gettop(m_pLua);
#if DEBUG_STACK
//debug
printf("debug lua: stack size before ExposeGlobalUserdata = %d\n", nSize1);
#endif


tolua_pushusertype(m_pLua, va, type);
lua_setglobal(m_pLua, name);


//clear the stack
lua_settop(m_pLua, nSize1);


#if DEBUG_STACK
//debug
int nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after ExposeGlobalUserdata = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif
}


void * ScriptVM::GetGlobalUserdata(const char * name, const char * verify_type /*= NULL*/)
{
void * pRet = NULL;
int nSize1 = lua_gettop(m_pLua);
int nSize2 = 0;
#if DEBUG_STACK
//debug
printf("debug lua: stack size before GetGlobalUserdata = %d\n", nSize1);
#endif


lua_getglobal(m_pLua, name);


//verify type
if (verify_type)
{
tolua_Error tolua_err;
if (
!tolua_isusertype(m_pLua, 1, verify_type, 0, &tolua_err) ||
!tolua_isnoobj(m_pLua, 2, &tolua_err)
)
{
tolua_error(m_pLua, "#ferror in function 'ScriptVM::GetGlobalUserdata'.", &tolua_err);
goto failed;
}
}


pRet = tolua_tousertype(m_pLua, -1, 0);
//clear the stack
lua_settop(m_pLua, nSize1);








#if DEBUG_STACK
//debug
nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after GetGlobalUserdata = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif
return pRet;


failed:
//lua_settop(m_pLua,0);
lua_settop(m_pLua, nSize1);
return NULL;


}


double ScriptVM::GetGlobalNumber(const char * name)
{
int nSize1 = lua_gettop(m_pLua);
#if DEBUG_STACK
//debug
printf("debug lua: stack size before GetGlobalUserdata = %d\n", nSize1);
#endif


lua_getglobal(m_pLua, name);


double ret = tolua_tonumber(m_pLua, -1, 0);
//clear the stack
lua_settop(m_pLua, nSize1);


#if DEBUG_STACK
//debug
int nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after GetGlobalUserdata = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif
return ret;
}


void * ScriptVM::GetUserdata(const std::vector<const char *>& modules, const char * name, const char * verify_type/*= NULL*/)
{
void * pRet = NULL;
int nSize1 = lua_gettop(m_pLua);
int nSize2 = 0;
#if DEBUG_STACK
printf("debug lua: stack size before GetUserdata = %d\n", nSize1);
#endif
if (modules.empty()) //userdata is global
{
lua_getglobal(m_pLua, name);
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = modules.begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetUserdata: Invalid table name: %s\n", *it);
goto failed;
}


for (++it; it != modules.end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
std::vector<const char *>::const_iterator itMsg = modules.begin();
std::string sMsg;
for (; itMsg <= it; ++itMsg)
{
sMsg.append(*itMsg);
if (itMsg != it)
{
sMsg.append(".");
}
}
error_msg(true, "GetUserdata: Invalid table name: %s\n", sMsg.c_str());
goto failed;
}
}
//get the data
lua_pushstring(m_pLua, name);
lua_gettable(m_pLua, -2);
}


//verify type
//if(verify_type)
//{
// tolua_Error tolua_err;
// if (
// !tolua_isusertype(m_pLua,1,verify_type,0,&tolua_err) ||
// !tolua_isnoobj(m_pLua,2,&tolua_err)
// )
// {
// error_msg(m_pLua,"#ferror in function 'ScriptVM:GetUserdata: %s\n", name);
// goto failed;
// }
//}


pRet = tolua_tousertype(m_pLua, -1, 0);
//clear the stack
lua_settop(m_pLua, nSize1);


#if DEBUG_STACK
//debug
nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after GetUserdata = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif
return pRet;


failed:
lua_settop(m_pLua, nSize1);
return NULL;
}


static void stackDump(lua_State *m_pLua) {
int i;
int top = lua_gettop(m_pLua);
for (i = 1; i <= top; i++) { /* repeat for each level */
int t = lua_type(m_pLua, i);
switch (t) {
case LUA_TSTRING: /* strings */
printf("`%s'", lua_tostring(m_pLua, i));
break;
case LUA_TBOOLEAN: /* booleans */
printf(lua_toboolean(m_pLua, i) ? "true" : "false");
break;
case LUA_TNUMBER: /* numbers */
printf("%g", lua_tonumber(m_pLua, i));
break;
default: /* other values */
printf("%s", lua_typename(m_pLua, t));
break;
}
printf(" "); /* put a separator */
}
printf("\n"); /* end the listing */
}


bool ScriptVM::LoadFileToBuffer(const char * filename, char *& buff, int& size, bool bAssertOnError)
{
std::ifstream ifs(filename, std::ifstream::binary);


// get pointer to associated buffer object
std::filebuf* pbuf = ifs.rdbuf();
if (pbuf == nullptr)
return false;


// get file size using buffer's members
size = pbuf->pubseekoff(0, ifs.end, ifs.in);
pbuf->pubseekpos(0, ifs.in);


if (size <= 0)
return false;


// get file data
buff = new char[size];


pbuf->sgetn(buff, size);


ifs.close();


return true;
}


bool ScriptVM::LoadFileToBuffer_IO(const char * filename, unsigned char *& buff, int& size, bool bAssertOnError)
{
IO::Stream* pStream = IO::IOServer::Instance()->CreateReadStream(filename);
if (pStream != NULL && pStream->Open())
{
size = pStream->GetSize();
if (size == 0)
{
std::string sMsg;
sMsg.append(filename).append("以上文件中无内容!");
assert( sMsg.c_str());
}


buff = new unsigned char[size];
pStream->Read(buff, size);


pStream->Close();


IO::IOServer::Instance()->ReleaseStream(pStream);


return true;
}
else
{
std::string sMsg;
sMsg.append("[").append(filename).append("]脚本文件不存在!");
assert( sMsg.c_str());


if (NULL != pStream)
{
IO::IOServer::Instance()->ReleaseStream(pStream);
}
return false;
}
}


//由tolua注册过的类名创建对象
void * ScriptVM::CreateObjectByTypeName(const char * sTypeName)
{
//处理lua脚本
int nSize = (int)strlen(sTypeName) + 20;
unsigned char* buffer = new unsigned char[nSize];
sprintf_s((char*)buffer, nSize, "pMyCreatedObj=%s:new()", sTypeName);


//执行脚本
ExecuteScript((char*)buffer);


void* ret = GetGlobalUserdata("pMyCreatedObj");


return ret;
}


//获得全局表中常量
double ScriptVM::GetGlobalTableNumber(const char *sTableName, const char* key)
{
double ret = 0;
int nSize1 = lua_gettop(m_pLua);
int nSize2 = 0;
#if DEBUG_STACK
//debug
printf("debug lua: stack size before GetGlobalUserdata = %d\n", nSize1);
#endif
lua_getglobal(m_pLua, sTableName);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetGlobalTableNumber: %s isn't a Lua Table.", sTableName);
goto failed;
}


lua_pushstring(m_pLua, key);
lua_gettable(m_pLua, -2);
if (!lua_isnumber(m_pLua, -1))
{
error_msg(true, "GetGlobalTableNumber: %s isn't a number.", key);
goto failed;
}
ret = lua_tonumber(m_pLua, -1);
lua_settop(m_pLua, nSize1);
#if DEBUG_STACK
//debug
nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after GetUserdata = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif


return ret;


failed:
lua_settop(m_pLua, nSize1);
return 0;
}


void ScriptVM::SetHook(lua_Hook func, int nMask, int nCount)
{
if (!m_pLua)
return;


//lua_sethook(m_pLua, func, nMask, nCount);
}


bool ScriptVM::ExistVariable(const std::vector<const char *>& modules, const char * name)
{
int nSize1 = lua_gettop(m_pLua);


if (modules.empty()) //userdata is global
{
lua_getglobal(m_pLua, name);
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = modules.begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetUserdata: Invalid table name: %s\n", *it);
goto failed;
}


for (++it; it != modules.end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetUserdata: Invalid table name: %s\n", *it);
goto failed;
}
}
//get the data
lua_pushstring(m_pLua, name);
lua_gettable(m_pLua, -2);
}


if (lua_isnil(m_pLua, -1))
{
goto failed;
}
else
{
goto success;
}


failed:
lua_settop(m_pLua, nSize1);
return false;


success:
lua_settop(m_pLua, nSize1);
return true;
}


// 替换字符串
void string_replace(std::string &strBig, const std::string &strsrc, const std::string &strdst)
{
std::string::size_type pos = 0;
std::string::size_type srclen = strsrc.size();
std::string::size_type dstlen = strdst.size();


while ((pos = strBig.find(strsrc, pos)) != std::string::npos)
{
strBig.replace(pos, srclen, strdst);
pos += dstlen;
}
}


double ScriptVM::ComputingFormula(const char* sFormula, const std::map<std::string, double>& kParams)
{
std::string sRealFormula = sFormula;
if (sRealFormula == "")
{
assert(false && "公式错误");
return 0.0;
}


std::string sParamValue = "0";
std::stringstream kStrStream;


// 替换参数
for (std::map<std::string, double>::const_iterator it(kParams.begin()); it != kParams.end(); it++)
{// 得到公式
// kStrStream<<it->second;
// sParamValue = kStrStream.str();
sParamValue = std_string_format(sParamValue, "%f", it->second);
string_replace(sRealFormula, it->first, sParamValue);


/*
// 支持在公式中包含的脚本函数调用可以有字符串参数,避免以当前属性名为字符串的参数被属性值替换 [2/18/2011 shuaiwang]
if (sRealFormula.find('\'') != 0xffffffff)
{
std::string sProParam = std::string("\'").append(it->first).append("\'");
sRealFormula.replace(sProParam, "????");
sRealFormula.replace(it->first, sParamValue);
sProParam = std::string("\"").append(it->first).append("\"");
sRealFormula.replace("????", sProParam);
}
else
{
sRealFormula.replace(it->first, sParamValue);
}
*/


//sRealFormula.Replace(it->first, sParamValue);
}


std::string sReturn = "return ";


if (sRealFormula.find(sReturn) == std::string::npos)
{
sRealFormula = sReturn.append(sRealFormula);
}


/*std::stringstream kFunStream;


kFunStream<<"computing_formula_teamfunction = function()\n"
<< sRealFormula << "\n"
<< "end";*/


std::string sTempFunction = std::string("computing_formula_teamfunction = function()\n").append(sRealFormula).append("\nend")/*kFunStream.str()*/;


ExecuteScript(sTempFunction.c_str(), true);


std::vector<const char *> modules;
double dValue = 0.0;
if (ExecuteScriptFunc(modules, "computing_formula_teamfunction", false, ">d", &dValue) == false)
{
//_logError2("computing_formula_teamfunction这个问题的来源是:", sFormula << ",请在数据表中修改相应公式!");
}


ExecuteScript("computing_formula_teamfunction = nil", true);


return dValue;
}


bool ScriptVM::ComparingCondition(const char* sCondition, const std::map<std::string, int>& kParams)
{
std::string sRealCondition = sCondition;
if (sRealCondition == "")
{
assert(!"[ScriptVM::ComparingCondition] Condition Error");
return false;
}


std::string sParamValue = "false";


for (std::map<std::string, int>::const_iterator it(kParams.begin()); it != kParams.end(); it++)
{
sParamValue = std_string_format(sParamValue, "%d", it->second);
string_replace(sRealCondition, it->first, sParamValue);
}


std::string sReturn = "return ";


//sRealCondition = "({true} and {true})[1]";


if (sRealCondition.find(sReturn) == std::string::npos)
{
sRealCondition = sReturn.append(sRealCondition);
}


std::string sTempFunction = std::string("comparing_condition_function = function()\n").append(sRealCondition).append("\nend")/*kFunStream.str()*/;


//boost::timer btimer;
ExecuteScript(sTempFunction.c_str(), true);


std::vector<const char *> modules;
bool bValue = false;
if (ExecuteScriptFunc(modules, "comparing_condition_function", false, ">b", &bValue) == false)
{
assert(!"[ScriptVM::ComparingCondition] ExecuteScriptFunc Error");
}


ExecuteScript("computing_formula_function = nil", true);


return bValue;
}


std::string ScriptVM::GetScriptReturnString(std::string sScript)
{
std::string sReturn = "return ";


if (sScript.find(sReturn) == 0xffffffff)
{
sScript = sReturn.append("\"").append(sScript).append("\"");
}


std::string sTempFunction = std::string("string_function = function()\n").append(sScript).append("\nend");
ExecuteScript(sTempFunction.c_str(), true);


std::vector<const char *> modules;
std::string sReturnString;
ExecuteScriptFunc(modules, "string_function", false, ">s", &sReturnString);


ExecuteScript("string_function = nil", true);
return sReturnString.c_str();
}


void ScriptVM::RegistGlobalFunction(const char* sFunctionName, lua_CFunction pFunction)
{
lua_register(m_pLua, sFunctionName, pFunction);
}


bool ScriptVM::GetNumber(const std::vector<const char *>& modules, const char * name, double& dValue)
{
int nSize1 = lua_gettop(m_pLua);


if (modules.empty()) //userdata is global
{
lua_getglobal(m_pLua, name);
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = modules.begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetUserdata: Invalid table name: %s\n", *it);
lua_settop(m_pLua, nSize1);
return false;
}


for (++it; it != modules.end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
error_msg(true, "GetUserdata: Invalid table name: %s\n", *it);
lua_settop(m_pLua, nSize1);
return false;
}
}
//get the data
lua_pushstring(m_pLua, name);
lua_gettable(m_pLua, -2);
}


if (lua_isnumber(m_pLua, -1))
{
dValue = tolua_tonumber(m_pLua, -1, 0);
lua_settop(m_pLua, nSize1);
return true;
}
else
{
lua_settop(m_pLua, nSize1);
return false;
}
}


void ScriptVM::DoExecuteFunc(const char* func, bool bAllowNonexist, const ScriptVmArgs::ArgsInput& input, ScriptVmArgs::ArgsOutput& output, const std::vector<const char*>* pModuls)
{


//get the actual function
const int nOrgSize = lua_gettop(m_pLua);
int nInputSize = input.Size();
int nOutputSize = output.Size();


if (!pModuls || (pModuls && pModuls->empty())) //func is global
{
lua_getglobal(m_pLua, func);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = pModuls->begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}


for (++it; it != pModuls->end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}
}


//get the func
lua_pushstring(m_pLua, func);
lua_gettable(m_pLua, -2);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}


/* push arguments */
input.Push(*m_pLua);


/* do the call */
if (lua_pcall(m_pLua, nInputSize, nOutputSize, 0) != 0) /* do the call */
{
report_last_error(m_pLua, true);
goto failed;
}


/* retrieve results */
output.Pop(*m_pLua);


failed:
//clear the stack
lua_settop(m_pLua, nOrgSize);
}


int ScriptVM::GetConsumeMemoryCount()
{
int nCount = 0;
if (m_pLua)
{
nCount = lua_getgccount(m_pLua);
}


return nCount;
}


void ScriptVM::PrintGlobalVariables()
{
ExecuteScript("for n in pairs(_G) do print(n) end", true);
}


void ScriptVmArgs::DoPush(lua_State& lua, const char* pcArg)
{
lua_pushstring(&lua, pcArg);
}


void ScriptVmArgs::DoPush(lua_State& lua, const bool bArg)
{
lua_pushboolean(&lua, bArg);
}


void ScriptVmArgs::DoPush(lua_State& lua, const float fArg)
{
lua_pushnumber(&lua, fArg);
}


void ScriptVmArgs::DoPush(lua_State& lua, const int nArg)
{
lua_pushnumber(&lua, nArg);
}


void ScriptVmArgs::DoPush(lua_State& lua, unsigned uArg)
{
lua_pushinteger(&lua, uArg);
}


void ScriptVmArgs::DoPop(lua_State& lua, bool& bArg, int idx)
{
bArg = lua_toboolean(&lua, idx) != 0;
}


void ScriptVmArgs::DoPop(lua_State& lua, std::string& sArg, int idx)
{
sArg = lua_tostring(&lua, idx);
}


void ScriptVmArgs::DoPop(lua_State& lua, float& fArg, int idx)
{
fArg = (float)lua_tonumber(&lua, idx);
}


void ScriptVmArgs::DoPop(lua_State& lua, int& nArg, int idx)
{
nArg = (int)lua_tonumber(&lua, idx);
}


void ScriptVmArgs::DoPop(lua_State& lua, unsigned& uArg, int idx)
{
uArg = lua_tointeger(&lua, idx);
}


OK,下面来实现一下测试代码,来看看如何 lua 和 c++进行交互的。


lua_project_test_code


我们来简单编写几个文件。

函数调用:

ScriptFunction.h

/**************************************************************************************
vic.MINg 2018/09/19
***************************************************************************************/


#pragma once


namespace Script {
//tolua_begin
void show_luaproject_info();
//tolua_end
}

ScriptFunction.cpp

#include "ScriptFunction.h"
#include <stdio.h>


namespace Script {


void show_luaproject_info()
{
printf("Show LuaProject C++ Info.\n");
}


}

结构体:

Hag.h

/***********************************************************************
vic. MINg 2018/09/25
***********************************************************************/


#pragma once
#include <string>


//tolua_begin
struct Hag
{
int hag_id;
std::string hag_name;
bool is_valid;
int hag_score;


Hag() { }
virtual ~Hag() {}
};
//tolua_end

类:

Student.h

#pragma once


#include<iostream>
using namespace std;


//tolua_begin
class Student
{
public:


Student();
virtual ~Student();


void Run();
void Run(int a);


};
//tolua_end

Student.cpp

#include "Student.h"




Student::Student()
{
}


void Student::Run()
{
//cout << "Student Run" << endl;
printf("Student Student Run.\n");
}


void Student::Run(int a)
{
//cout << "Student Run" <<a<< endl;
printf("Student Student Run with %d.\n", a);
}


Student::~Student()
{
}

非常简单的测试代码。不言语。继续往下。我们需要编写一份 LuaScript.pkg 文件来导出这些文件,让lua可以调用他们。

LuaScript.pkg

#include "tolua++.h"


$#pragma warning(disable : 4800) //forcing value to bool warning




// script functions
$cfile "ScriptFunction.h"
$using namespace Script;




// class
$cfile "Hag.h"
$cfile "Student.h"

做一个实行脚本 glue.bat 来通过 LuaScript.pkg 编译 lua 的cpp文件。

glue.bat

..\..\Debug\tolua -o l_LuaScript.cpp LuaScript.pkg
pause

这样会生成 l_LuaScript.cpp 来实行lua和c++ 之间的相互调用,这里由于是生成出来的 就不粘代码了

值得注意的几个方面:使用 boost 的时候为了方便 我们使用了 BOOST_ALL_NO_LIB ,这样 就不用编译 boost了

//  "boost/config/user.hpp" 取消 BOOST_ALL_NO_LIB 注释
#define BOOST_ALL_NO_LIB

注意 tolua中 注释的使用,如果没使用 //tolua_export、//tolua_begin、//tolua_end 是无法在实行 glue.bat 时生成的 l_LuaScript.cpp 中生成关联映射的。

工程配置:附加包含目录:..;..\include;....\lua\src;....\tolua\include; 附加依赖项:lua.lib;tolua.lib; 预处理器定义:WIN32;_CRT_SECURE_NO_WARNINGS;

main.cpp

#include "ScriptVM.h"
#include "ScriptVmArgs.h"
#include <stdio.h>
#include <string>
#include <assert.h>
#include <iostream>


extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}




#include "tolua++.h"


#include "IOServer.h"
#include "Stream.h"
#include "Hag.h"


#include "FSWrapper.h"


#include "boost/ref.hpp"


extern void report_last_error(lua_State *L, bool bUseAssert);
extern int tolua_LuaScript_open(lua_State* tolua_S);


static int OverrideDofile(lua_State * L) {


int n = lua_gettop(L); /* number of arguments */
lua_getglobal(L, "tostring");


if (n != 1)
return luaL_error(L, "'dofile' param error!");


std::string sFileName = "";
lua_pushvalue(L, -1); /* function to be called */
lua_pushvalue(L, 1); /* value to print */
lua_call(L, 1, 1);
sFileName = lua_tostring(L, -1); /* get result */
if (sFileName == "")
return luaL_error(L, "'dofile' param error!");


sFileName = Internal::FSWrapper::GetBinDirectory().c_str();
sFileName.append("/Scripts/test.lua");
lua_pop(L, 1); /* pop result */


//get chunk name as modified script name
std::string sChunkName(sFileName.c_str());
for (unsigned int i = 0; i < sChunkName.length(); i++)
{
if (sChunkName[i] == '/' || sChunkName[i] == '.')
sChunkName[i] = '_';
}


//load it first
char * pBuff = nullptr;
int nSize;
if (ScriptVM::LoadFileToBuffer(sFileName.c_str(), pBuff, nSize, true))
{
luaL_loadbuffer(L, (char *)pBuff, nSize, sFileName.c_str());


delete[] pBuff;


//luaL_loadfile(L, sScriptName);
lua_setglobal(L, sChunkName.c_str());
lua_getglobal(L, sChunkName.c_str());


if (lua_pcall(L, 0, 0, 0) != 0)
{
//printf("文件 " << sFileName.c_str() << " 执行错误!");
std::string sErrorMsg = "error executing script file %s: ";
sErrorMsg += sFileName;
assert(sErrorMsg.c_str());
//error_msg(true, "error executing script file %s: ", sFileName.AsCharPtr());
report_last_error(L, true);
}
}
else
{


lua_settop(L, n);
return luaL_error(L, "error in the file!");
}


return 0;
}


int main(int argc, char** argv)
{
ScriptVM* pScriptVM = new ScriptVM;


if (!pScriptVM->Init())
{
printf("ScriptVM Init failed! \n");
return -1;
}


pScriptVM->RegistGlobalFunction("dofile", OverrideDofile);
// 注意调用 tolua_LuaScript_open ,使lua和c++可以相互调用
tolua_LuaScript_open(pScriptVM->GetLuaState());


std::string strScriptLua;
#ifdef _WIN32
strScriptLua = DirectoryManager::GetExeDirectory() + "scripts/scriptvm_hello.lua";
#else
strScriptLua = "scripts/scriptvm_hello.lua";
#endif
// 加载一个lua脚本
pScriptVM->ExecuteScriptFile(strScriptLua.c_str());




// 调用 lua 中的 student_run 方法 , ("i", 20) 表示传参, i 表示传一个参数 为int型
// 假设要传 两个参数 一个是 double 一个是 std::string 可以写成 ("ds", 3.14, "vic.MINg")
std::vector<const char *>modules;
pScriptVM->ExecuteScriptFunc(modules, "student_run", false, "i", 20);




// 调用 lua 中的 add 方法
// inTuple 为进参、 outTuple 为出参
// 调用方法如下
int a = 20, b = 10;
int sum = 0;
boost::tuple<int, int> inTuple = boost::tuples::make_tuple(a, b);
boost::tuple<int&> outTuple = boost::tuples::make_tuple(boost::ref(sum));
pScriptVM->ExecuteFunc("add", false, inTuple, outTuple);
printf("Lua Func Add %d + %d = %d \n", a, b, sum);




// 这里我们 传一个字符串 来在lua中 new一个 新类
// 看似比较平庸,我们可以通过某些文件来动态生成一个类
// 比如 我们在 xml 中定义一个 <HagType>PlayerHag</HagType>
// 然后 读取该项 然后 类型下面代码 生成 PlayerHag, 然后通过 RTTI 来判断 应用该类
std::string strHag = "english_hag = nil english_hag = Hag:new()";
pScriptVM->ExecuteScript(strHag.c_str());
Hag* pEnglish_hag = static_cast<Hag*>(pScriptVM->GetGlobalUserdata("english_hag"));
if (pEnglish_hag)
{
pEnglish_hag->hag_id = 1;
pEnglish_hag->hag_name = "English";
pEnglish_hag->is_valid = true;
pEnglish_hag->hag_score = 94;
}




// 通过 lua 快速公式计算
std::string strFormula = "1.8586*level + 8.7842 +50+(level-1)*5/3";
std::map<std::string, double> mapParams;
mapParams.insert(std::make_pair( "level", 3));
double dValue = pScriptVM->ComputingFormula(strFormula.c_str(), mapParams);
printf("Lua ComputingFormula level = %d, 1.8586*level + 8.7842 +50+(level-1)*5/3 = %lf \n", 3, dValue);


// 通过 lua 条件判断
std::string strCondition = "(command_id == 1001) and (command_execution_stage == 0)";
//std::string strCondition = "command_id == 1001";
std::map<std::string, int> mapParamsCondition;
mapParamsCondition.insert(std::make_pair("command_id", 1002));
mapParamsCondition.insert(std::make_pair("command_execution_stage", 0));
bool bValue = pScriptVM->ComparingCondition(strCondition.c_str(), mapParamsCondition);
printf("Lua ComparingCondition ( command_id == 1001 AND command_execution_stage == 0 ) return %d \n", bValue);




system("pause");
delete pScriptVM;


return 0;
}

LuaScript 工程

这个项目 使用BabeLua插件创建,方便lua脚本的编写和调试。

scriptvm_hello.lua

str = "I am so cool"  
tbl = {name = "shun", id = 20114442}


function add(a,b)
return a + b + math.pi
end


function student_run(n)
local st = Student:new()
st:Run(n)
st:delete()
show_luaproject_info()
end

OK 这样一套完善的lua与c++相互交互体系就这样实现了,运行结果如下~


lua_project_scriptvm_test


文章转载自林元皓,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论