8. Lua 与 C/C++ 交互

您所在的位置:网站首页 lua编译成c 8. Lua 与 C/C++ 交互

8. Lua 与 C/C++ 交互

2023-09-28 09:24| 来源: 网络整理| 查看: 265

绑定Lua和C/C++的库 CPPlua tolua tolua++ luawrapper luabind luaplus Lua调用C/C++ 简介

Lua(念“鲁啊”)作为一门发展成熟的脚本语言,正在变得越来越流行。它也可以作为和C/C++执行脚本交互的语言。并且Lua的整个库很小,Lua 5.1版本整个静态链接的lua.dll才164KB,所以Lua很轻量,特别适合轻量级脚本嵌入。

这节要讲Lua和C/C++的交互——Lua通过C/C++导出的dll来调用。

LUA调用C文件中的函数方法

C中注册函数 lua_pushcfunction(l, l_sin); //注册在lua中使用的c函数l_sin lua_setglobal(l, "mysin"); //设定绑定到lua中的名字为mysin C中提供的函数其定义要符合: typedef int function(lua_State *L) 准备工作

安装完Lua,需要在Visual Studio中配置Lua路径,使得你的编译器能搜寻到。关于VS2010的配置,见我的博文《VS2010 C++目录配置》一文。完成后新建一个Dll工程便可以了。

我们用一个在Lua中显示Windows对话框的程序来简要介绍一下,程序虽小,但五脏俱全。程序如下:

// 将一些有用的Win32特性导出 // 以便在Lua中使用 extern "C" { #include #include #include #pragma comment(lib, "lua.lib") }; #include #include using namespace std; static const char* const ERROR_ARGUMENT_COUNT = "参数数目错误!"; static const char* const ERROR_ARGUMENT_TYPE = "参数类型错误!"; // 发生错误,报告错误 void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo) { lua_pushstring(luaEnv, pszErrorInfo); lua_error(luaEnv); } // 检测函数调用参数个数是否正常 void CheckParamCount(lua_State* luaEnv, int paramCount) { // lua_gettop获取栈中元素个数. if (lua_gettop(luaEnv) != paramCount) { ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT); } } // 显示Windows对话框. // @param [in] pszMessage string 1 // @param [in] pszCaption string 2 extern "C" int ShowMsgBox(lua_State* luaEnv) { const char* pszMessage = 0; const char* pszCaption = 0; // 检测参数个数是否正确. CheckParamCount(luaEnv, 2); // 提取参数. pszMessage = luaL_checkstring(luaEnv, 1); pszCaption = luaL_checkstring(luaEnv, 2); if (pszCaption && pszMessage) { ::MessageBox( NULL, pszMessage, pszCaption, MB_OK | MB_ICONINFORMATION ); } else { ErrorMsg(luaEnv, ERROR_ARGUMENT_TYPE); } // 返回值个数为0个. return 0; } // 导出函数列表. static luaL_Reg luaLibs[] = { {"ShowMsgBox", ShowMsgBox}, {NULL, NULL} }; // Dll入口函数,Lua调用此Dll的入口函数. extern "C" __declspec(dllexport) int luaopen_WinFeature(lua_State* luaEnv) { const char* const LIBRARY_NAME = "WinFeature"; luaL_register(luaEnv, LIBRARY_NAME, luaLibs); return 1; } 程序解析

首先我们包含Lua的头文件,并链入库文件。注意:Lua的头文件为C风格,所以用external “C”来含入。在此例中,我们最终的导出函数为“ShowMsgBox”。

每一个导出函数的格式都为:

extern “C”int Export_Proc_Name(luaState* luaEnv);

其中,luaState*所指的结构中包含了Lua调用此Dll时必备的Lua环境。那么Lua向函数传递参数该怎么办呢?实际上是用luaL_check[type]函数来完成的。如下:

const char* pHelloStr = luaL_checkstring(luaEnv, 1); double value = luaL_checknumber(luaEnv, 2); int ivalue = luaL_checkint(luaEnv, 3);

luaL_check系列函数的第二个参数是Lua调用该函数时传递参数从坐到右的顺序(从1开始)。

然后我们看到,static的一个luaL_Reg的结构数组中包含了所有要导出的函数列表。最后通过luaopen_YourDllName的一个导出函数来完成一系列操作。YourDllName就是你最终的Dll的名字(不含扩展名)。因为你在Lua中调用此Dll时,Lua会根据此Dll名字找luaopen_YourDllName对应的函数,然后从此函数加载该Dll。

