完成了准备工作之后,就可以开始撸代码了。因为项目也不是很大,就打算大部分都用lua来开发。上一篇已经写了一部分测试代码,但都是塞到一个Main.cpp之中,主要是为了测试配置是否成功。这次的工作就要把测试代码给提取出来,用lua来实现SDL窗口的创建。
Lua的搭建
上一篇是用dostring来执行lua的,项目中不会用这玩意开发的(那要多累啊,排版也不爽)。先右键Ryuujinn工程创建一个Include文件夹来存放.h文件,再创建一个Script文件夹来存放lua文件。文件分类工作做好后,在Script文件夹下创建一个Main.lua,代码也很简单,就是把上一篇写在字符串中的lua代码拷过来,就OK。
Main.lua
print("Hello, lua")
lua的工作,先到此,接下来,在Include文件夹创建LuaClient.h文件,在Source文件夹创建LuaClient.cpp文件。把Main.cpp中跟lua相关的代码(引入头文件和链接Lua库)全部拷贝到LuaClient.h文件中,再新建个类LuaClient。
设计LuaClient为饿汉单例类,因此内部添加了个Garbage类来做LuaClient单例类的内存释放工作。单例的具体实现被我用#pragma region指令给wrap了,如果单例模式都不清楚,请去看设计模式。
接下来添加私有的构造函数和析构函数和一个公有的Start函数,和一个私有的lua虚拟栈变量m_pLuaState。具体的看代码
LuaClient.h
#pragma once
// 引入lua需要的头文件
extern "C"
{
#include
#include
#include
}
// 链接Lua工程生成的静态库
#pragma comment(lib, "Lua.lib")
class LuaClient
{
#pragma region Singleton 单例,利用Garbage类来释放内存
public:
static LuaClient* GetInstance() { return m_instance; }
private:
class Garbage
{
public:
~Garbage()
{
if (m_instance)
delete m_instance;
m_instance = nullptr;
}
};
private:
static LuaClient* m_instance;
static Garbage m_garbage;
#pragma endregion
private:
LuaClient();
virtual ~LuaClient();
public:
/**
* Lua脚本入口
* In -> const char* strStartLuaFile - 入口Lua脚本路径
*/
void Start(const char* strStartLuaFile);
private:
lua_State* m_pLuaState;
};
接下来就是实现了,也很简单,就是将上一篇的代码提取下分开放到构造函数和析构函数中,唯一的变化就是执行lua代码这一处地方。将dostring改成dofile而已。
LuaClient.cpp
#include "Test.h"
#include
LuaClient* LuaClient::m_instance = new LuaClient();
LuaClient::Garbage LuaClient::m_garbage;
LuaClient::LuaClient()
{
std::cout << "LuaClient::LuaClient()" << std::endl;
// 创建虚拟栈
m_pLuaState = luaL_newstate();
// 引入lua的标准库,不然调用print会报错,可以注释掉代码,查看报错
luaL_openlibs(m_pLuaState);
}
LuaClient::~LuaClient()
{
std::cout << "LuaClient::~LuaClient()" << std::endl;
// 关闭虚拟栈
lua_close(m_pLuaState);
}
void LuaClient::Start(const char* strStartLuaFile)
{
if (!m_pLuaState) return;
int result = luaL_dofile(m_pLuaState, strStartLuaFile);
if (LUA_OK != result)
{
std::cout << lua_tostring(m_pLuaState, -1) << std::endl;
return;
}
}
构造函数和析构函数添加输出代码,只是为了测试是否正常执行而已。接下来就是修改Main.cpp文件。因为lua相关的代码已经去掉了,比较简洁了,但看着还是不舒服,把SDL的代码也剪切掉,随便放在一个txt文件中也可以,等会会用到的。
修改后的Main.cpp:
#include "LuaClient.h"
#include
int main()
{
LuaClient::GetInstance()->Start("Script/Main.lua");
system("pause");
return 0;
}
恩,干净多了,距离这一篇要完成后Main.cpp就差一点了。如果运行得到下面的结果,就说明目前的进制很顺利。
接下来就是Lua调用C++的工作了,但在这之前,先来完善LuaClient这个类。来看一下完善后的LuaClent文件, 这里得说下,需要在Source文件夹下添加一个LuaClinetBind.cpp文件。
LuaClient.h
#pragma once
// 引入lua需要的头文件
extern "C"
{
#include
#include
#include
}
// 链接Lua工程生成的静态库
#pragma comment(lib, "Lua.lib")
class LuaClient
{
#pragma region Singleton 单例,利用Garbage类来释放内存
public:
static LuaClient* GetInstance() { return m_instance; }
private:
class Garbage
{
public:
~Garbage()
{
if (m_instance)
delete m_instance;
m_instance = nullptr;
}
};
private:
static LuaClient* m_instance;
static Garbage m_garbage;
#pragma endregion
private:
LuaClient();
virtual ~LuaClient();
public:
/**
* Lua脚本入口
* In -> const char* strStartLuaFile - 入口Lua脚本路径
* const char* strEntryFunction - 入口函数
*/
void Start(const char* strStartLuaFile, const char* strEntryFunction);
/**
* 导出Cpp到lua
*/
void BindCppToLua();
/**
* 输出lua虚拟栈的内容
* In -> bool bPrintTable - 是否输出table表中的内容
*/
void DumpStack(bool bPrintTable = false);
private:
/**
* 添加lua目录
*/
void AddSearchPath();
/**
* 获取Lua Script目录
*/
bool GetScriptPath(char* strOutPath, size_t length);
/**
* 输出lua虚拟栈中table的内容
* In -> int index - lua虚拟栈中的索引
*/
void PrintTable(int index);
private:
lua_State* m_pLuaState;
};
LuaClient.cpp
#include "LuaClient.h"
#include
#include
LuaClient* LuaClient::m_instance = new LuaClient();
LuaClient::Garbage LuaClient::m_garbage;
LuaClient::LuaClient()
{
std::cout << "LuaClient::LuaClient()" << std::endl;
// 创建虚拟栈
m_pLuaState = luaL_newstate();
// 引入lua的标准库,不然调用print会报错,可以注释掉代码,查看报错
luaL_openlibs(m_pLuaState);
AddSearchPath();
}
LuaClient::~LuaClient()
{
std::cout << "LuaClient::~LuaClient()" << std::endl;
// 关闭虚拟栈
lua_close(m_pLuaState);
}
void LuaClient::Start(const char* strStartLuaFile, const char* strEntryFunction)
{
if (!m_pLuaState) return;
BindCppToLua();
// 将全局表压入栈中
//lua_getglobal(m_pLuaState, "_G");
//DumpStack(true);
int result = luaL_dofile(m_pLuaState, strStartLuaFile);
if (LUA_OK != result)
{
std::cout << lua_tostring(m_pLuaState, -1) << std::endl;
return;
}
//DumpStack(true);
//lua_pop(m_pLuaState, 1);
lua_getglobal(m_pLuaState, strEntryFunction);
// 调用指定的入口函数
int argsNum = 0; // 参数数量
int resultsNum = 0; // 返回值数量
int errorFunc = 0; // 错误处理函数在栈中的索引
result = lua_pcall(m_pLuaState, argsNum, resultsNum, errorFunc);
if (LUA_OK != result)
{
std::cout << lua_tostring(m_pLuaState, -1) << std::endl;
return;
}
}
void LuaClient::DumpStack(bool bPrintTable)
{
if (!m_pLuaState) return;
int top = lua_gettop(m_pLuaState);
std::cout << "----------------------Stack Top----------------------" << std::endl;
for (int i = top; i >= 1; --i)
{
int luaType = lua_type(m_pLuaState, i);
switch (luaType)
{
case LUA_TNIL:
{
std::cout << "[" << i << "]: nil" << std::endl;
break;
}// For case LUA_TNIL
case LUA_TBOOLEAN:
{
std::cout << "[" << i << "]: " << (lua_toboolean(m_pLuaState, i) ? "true" : "false") << std::endl;
break;
}// For case LUA_TBOOLEAN
case LUA_TNUMBER:
{
std::cout << "[" << i << "]: " << lua_tonumber(m_pLuaState, i) << std::endl;
break;
}// For case LUA_TNUMBER
case LUA_TSTRING:
{
std::cout << "[" << i << "]: " << lua_tostring(m_pLuaState, i) << std::endl;
break;
}// For case LUA_TSTRING
case LUA_TTABLE:
{
if (bPrintTable)
{
std::cout << "[" << i << "]: " << std::endl;
PrintTable(i);
}
else
std::cout << "[" << i << "]: " << lua_typename(m_pLuaState, luaType)
<< ": " << lua_topointer(m_pLuaState, i) << std::endl;
break;
}// For case LUA_TTABLE
default:
std::cout << "[" << i << "]: " << lua_typename(m_pLuaState, luaType)
<< ": " << lua_topointer(m_pLuaState, i ) << std::endl;
break;
}// For switch (luaType)
}// For end
std::cout << "----------------------Stack Bottom----------------------" << std::endl;
}
void LuaClient::AddSearchPath()
{
char luaScriptPath[1000] = { 0 };
bool bResult = GetScriptPath(luaScriptPath, 1000);
if (!bResult) return;
// 获取lua搜索路径
lua_getglobal(m_pLuaState, "package");
lua_pushstring(m_pLuaState, "path");
lua_gettable(m_pLuaState, -2);
const char* path = lua_tostring(m_pLuaState, -1);
// 拼接新的lua搜索路径
static char newPath[1000] = { 0 };
strcpy_s(newPath, 1000, path);
strcat_s(newPath, 1000, ";");
strcat_s(newPath, 1000, luaScriptPath);
// 重新设置lua搜索路径
lua_pop(m_pLuaState, 1);
lua_pushstring(m_pLuaState, "path");
lua_pushstring(m_pLuaState, newPath);
lua_settable(m_pLuaState, -3);
// 恢复lua栈
lua_pop(m_pLuaState, 1);
}
bool LuaClient::GetScriptPath(char* strOutPath, size_t length)
{
if (nullptr == strOutPath) return false;
// 获取exe的路径
char exePath[1000] = { 0 };
GetModuleFileName(NULL, exePath, 1000);
// 获取当前解决方案的路径
char* findChar = strstr(exePath, "bin");
size_t copyLength = strlen(exePath) - strlen(findChar);
if (length <= copyLength) return false;
memcpy(strOutPath, exePath, copyLength);
// 拼接lua脚本所在的相对路径
strcpy_s(strOutPath + strlen(strOutPath), length - strlen(strOutPath), "Ryuujinn\\Script\\?.lua");
return true;
}
void LuaClient::PrintTable(int index)
{
std::cout << "{" << std::endl;
if (m_pLuaState)
{
// 缓存之前的虚拟栈,因为后面要对虚拟栈进行操作
int oldTop = lua_gettop(m_pLuaState);
// 将需要输出的table压入栈顶
lua_pushvalue(m_pLuaState, index);
// 利用当前key(nil)值在进行遍历查找下一个key和value
lua_pushnil(m_pLuaState);
while (0 != lua_next(m_pLuaState, -2))
{
std::cout << "\t";
// Stack
// from bottom to top from top to bottom
// | value | 3 -1
// +--------+
// | key | 2 -2
// +--------+
// | table | 1 -3
// +========+
int luaType = lua_type(m_pLuaState, -2);
// 对key值进行处理
{
switch (luaType)
{
case LUA_TNIL:
{
std::cout << "nil";
break;
}// For case LUA_TNIL
case LUA_TBOOLEAN:
{
std::cout << (lua_toboolean(m_pLuaState, -2) ? "true" : "false");
break;
}// For case LUA_TBOOLEAN
case LUA_TNUMBER:
{
std::cout << lua_tonumber(m_pLuaState, -2);
break;
}// For case LUA_TNUMBER
case LUA_TSTRING:
{
std::cout << lua_tostring(m_pLuaState, -2);
break;
}// For case LUA_TSTRING
default:
std::cout << lua_typename(m_pLuaState, luaType)
<< ": " << lua_topointer(m_pLuaState, -2);
break;
}// For switch (luaType)
}// Process key
std::cout << "\t\t";
// 对Value值进行处理
{
luaType = lua_type(m_pLuaState, -1);
switch (luaType)
{
case LUA_TNIL:
{
std::cout << "nil" << std::endl;
break;
}// For case LUA_TNIL
case LUA_TBOOLEAN:
{
std::cout << (lua_toboolean(m_pLuaState, -1) ? "true" : "false") << std::endl;
break;
}// For case LUA_TBOOLEAN
case LUA_TNUMBER:
{
std::cout << lua_tonumber(m_pLuaState, -1) << std::endl;
break;
}// For case LUA_TNUMBER
case LUA_TSTRING:
{
std::cout << lua_tostring(m_pLuaState, -1) << std::endl;
break;
}// For case LUA_TSTRING
default:
std::cout << lua_typename(m_pLuaState, luaType)
<< ": " << lua_topointer(m_pLuaState, -1) << std::endl;
break;
}// For switch (luaType)
}// Process value
// 将Value从虚拟栈继续弹出,利用当前key值在进行遍历查找下一个key和value
lua_pop(m_pLuaState, 1);
}
// 恢复成之前缓存的虚拟栈
lua_settop(m_pLuaState, oldTop);
}
std::cout << "}" << std::endl;
}
LuaClinetBind.cpp
#include "LuaClient.h"
void LuaClient::BindCppToLua()
{
}
新加的几个函数中DumpStack和PrintTable函数我就不细说了,因为没啥好说的。如果对lua虚拟栈比较清楚的话,自己也可以写,网上搜也有一堆,如果不清楚的话,就需要从lua和C++通信开始说起,有点长了。从栈顶往栈底显示只是出于个人习惯。如果这篇篇幅不长的话,我可以试着在最下面来介绍一下。
AddSearchPath和GetScriptPath暂时先不讲,因为现在还用不上。BindCppToLua函数注释上也写明白了功能,但现在没有导出的Cpp,所以是个空函数。那就讲修改过后的Start函数。在讲之前,先修改Main.lua脚本。
修改后Main.lua
function Main()
print("Main")
end
用lua编译器等工具执行(不能用Ryuujinn工程来执行,会报错,如果一步一步来写改变的话,篇幅太长),没有看到控制台输出Main这个字符串的,如果想看到输出怎么办?最简单的方法是在Main.lua下添加调用就好,比如这样:
再次修改后Main.lua
function Main()
print("Main")
end
Main()
但是我偏不想这样调用,想通过Cpp来控制调用呢?这就是修改后Start函数的功能,先来看注释,const char* strEntryFunction指的是入口函数,就是说想调用lua的那一个函数(根据函数名来调用)。这里先恢复成刚才只有一个Main函数的Main.lua脚本。
现在来看修改后的Start函数。dofile之后新增加的代码就只有lua_getglobal和lua_pcall这2个API,这里简单介绍下这2个API,lua_getglobal就是根据第2个参数从全局表查找对应的值,如果找到就将其压入栈中,没有找到就将nil压入栈中。lua_pcall就更简单明了了,就是调用一个函数,3个参数都有注释,就不再解释了。现在应该明白dofile之后的代码功能了吧:根据函数名从全局表中查找函数的指针,然后调用查找到的函数。
我这里还添加了总共4行辅助代码,都被我注释了,可以取消注释,从控制台查看变化。这里可以解释下具体的实现:前2句注释代码实现的功能是将lua中的全局表压入栈中,输入lua全局表的内容。后2句注释代码实现的功能是输出dofile后lua全局表的内容,再利用lua_pop这个API实现堆栈平衡。你会发现在全局表中多了一个键值:key是Main,Value是一个函数。
好了,上面已经完美实现调用Lua脚本这个功能了(看起来),接下来就需要实现Cpp导出lua的功能了。虽然可以直接将SDL相关的方法导出到lua,但查找API就不太方便了,因此添加了一个帮助类Renderer。在Include文件夹中添加Renderer.h方法,在Source文件夹中添加Renderer.cpp方法,并且在Source文件夹中添加一个新的文件夹:Wrap文件夹(里面存放导出实现Cpp导出lua功能的文件),并且添加第一个Cpp导出Lua的文件RendererWrap.cpp。
Renderer.h
#pragma once
#define SDL_MAIN_HANDLED
// 引入SDL需要的头文件
#include
#include
// 链接SDL静态库
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
#pragma comment(lib, "SDL2_image.lib")
class Renderer
{
public:
static bool Init(Uint32 flag);
static SDL_Window* CreateWindow(const char* title, int posX, int posY, int width, int hegiht, Uint32 flags);
static SDL_Renderer* CreateRenderer(SDL_Window* pWindow, int index, Uint32 flags);
static void DestroyWindow(SDL_Window* pWindow);
static void DestroyRenderer(SDL_Renderer* pRenderer);
static void Quit();
};
struct lua_State;
namespace LuaWrap
{
void RegisterRenderer(lua_State* L);
}
Renderer.cpp
#include "Renderer.h"
bool Renderer::Init(Uint32 flag)
{
return 0 == SDL_Init(flag);
}
SDL_Window* Renderer::CreateWindow(const char* title, int posX, int posY, int width, int hegiht, Uint32 flags)
{
return SDL_CreateWindow(title, posX, posY, width, hegiht, flags);
}
SDL_Renderer* Renderer::CreateRenderer(SDL_Window* pWindow, int index, Uint32 flags)
{
return SDL_CreateRenderer(pWindow, index, flags);
}
void Renderer::DestroyWindow(SDL_Window* pWindow)
{
if (pWindow)
SDL_DestroyWindow(pWindow);
}
void Renderer::DestroyRenderer(SDL_Renderer* pRenderer)
{
if (pRenderer)
SDL_DestroyRenderer(pRenderer);
}
void Renderer::Quit()
{
SDL_Quit();
}
Renderer类的API其实很简单,就是包装了一下SDL的API,完全没有啥内容。这里解释下,我不会仔细介绍SDL相关的API(网上一搜就有很多),因为我没有这个资格介绍(学习SDL也没多久,撑死1个月的时间),而且这都是官方定义好的,就像Window窗口的初始化,RegisterWindow,CreateWindow及ShowWindow等这些API,只要会用就好。
RendererWrap.cpp
#include "Renderer.h"
#include
#include
int Init(lua_State* L)
{
Uint32 flag = (Uint32)lua_tointeger(L, 1);
lua_pushboolean(L, Renderer::Init(flag));
return 1;
}
int CreateWindow(lua_State* L)
{
const char* title = lua_tostring(L, 1);
int posX = (int)lua_tointeger(L, 2);
int posY = (int)lua_tointeger(L, 3);
int width = (int)lua_tointeger(L, 4);
int hegiht = (int)lua_tointeger(L, 5);
Uint32 flag = (Uint32)lua_tointeger(L, 6);
lua_pushlightuserdata(L, Renderer::CreateWindow(title, posX, posY, width, hegiht, flag));
return 1;
}
int CreateRenderer(lua_State* L)
{
SDL_Window* pWindow = (SDL_Window*)lua_touserdata(L, 1);
int index = (int)lua_tointeger(L, 2);
Uint32 flag = (Uint32)lua_tointeger(L, 3);
lua_pushlightuserdata(L, Renderer::CreateRenderer(pWindow, index, flag));
return 1;
}
int DestroyWindow(lua_State* L)
{
SDL_Window* pWindow = (SDL_Window*)lua_touserdata(L, 1);
Renderer::DestroyWindow(pWindow);
return 0;
}
int DestroyRenderer(lua_State* L)
{
SDL_Renderer* pRenderer = (SDL_Renderer*)lua_touserdata(L, 1);
Renderer::DestroyRenderer(pRenderer);
return 0;
}
int Quit(lua_State* L)
{
Renderer::Quit();
return 0;
}
int RendererGet(lua_State* L)
{
const char* strKey = lua_tostring(L, 2);
luaL_getmetatable(L, "RendererMetaTable");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (lua_isnil(L, -1))
std::cout << "Renderer don't have the field: " << strKey << std::endl;
return 1;
}
int RendererSet(lua_State* L)
{
luaL_getmetatable(L, "RendererMetaTable");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
lua_rawset(L, -3);
}
else
{
if(LUA_TFUNCTION == lua_type(L, -1))
std::cout << "The action is not allowed." << std::endl;
else
{
lua_pop(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
lua_rawset(L, -3);
}
}
return 0;
}
void LuaWrap::RegisterRenderer(lua_State* L)
{
lua_newtable(L);
luaL_newmetatable(L, "RendererMetaTable");
lua_pushstring(L, "__index");
lua_pushcfunction(L, RendererGet);
lua_rawset(L, -3);
lua_pushstring(L, "__newindex");
lua_pushcfunction(L, RendererSet);
lua_rawset(L, -3);
lua_pushstring(L, "Init");
lua_pushcfunction(L, Init);
lua_rawset(L, -3);
lua_pushstring(L, "CreateWindow");
lua_pushcfunction(L, CreateWindow);
lua_rawset(L, -3);
lua_pushstring(L, "CreateRenderer");
lua_pushcfunction(L, CreateRenderer);
lua_rawset(L, -3);
lua_pushstring(L, "DestroyWindow");
lua_pushcfunction(L, DestroyWindow);
lua_rawset(L, -3);
lua_pushstring(L, "DestroyRenderer");
lua_pushcfunction(L, DestroyRenderer);
lua_rawset(L, -3);
lua_pushstring(L, "Quit");
lua_pushcfunction(L, Quit);
lua_rawset(L, -3);
lua_setmetatable(L, -2);
lua_setglobal(L, "Renderer");
}
之前LuaClinetBind.cpp也要修改下:
#include "LuaClient.h"
#include "Renderer.h"
void LuaClient::BindCppToLua()
{
LuaWrap::RegisterRenderer(m_pLuaState);
}
估计有不少人发现Renderer.h文件中命名空间LuaWrap下的RegisterRenderer函数的实现没有在Renderer.cpp中,没错,实现在RendererWrap.cpp中。因为Cpp导出lua的核心就在这里,为了逻辑清晰,特意分离出来。先来说下Cpp函数导入到lua的结构必须是int FunctionName(lua_State*)结构,返回值int表示有多少个返回值,这个也许好理解,但参数呢?需要从虚拟栈中取。
就拿CreateWindow来举例,该函数有6个参数,先从lua调用开始说起:
1.lua调用lua的CreateWindow函数,按从左到右的顺序将参数依次压入栈中,所以最左边的参数在栈底,最右边的参数在栈顶。
2.lua的CreateWindow函数根据Cpp函数的地址调用到了Cpp的CreateWindow函数
3.根据虚拟栈取出需要的参数。lua的索引关系是这样的,从栈底到栈顶,索引是正的,按照顺序排列,比如1,2,3,4...n这样,但从栈顶到栈底,索引是负的,按照逆序排列,比如-1,-2,-3,-4...-n这样。所以可以理解为栈顶即等于-1,也等于n,栈底即等于1,也等于-n。
4.因为有一个返回值,所以需要将Renderer::CreateWindow(也就是SDL_CreateWindow返回的SDL_Window*)压入栈中
5.lua从栈中从栈顶按返回值数量取值
以上5步就完成了一次lua调用Cpp的功能,是不是很简单,如果搞清楚lua的栈后,都是小case了。
但在lua调用Cpp函数之前,我们需要把Cpp函数注册到lua内存中,这就是RegisterRenderer函数实现的功能了。逻辑大致是这样的:
1.创建一个table,后面命名为Renderer(跟C++类名相同,方便调用)。
2.创建一个名称为RendererMetaTable的元表。
3.设置元表的__index和__newindex元方法
4.注入C++中Renderer的函数到RendererMetaTable元表中。
5.设置RendererMetaTable元表为Renderer表的元表。
其中调用lua_rawset是不想触发__newindex元方法,稍微加快了速度。不想把C++的函数注册到Renderer中,而注册到元表中的原因是我想不让人在lua中重载了注册到lua中的C++函数。
举个例子:
1.先把RendererSet这个函数中的else代码段改成if中的一样
int RendererSet(lua_State* L)
{
luaL_getmetatable(L, "RendererMetaTable");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
lua_rawset(L, -3);
}
else
{
lua_pop(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
lua_rawset(L, -3);
}
//std::cout << "The action is not allowed." << std::endl;
return 0;
}
2.在lua中重载Renderer.Init函数,将其改成lua中随便写的一个Test函数。
local function Test(flag)
print("Lua flag: "..tostring(flag))
end
function Main()
print("Lua start...")
local flag = "00000020"
flag = tonumber(flag, 16)
Renderer.Init(flag)
print(Renderer.Init)
Renderer.Init = Test
Renderer.Init(flag)
print(Renderer.Init)
print("Lua end...")
end
3.调用Renderer.Init函数。
你会发现你调用的不是C++中的函数,而是刚才重新赋值后的Test函数(lua中的)。这个结果不是我所期望的,所以必须控制,但__newindex元方法的特性是如果已存在的索引键,则会进行赋值,而不调用元方法__newindex,因此不想被修改的函数都注册到元表中。
但为了可以扩展,所以将__newindex元方法改成RendererWrap中的RendererSet函数一样,可以添加新的键值对,但不可以修改已有的。比如将Main.lua改成这样:
local function Test(flag)
print("Lua flag: "..tostring(flag))
end
function Main()
print("Lua start...")
local flag = "00000020"
flag = tonumber(flag, 16)
Renderer.Init(flag)
print(Renderer.Init)
Renderer.Init = Test
Renderer.Init(flag)
print(Renderer.Init)
print("Lua Add Test field in Renderer table...")
Renderer.Test = Test
Renderer.Test(flag)
print("Lua end...")
end
结果如下图,可以看到在尝试修改Renderer.Init函数的时候,报了个Error: The action is not allowed.但是我们有为Renderer表添加了一个新的键(Test字符串)值(Test函数)对,执行后没有问题。
介绍C++导出类到Lua花了不少时间,现在开始回归当前的目标,Lua创建SDL窗口。继续在Script添加一个Game.lua,内容暂时先这样:
Game.lua
local GameBase =
{
pSDLWindow = nil,
pSDLRenderer = nil
}
local Game = setmetatable({}, { __index = GameBase })
print("Game")
return Game
Main.lua
function Main()
print("Main")
--将16进制字符串转换成10进制数字
local flag = "00000020"
flag = tonumber(flag, 16)
Renderer.Init(flag)
--这里需要设置lua查找路径
gGame = require("Game")
gGame.pSDLWindow = Renderer.CreateWindow("Test", 100, 100, 500, 500, 0)
gGame.pSDLRenderer = Renderer.CreateRenderer(gGame.pSDLWindow, -1, 0)
--while循环中添加print函数是为了避免执行太快,窗口一闪而过
local count = 1
while count < 10000 do
count = count + 1
print(count)
end
Renderer.DestroyRenderer(gGame.pSDLRenderer)
Renderer.DestroyWindow(gGame.pSDLWindow)
Renderer.Quit()
end
Game.lua的代码很简单,没啥好说的,Main.lua脚本也差不多,跟C++写代码没多少区别。这里有3处地方说一下。2处比较简单(代码中也已经有详细的注释了),1处比较麻烦,涉及到之前没用讲的LuaClient类中AddSearchPath和GetScriptPath函数。如果之前没有设置好lua文件查找路径的话,代码执行到require("Game")会报错,控制台上会显示处一堆路径。原因其实很简单,Game.lua不在lua查找路径中,找不到该文件,所以require失败。这里有篇文章是讲require路径的,可以看下。
好,回到LuaClient类中AddSearchPath函数中,这个函数在构造函数调用中,是一开始就已经执行了的,调用顺序就不要关心了。注释也比较清楚,只是有一个要点是不要传局部变量(字符串)到lua中,lua其实是没有拷贝内容的,只是拷贝了地址,过了作用域后,就变成野指针了,会报错。GetScriptPath函数功能就是定位lua脚本路径的,因为我之前修改过exe的生成路径,所以代码是这样的,如果exe路径生成路径跟我不一样,需要自己去定位lua脚本路径。
源码下载地址