Приветствую, уважаемые!
В процессе написания игрушки в полный рост встала задача биндить С++-объекты в Lua. По некоторым причинам сторонние библиотеки не подошли (то руки мои кривые, то лень, то просто другой взгляд на удобный биндинг). Собственно, предложенное ниже решение не претендует на полноту и истину в последней инстанции, однако, вполне возможно, пригодится кому-нибудь из вас.
Один из главных пойнтов написания велосипеда был в том, что были не нужны ни настраиваемые конструкторы, ни темплейтный биндинг методов напрямую — суть в том, что была уже разработана некая база классов, возможности которых вдруг захотелось поиметь из скриптования на Lua. Очевидно, что самый простой способ для этого — отдать публикацию своих методов и свойств на откуп самим классам, что и было сделано.
В приведенных исходниках закомментированные строчки показывают использование движка HGE для загрузки скриптов прямо из архива (все это легко переделать на вызов скриптов из файла), плюс добавляются парочку обработчиков функций (скажем, preload используется в проекте вместо стандартного include, чтобы грузить файлы опять-таки из архива к игре, а bitor — реализует аналог оператора | ). В общем, молчать дальше сил нет
В самооправдание — код местами не найс, в смысле — выглядит не всегда красиво.
Хидер (LuaStarter.h):
#pragma once
#include "lua.hpp"
#pragma comment(lib,"lua51.lib")
#include <assert.h>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
/////////////////////////////////////////////////////////////////////////////////
// Simple starter for lua engine
/////////////////////////////////////////////////////////////////////////////////
class LuaEngine
{
lua_State* lua;
void AttachMyLoader();
public:
void DoFile(const char* fileName);
lua_State* Lua()
{
return lua;
};
void Close()
{
if(lua)
lua_close(lua);
lua = NULL;
}
LuaEngine(bool openLibs)
{
lua = lua_open();
if(openLibs)
luaL_openlibs(lua);
AttachMyLoader();
};
~LuaEngine(void)
{
Close();
};
operator lua_State* ()
{
return this->lua;
};
};
//////////////////////////////////////////////////////////////////////
// LUA SETTERS
//////////////////////////////////////////////////////////////////////
template<typename T>
void luaPushParam(lua_State* luaVM, T t);
template<>
void luaPushParam(lua_State* luaVM, int p);
template<>
void luaPushParam(lua_State* luaVM, char* p);
template<>
void luaPushParam(lua_State* luaVM, char const* p);
template<>
void luaPushParam(lua_State* luaVM, double p);
template<>
void luaPushParam(lua_State* luaVM, bool p);
// UNTESTED PIECE OF CODE :(
template<typename T>
void luaPushParam(lua_State* L, const std::vector<T>& arg)
{
lua_newtable(L); // [-1] - table
const size_t size = arg.size();
for(size_t i = 0; arg.size() > i; ++i)
{
lua_pushnumber(L, i + 1); // [-2] - index
luaPushParam(L, arg[i]); // [-3] - value
lua_settable(L, -3); // [-1] - table
}
}
template<typename TKey, typename TValue>
void luaPushParam(lua_State* L, const std::map<TKey, TValue>& arg)
{
lua_newtable(L); // stack: [-1] - table
for(typename std::map<TKey, TValue>::const_iterator i = arg.begin(); arg.end() != i; ++i)
{
luaPushParam(L, i->first); // [-2] - table key
luaPushParam(L, i->second); // [-3] - table key value
lua_settable(L, -3); // [-1] - table
}
}
//////////////////////////////////////////////////////////////////////
// LUA GETTERS
//////////////////////////////////////////////////////////////////////
template<typename T>
void luaGetParam(lua_State* luaVM, int idx, T& param);
template<>
void luaGetParam(lua_State* luaVM, int idx, const char*& param);
template<>
void luaGetParam(lua_State* luaVM, int idx, int& param);
template<>
void luaGetParam(lua_State* luaVM, int idx, DWORD& param);
template<>
void luaGetParam(lua_State* luaVM, int idx, double& param);
template<>
void luaGetParam(lua_State* luaVM, int idx, bool& param);
// UNTESTED PIECE OF CODE :(
template<typename TKey, typename TValue>
void luaGetParam(lua_State* L, int index, std::map<TKey, TValue>& ret);
template<typename T>
void luaGetParam(lua_State* L, int index, std::vector<T>& ret);
template<typename T>
void luaGetParam(lua_State* L, int index, const char* key, T& ret);
////////////////////////////////////////////////////////////////////////
// bind object functions helpers
////////////////////////////////////////////////////////////////////////
struct ILuaBindableEvents // events handlers for dynamically binded methods and properties
{
// dynamic methods handler
virtual int OnMethod(lua_State* state, int MethodID, const char* MethodName) = 0;
// dynamic property get handler
virtual int OnPropertyGet(lua_State* state, const char* PropertyName) = 0;
// dynamic property set handler
virtual int OnPropertySet(lua_State* state, const char* PropertyName) = 0;
};
// binder for dynamic objects methods
template<class T >
class LuaObjectBinder
{
private:
T* m_BindedObj; // binded object
lua_State* lua; // Lua VM
typedef struct
{
LuaObjectBinder<T>* _this; // this pointer
const char* MethodName; // name of binded method
int MethodID; // binded method id
} CThisData; // holder for dynamic methods
typedef std::vector<CThisData> CThisVec;
CThisVec m_Methods; // methods to bind
// hook function for our methods
static int MethodHandler(lua_State* state)
{
// get data pointer
CThisData* data = (CThisData* ) lua_touserdata(state, lua_upvalueindex(1));
// call OnMethod in binded object
return data->_this->OnMethod(state,data);
}
static int PropertyGetHandler(lua_State* state)
{
// get this pointer
LuaObjectBinder<T>* _this = (LuaObjectBinder<T>*) lua_touserdata(state, lua_upvalueindex(1));
// check for valid property name
if(!lua_isstring(state,-1))
return 0;
// get property name
const char* propname = lua_tostring(state,-1);
// call handler
return _this->m_BindedObj->OnPropertyGet(state,propname);
}
static int PropertySetHandler(lua_State* state)
{
// get this pointer
LuaObjectBinder<T>* _this = (LuaObjectBinder<T>*) lua_touserdata(state, lua_upvalueindex(1));
// get property name index
int top = lua_gettop(state);
int propnameidx = -(top-1);
// check for valid property name
if(!lua_isstring(state,propnameidx))
return 0;
// get property name
const char* propname = lua_tostring(state,propnameidx);
// call handler
return _this->m_BindedObj->OnPropertySet(state,propname);
}
int OnMethod(lua_State* state, CThisData* data)
{
return m_BindedObj->OnMethod(state,data->MethodID,data->MethodName);
}
public:
void AddMethod(const char* name, int methodId)
{
CThisData dt;
dt._this = this;
dt.MethodName = name;
dt.MethodID = methodId;
m_Methods.push_back(dt);
};
void Bind() // bind object methods and properties
{
// create new table for object
lua_newtable(lua); // [-1] - table
int methods = lua_gettop(lua); // methods in table
// create metatable
lua_newtable(lua);
// create property get handler
lua_pushstring(lua,"__index");
lua_pushlightuserdata(lua, (void*) this);
lua_pushcclosure(lua, PropertyGetHandler, 1);
lua_settable(lua, -3); // metatable.__index = PropertyGetHandler;
// create property set handler
lua_pushstring(lua,"__newindex");
lua_pushlightuserdata(lua, (void*) this);
lua_pushcclosure(lua, PropertySetHandler, 1);
lua_settable(lua, -3); // metatable.__newindex = PropertySetHandler;
// set metatable for our object
lua_setmetatable(lua,methods);
// bind methods to the our object table
for(CThisVec::iterator it = m_Methods.begin();it != m_Methods.end(); ++it)
{
lua_pushstring(lua, (*it).MethodName); // [-1] - method name, [-2] - table
lua_pushlightuserdata(lua, (void* )&(*it));
lua_pushcclosure(lua, MethodHandler, 1); // [-1] - function, [-2] - method name, [-3] - table
// bind without metamethods called
lua_rawset(lua, -3); // [-1] - table
} // for
// init object table done.
// now object placed to the Lua stack
// and we ready to add other params to stack.
};
private:
// hide copy ctor and assignment operator
LuaObjectBinder(const LuaObjectBinder& rhs);
LuaObjectBinder operator=(const LuaObjectBinder& rhs);
public:
LuaObjectBinder(lua_State* l, T* bindableObj,unsigned int methodsToReserve=0)
: lua(l)
, m_BindedObj(bindableObj)
{
if(methodsToReserve)
m_Methods.reserve(methodsToReserve);
};
~LuaObjectBinder(){};
lua_State* Lua()
{
return this->lua;
};
};
class LuaFunction; // forward declaration
struct ILuaFunctionEvents // events for function (optional)
{
virtual void BeforeCall(LuaFunction* func) = 0;
virtual void AfterCall(LuaFunction* func) = 0;
};
class LuaFunction
{
private:
int returns; // number of wanted returns
int stackPtr; // stack pointer
ILuaFunctionEvents* events; // events handler
lua_State* lua; // lua VM
const char* name; // function name
int params; // params count
const char* errMsg;
bool hasCallErrors;
bool HasReturns() { return (returns > 0); }
public:
// assign the event handler
void SetSubscriber(ILuaFunctionEvents* ev) {events = ev;};
// we want N return values from function
void WantReturns(int num) {returns = num; };
// add one or more params to the stack
void AddParam(int p) { luaPushParam(lua,p); ++params; };
void AddParam(char* p) { luaPushParam(lua,p); ++params; };
void AddParam(const char* p) { luaPushParam(lua,p); ++params; };
void AddParam(double p) { luaPushParam(lua,p); ++params; };
void AddParam(bool p) { luaPushParam(lua,p); ++params; };
// add dynamic object to the LUA stack
template<class T>
void AddParam(LuaObjectBinder<T>* binder)
{
binder->Bind(); // bind dynamic object by creating object table
++params;
};
// gets return value from stack
void ReturnValue(const char*& res) // return string from lua
{
res = NULL;
if(!HasReturns())
return;
luaGetParam(lua,stackPtr,res);
--stackPtr;
}
void ReturnValue(int& res) // return int from lua
{
res = -1;
if(!HasReturns())
return;
luaGetParam(lua,stackPtr,res);
--stackPtr;
}
void ReturnValue(double& res) // return double from lua
{
res = -1;
if(!HasReturns())
return;
luaGetParam(lua,stackPtr,res);
--stackPtr;
}
void ReturnValue(bool& res) // return bool from lua
{
res = false;
if(!HasReturns())
return;
luaGetParam(lua,stackPtr,res);
--stackPtr;
}
const char* ErrorMessage()
{
return errMsg;
};
bool CallSucceeded()
{
return !hasCallErrors;
};
// call LUA function
bool Call()
{
errMsg = NULL;
hasCallErrors = false;
if(events)
events->BeforeCall(this);
// call function
int res = lua_pcall(lua,params,returns,0);
hasCallErrors = res != 0;
if(!hasCallErrors) // no errors
{
if(HasReturns())
stackPtr = lua_gettop(lua);
}
else
{
luaGetParam(lua,-1,errMsg);
#ifdef _DEBUG
::MessageBox(GetForegroundWindow(),errMsg,NULL,MB_OK | MB_TASKMODAL);
exit(res);
#endif
} // else
if(events)
events->AfterCall(this);
return !hasCallErrors;
}
public:
LuaFunction(lua_State* l,const char* n)
: lua(l)
, name(n)
, params(0)
, returns(0)
, stackPtr(0)
, events(NULL)
{
// get function pointer to the Lua stack
lua_getfield(lua, LUA_GLOBALSINDEX, name);
};
~LuaFunction()
{
};
lua_State* Lua()
{
return this->lua;
};
};
Сырец (LuaStarter.cpp):
#include "StdAfx.h"
#include "LuaStarter.h"
static int BitorOp(lua_State* lua)
{
int params = lua_gettop(lua);
int res = 0;
if(params > 0)
{
int param = 0;
for(int i=0;i<params;++i)
{
luaGetParam(lua,-1,param);
res |= param;
} // for
} // if(params > 0)
luaPushParam(lua,res);
return 1;
}
static int MyLoader(lua_State* pState)
{
std::string module = lua_tostring(pState, 1);
std::string fullPath = module;
fullPath += _T(".lua");
//CHGEStarter* engine = CHGEStarter::Instance();
DWORD size = 0;
const TCHAR* buf = (const TCHAR*) _T("тут код скрипта");//engine->GetHGE()->Resource_Load(fullPath.c_str(),&size);
if(size > 0)
{
lua_getfield(pState, LUA_REGISTRYINDEX, _T("_LOADED")); // push "package"
const TCHAR* name = module.c_str();
luaL_loadbuffer(pState, buf, size, fullPath.c_str());
lua_pushstring(pState, name); // pass name as argument to module
lua_call(pState, 1, 1); // run loaded module
if (!lua_isnil(pState, -1)) // non-nil return?
lua_setfield(pState, 2, name); // _LOADED[name] = returned value
lua_getfield(pState, 2, name);
lua_pushboolean(pState, 1); // use true as result
lua_pushvalue(pState, -1); // extra copy to be returned
lua_setfield(pState, 2, name); // _LOADED[name] = true
lua_pop(pState, 1);
}
//engine->FreeInstance();
return 0;
}
void LuaEngine::DoFile(const char* fileName)
{
//CHGEStarter* engine = CHGEStarter::Instance();
const char* fPtr = fileName;
if(*fPtr == _T('\\') || *fPtr == _T('/')) // skip beginning slashes
++fPtr;
std::string fullPath = fPtr;
DWORD size = 0;
const TCHAR* buf = (const TCHAR*) _T("тут код скрипта");//engine->GetHGE()->Resource_Load(fileName,&size);
//engine->FreeInstance();
luaL_loadbuffer(lua,buf,size,fileName);
lua_pcall(lua,0,LUA_MULTRET,0);
}
void LuaEngine::AttachMyLoader()
{
lua_register( lua, _T("preload"), MyLoader );
lua_register(lua,_T("bitor"), BitorOp );
}
template<>
void luaPushParam(lua_State* luaVM, char* p)
{
// push string to the Lua stack
lua_pushstring(luaVM, p);
}
template<>
void luaPushParam(lua_State* luaVM, char const* p)
{
// push string to the Lua stack
lua_pushstring(luaVM, p);
}
template<>
void luaPushParam(lua_State* luaVM, bool p)
{
// push boolean to the Lua stack
lua_pushboolean(luaVM, p);
}
template<>
void luaPushParam(lua_State* luaVM, double p)
{
// push double to the Lua stack
lua_pushnumber(luaVM, p);
}
template<>
void luaPushParam(lua_State* luaVM, int p)
{
// push int to the Lua stack
lua_pushnumber(luaVM,p);
}
template<>
void luaGetParam(lua_State* luaVM, int idx, const char*& param)
{
assert(lua_isstring(luaVM,idx));
param = lua_tostring(luaVM,idx);
// pop returned value from stack
lua_pop(luaVM, 1);
}
template<>
void luaGetParam(lua_State* luaVM, int idx, int& param)
{
assert(lua_isnumber(luaVM,idx));
param = static_cast<int>(lua_tonumber(luaVM,idx));
// pop returned value from stack
lua_pop(luaVM, 1);
}
template<>
void luaGetParam(lua_State* luaVM, int idx, DWORD& param)
{
assert(lua_isnumber(luaVM,idx));
param = static_cast<DWORD>(lua_tonumber(luaVM,idx));
// pop returned value from stack
lua_pop(luaVM, 1);
}
template<>
void luaGetParam(lua_State* luaVM, int idx, double& param)
{
assert(lua_isnumber(luaVM,idx));
param = (double) lua_tonumber(luaVM,idx);
// pop returned value from stack
lua_pop(luaVM, 1);
}
template<>
void luaGetParam(lua_State* luaVM, int idx, bool& param)
{
assert(lua_isboolean(luaVM,idx));
param = lua_toboolean(luaVM,idx) != 0;
// pop returned value from stack
lua_pop(luaVM, 1);
}
template<typename TKey, typename TValue>
void luaGetParam(lua_State* L, int index, std::map<TKey, TValue>& ret)
{
// stack:
if(!lua_istable(L, index))
return;
lua_pushvalue(L, index); // stack: map
lua_pushnil(L); // stack: map nil
while(lua_next(L, -2)) // stack: map key value
{
TKey key;
luaGetParam(L, -2, key);
TValue value;
luaGetParam(L, -1, value);
ret[key] = value;
lua_pop(L, 1); // stack: map key
}
// stack: map
lua_pop(L, 1); // stack:
}
template<typename T>
void luaGetParam(lua_State* L, int index, std::vector<T>& ret)
{
// stack:
if(!lua_istable(L, index))
return;
lua_pushvalue(L, index); // stack: vector
const int count = luaL_getn(L, -1);
for(int i = 1; count >= i; ++i)
{
lua_pushnumber(L, i);
lua_gettable(L, -2);
T value;
luaGetParam(L, -1, value);
ret.push_back(value);
lua_pop(L, 1); // stack: vector
}
lua_pop(L, 1); // stack:
}
template<typename T>
void luaGetParam(lua_State* L, int index, const char* key, T& ret)
{
// stack: table
lua_getfield(L, index? index : LUA_GLOBALSINDEX, key); // stack: table value
luaGetParam(L, -1, ret);
lua_pop(L, 1); // stack: table
}
Пример использования:
class CDynamic : public ILuaBindableEvents
{
private:
LuaObjectBinder<CDynamic>* binder;
public:
LuaObjectBinder<CDynamic>* GetBinder() {return binder; };
// our script object handlers
virtual int OnPropertyGet(lua_State* state, const char* PropertyName)
{
// тут возвращаем значение внутреннего свойства в скриптовую машину
};
virtual int OnPropertySet(lua_State* state, const char* PropertyName)
{
// тут устанавливаем внутреннее значение свойства
};
virtual int OnMethod(lua_State* state, int MethodID, const char* MethodName)
{
switch(MethodID)
{
case 1: // HelloWorld
{
luaPushParam(state,_T("Hello there!"));
return 1;
}
}
};
void BindTo(lua_State* state)
{
binder = new LuaObjectBinder<CDinamic>(state,this);
binder->AddMethod(_T("HelloWorld"),1);
};
}
// создаем экземпляр скриптовой машины
LuaEngine engine(true);
engine.DoFile(_T("scripts/test.lua")); // парсим файл
CDynamic dyn; // наш объект с динамическими методами
dyn.BindTo(engine.Lua()); // настраиваем методы
LuaFunction func(engine.Lua(),_T("SayHello")); // экземпляр функции скрипта
func.AddParam<CDynamic>(dyn.GetBinder()); // передаем объект в скриптовую машину
func.Call(); // вызываем функцию
Где функция в скрипте выглядит примерно так:
function SayHello(obj)
local message = obj:HelloWorld() -- теперь в message находится строка "Hello there"
end
Вот примерно в таком разрезе. Выкорчевывал из проекта, при возникновении вопросов/проблем — велкам.
З.Ы. Да, некоторый код (совсем некоторый, например, развертывание вектора в Lua) — взят из открытых источников, ссылки не помню, но если автор напомнит — с удовольствием вставлю ссылку на него следующим постом.