Dll入口函数格式如下:

extern "C" __declspec(dllexport) int luaopen_WinFeature(lua_State* luaEnv) { const char* const LIBRARY_NAME = "WinFeature"; luaL_register(luaEnv, LIBRARY_NAME, luaLibs); return 1; }

我们通过luaL_register将LIBRARY_NAME对应的库名,以及luaL_Reg数组对应的导出列表来注册到lua_State*对应的Lua环境中。

Lua调用

那么我们要如何调用该Dll呢?首先,把该Dll放到你的Lua能搜寻到的目录——当前目录、Lua安装目录下的clibs目录,然后通过require函数导入。

因为Lua中如果你的函数调用参数只有一个,并且该参数为字符串的话,函数调用时的括号是可以省略的,所以:require(“YourLibName”)和requir“YourLibName”都是合法的。我们把刚刚生成的WinFeature.dll文件拷贝到C盘下,然后在C盘启动Lua。示例如下:

> require "WinFeature" > for k, v in pairs(WinFeature) do >> print(k, v) >> end ShowMsgBox functon:0028AB90 >

可以看到,函数调用方式都是“包名.函数名”,而包名就是你的Dll的名字。我们可以用下面的方式查看一个包中的所有函数:

for k, v in pairs(PackageName) do print(k, v) end

然后我们调用WinFeature.ShowMsgBox函数:

> WinFeature.ShowMsgBox("Hello, this is a msgBox", "Tip")

会弹出对话框显示内容为"Hello, this is a msgBox"和标题为"Tip"。

Lua堆栈详解

唔,那么lua_State结构如何管理Lua运行环境的呢?Lua又是如何将参数传递到C/C++函数的呢?C/C++函数又如何返回值给Lua呢?……这一切,都得从Lua堆栈讲起。

Lua在和C/C++交互时,Lua运行环境维护着一份堆栈——不是传统意义上的堆栈,而是Lua模拟出来的。Lua与C/C++的数据传递都通过这份堆栈来完成,这份堆栈的代表就是lua_State*所指的那个结构。

堆栈结构解析

堆栈通过lua_push系列函数向堆栈中压入值,通过luaL_check系列从堆栈中获取值。而用luaL_check系列函数时传递的参数索引,比如我们调用WinFeature.ShowMsgBox(“Hello”, “Tip”)函数时,栈结构如下:

栈顶 "Tip" 2或者-1 "Hello" 1或者-2 栈底

其中,参数在栈中的索引为参数从左到右的索引(从1开始),栈顶元素索引也可以从-1记起。栈中元素个数可以用lua_gettop来获得,如果lua_gettop返回0,表示此栈为空。(lua_gettop这个函数名取得不怎么样!)

提取参数

luaL_check系列函数在获取值的同时,检测这个值是不是符合我们所期望的类型,如果不是,则抛出异常。所有这个系列函数如下:

luaL_checkany —— 检测任何值(可以为nil) luaL_checkint —— 检测一个值是否为number(double),并转换成int luaL_checkinteger —— 检测一个值是否为number(double),并转换成lua_Integer(prtdiff_t),在我的机子上,ptrdiff_t被定义为int luaL_checklong —— 检测一个值是否为number(double),并转换成long luaL_checklstring —— 检测一个值是否为string,并将字符串长度传递在[out]参数中返回 luaL_checknumber —— 检测一个值是否为number(double) luaL_checkstring —— 检测一个值是否为string并返回 luaL_checkudata —— 检测自定义类型 传递返回值

当我们要传递返回值给Lua时,可以用lua_push系列函数来完成。每一个导出函数都要返回一个int型整数,这个整数是你的导出函数的返回值的个数。而返回值通过lua_push系列函数压入栈中。比如一个Add函数:

extern “C” int Add(lua_State* luaEnv) { CheckParamCount(luaEnv, 2); double left = luaL_checknumber(luaEnv, 1); double right = luaL_checknumber(luaEnv, 2); double result = left + right; lua_pushnumber(luaEnv, result); return 1; }

可以看出,我们用lua_pushnumber把返回值压入栈,最后返回1——1代表返回值的个数。lua_push系列函数如下:

lua_pushboolean —— 压入一个bool值 lua_pushcfunction —— 压入一个lua_CFunction类型的C函数指针 lua_pushfstring —— 格式化一个string并返回,类似于sprintf lua_pushinteger —— 压入一个int lua_pushlightuserdata —— 压入自定义数据类型 lua_pushliteral —— 压入一个字面值字符串 lua_pushlstring —— 压入一个规定长度内的string lua_pushnil —— 压入nil值 lua_pushnumber —— 压入lua_Number(double)值 lua_pushstring —— 压入一个string lua_pushthread —— 压入一个所传递lua_State所对应的线程,如果此线程是主线程,则返回1 lua_pushvalue —— 将所传递索引处的值复制一份压入栈顶 lua_pushvfstring —— 类似lua_pushfstring

通过这些函数,我们可以灵活的使用C/C++的高性能特性,来导出函数供Lua调用。

C/C++调用Lua脚本 简介

C调用LUA文件中的函数方法

lua_getglobal(L, ) //获取lua中的函数 lua_push*() //调用lua_push系列函数,把输入参数压栈。例如lua_pushnumber(L, x) lua_pcall(L, , , )

例如:

lua_settop(m_pLua,0); lua_getglobal(m_pLua,"mainlogic"); lua_pushlstring(m_pLua,(char*)msg.getBuf(),msg.size()); int ret = 0; ret = lua_pcall(m_pLua,1,4,0);

上一节介绍了如何在Lua中调用C/C++代码,本节介绍如何在C/C++中调用Lua脚本。本节介绍一个例子,通过Lua来生成一个XML格式的便签。便签格式如下:

发送方姓名 接收方姓名 发送时间 便签内容

我们通过C/C++来输入这些信息,然后让Lua来生成这样一个便签文件。

Lua代码 xmlHead = '\n' -- Open note file to wriet. function openNoteFile(fileName) return io.open(fileName, "w") end -- Close writed note file. function closeNoteFile(noteFile) noteFile:close() end function writeNestedLabel(ioChanel, label, nestCnt) if nestCnt == 0 then ioChanel:write(label) return end for i = 1, nestCnt do ioChanel:write("\t") end ioChanel:write(label) end function generateNoteXML(fromName, toName, msgContent) local noteFile = openNoteFile(fromName .. "_" .. toName .. ".xml") if not noteFile then return false end noteFile:write(xmlHead) noteFile:write("\n") local currNestCnt = 1 writeNestedLabel(noteFile, "", currNestCnt) noteFile:write(fromName) writeNestedLabel(noteFile, "\n", 0) writeNestedLabel(noteFile, "", currNestCnt) noteFile:write(toName) writeNestedLabel(noteFile, "\n", 0) local sendTime = os.time() writeNestedLabel(noteFile, "", currNestCnt) noteFile:write(sendTime) writeNestedLabel(noteFile, "\n", 0) writeNestedLabel(noteFile, "", currNestCnt) noteFile:write(msgContent) writeNestedLabel(noteFile, "\n", 0) noteFile:write("\n") closeNoteFile(noteFile) return true end

我们通过openNoteFile和closeNoteFile来打开/关闭XML文件。generateNoteXML全局函数接受发送方姓名、接收方姓名、便签内容,生成一个XML便签文件。便签发送时间通过Lua标准库os.time()函数来获取。writeNestedLabel函数根据当前XML的缩进数目来规范XML输出格式。此文件很好理解,不再赘述。

C++调用Lua脚本 extern "C" { #include #include #include #pragma comment(lib, "lua.lib") }; #include #include using namespace std; // 初始化Lua环境. lua_State* initLuaEnv() { lua_State* luaEnv = lua_open(); luaopen_base(luaEnv); luaL_openlibs(luaEnv); return luaEnv; } // 加载Lua文件到Lua运行时环境中 bool loadLuaFile(lua_State* luaEnv, const string& fileName) { int result = luaL_loadfile(luaEnv, fileName.c_str()); if (result) { return false; } result = lua_pcall(luaEnv, 0, 0, 0); return result == 0; } // 获取全局函数 lua_CFunction getGlobalProc(lua_State* luaEnv, const string& procName) { lua_getglobal(luaEnv, procName.c_str()); if (!lua_iscfunction(luaEnv, 1)) { return 0; } return lua_tocfunction(luaEnv, 1); } int main() { // 初始化Lua运行时环境. lua_State* luaEnv = initLuaEnv(); if (!luaEnv) { return -1; } // 加载脚本到Lua环境中. if (!loadLuaFile(luaEnv, ".\\GenerateNoteXML.lua")) { cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3