C++与Lua交互(四)

引言

通过前几篇,我们已经对Lua的C API有了一定的了解,如lua_push*、lua_is*、lua_to*等等。用C++调用Lua数据时,我们主要运用lua_getglobal与lua_push*配合以达到目的。现在我们来试试用Lua调用C++数据。

C++数据类型映射到Lua

C++中数据类型有这么几种:1、内建的int、float等;2、指针,如void *、int *、int (*fun)(int, int)等;3、用户自定义的class、strcut等。Lua中C API支持操作的数据类型有如下:

image

见名知意,从他们的参数,我们就可以看出来他们的作用。比如:lua_pushlightuserdata用来将指针压栈,lua_pushcclosure用来将函数压栈,不一而足。通过这些API,我们可以将C++中的数据结构一一映射到Lua中。

Lua调用C++内置常用数据类型与函数

我们要将一个值,从C++传入Lua,必须有两个步骤:1、值是多少?通过lua_push*将值压入栈顶,此时该值的类型与值的大小已确定;2、用什么名字来引用该值?通过lua_setglobal来用一个名字引用栈顶的值。我们来按照这个步骤,尝试一下将一个变量传入Lua,代码如下:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;
 
extern "C"
{
 
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
};
 
void TestLua2();
int main()
{
    TestLua2();
    return 0;
}
 
void TestLua2()
{
    lua_State *L = luaL_newstate();
    luaopen_base(L); //
    luaopen_table(L); //
    luaopen_package(L); //
    luaopen_io(L); //
    luaopen_string(L); //
    luaL_openlibs(L); //打开以上所有的lib
    
    int valueCPP = 1;
 
    // 将a值压入栈顶
    lua_pushnumber(L, valueCPP);
    // 命名栈顶的值
    lua_setglobal(L, "valueCPP");
 
    string str;
    while (true)
    {
        cout << "输入lua文件路径:" << endl;
        getline(cin, str, '\n');
        if (luaL_loadfile(L, str.c_str())
            || lua_pcall(L, 0, 0, 0) )
        {
            const char * error = lua_tostring(L, -1) ;
            cout << string(error) << endl;
        }
    }
 
}

Lua文件中代码如下:

image

运行结果如下:

image

将函数传入Lua有点复杂,如何确定函数的参数?如何确定函数的返回值及其个数?我们一起去查阅Lua文档,看看文档怎么说。

image 

翻译如下:将一个C函数入栈。这个函数接受一个C函数指针,将其压入作为一个Lua的function类型值压入栈,当这个值被调用时,Lua将调用该值对应的C函数。所有注册在Lua中的C函数必须遵守一个正确的协议来接受参数和返回返回值(查看lua_CFunction)。

image

翻译如下:为了正确的与Lua交互,必须遵守如下协议,该协议定义了参数和返回值的传递方式:一个C函数从Lua的栈中以直接(左边的参数先入栈)顺序来接收参数。因此,当该函数的调用开始时,lua_gettop(L) 得到用来调用该函数的参数个数(注:lua_gettop的作用是得到栈的栈顶元素的索引,即栈的长度)。第一个参数(如果有的话)的值索引为1,最后一个参数的值索引为lua_gettop(L)。为了将返回值返回给Lua,C函数将所有的返回值以直接的顺序(第一个返回值先入栈,以此类推)全部压入栈中,然后将返回值的个数作为值返回(注:此处的返回是return之意,不是传递到Lua中)。所有栈中其他的在返回值个数之下的索引对应的值会被Lua忽略掉,被Lua调用的C函数能传递多个值返回给Lua。例子我就不翻译了。

按照其描述,我们先来试试无参无返回值的函数:

image

image

lua文件内容如下:

image

结果如下:

image

现在,我们定义一个函数,接收两个int类型的参数,返回两个值:和与差。代码如下:

image

image

Lua文件内容如下:

image

结果如下:

image

以上,即可完成从Lua中调用C++函数。这里有个技巧,我们可以在Lua中改变在C++里定义的函数名,即调用lua_setglobal时指定该函数的名字。如果不想改变,我们可以定义一个宏来省略掉这些代码,如下:

image

这样,即可用一行代码,将函数在Lua中声明。

Lua调用C++中自定义类型

我低估了这么做的复杂度,这个内容我想准备一整个篇幅来描述。请静待下一篇。(请点击这里

将C++里的数据打包成dll供Lua调用

前面的操作,本质上还是Lua寄宿在C++中的。现在,我们将C++寄宿在Lua中。我们先回顾一下luaopen_*这一系列的API:

image

这些API的作用是打开相应的库,比如luaopen_string,打开lua的字符串操作库。如果不打开这个库,那么我们就无法使用string.format之类的操作,因为这些操作的定义是在这些库中实现的。我们先来看一下string库这个文件,它位于src目录下lstrlib.c。详细内容我就不贴出来了,主要看看最下面的代码:

image

如果我们要为Lua写扩展模块,我们必须遵循下面几条规定:1、必须有一个luaL_Reg结构,以便将库中所有的函数映射到Lua中,如上图。2、必须定义一个luaopen_*函数并且导出它,*为dll的名字(注意,函数的luaopen_后面的字符必须与dll的命名一致)。3、映射入Lua的函数,其参数的传递,必须按照Lua的规定(就是上文面描述的)。我们先来试试,首先修改CMakeLists.txt,新添加一个项目,用来构建Dll:

project(LuaTest)
 
include_directories(AFTER ${CMAKE_SOURCE_DIR})
 
##########lua静态库
set(LIB_FILES lapi.c lcode.c lctype.c ldebug.c ldo.c ldump.c lfunc.c lgc.c llex.c lmem.c lobject.c lopcodes.c lparser.c lstate.c lstring.c ltable.c ltm.c lundump.c lvm.c lzio.c lauxlib.c lbaselib.c lbitlib.c lcorolib.c ldblib.c liolib.c lmathlib.c loslib.c lstrlib.c ltablib.c loadlib.c linit.c)
source_group("\\libFiles" FILES ${LIB_FILES})
add_library (LuaLib STATIC ${LIB_FILES})
 
 
 
###########c++与lua交互###################
add_executable(LuaWithCPPTest source.cpp)
###########source_group("\\headFiles" FILES source.cpp)
target_link_libraries(LuaWithCPPTest LuaLib)
 
 
############lua解释器###########
add_executable(LuaInterpreter lua.c ${LIB_FILES})
 
############DLL#################
add_library(LuaDll SHARED sourceDll.cpp)
target_link_libraries(LuaDll LuaLib )
 
 
#ADD_DEFINITIONS(-DLUA_LIB -DLUA_BUILD_AS_DLL)
######################################define LUA_LIB##################################################
######################################define LUA_BUILD_AS_DLL#########################################

其中的DLL即是我们新添加的项目,sourceDll.cpp文件内容如下:

 
extern "C"
{
 
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
};
 
 
static int Test(lua_State *L)
{
    lua_pushnumber(L, 1);
    printf("This is a message from c++ dll\n");
    return 0;
}
 
static const struct luaL_Reg mydlllib[] = {
    {"Test", Test},
    {NULL,NULL},
};
 
extern "C" int __declspec(dllexport)luaopen_LuaDll(lua_State *L)
{
    printf("luaopen_LuaDll invoked\n");
    luaL_newlib(L, mydlllib); // 5.2之前使用luaL_register(L, "modulename", modulename);
 
    return 1;
}

得到的项目结构如下:

image

构建好LuaDll后,我们可以在其目录下,运行LuaInterpreter,在解释器中require,具体如下:

image

运行,得到结果如下:

image

我们观察它的输出,第一句是:“luaopen_LuaDll invoked”,这句话表明程序确实走到了我们构建的DLL中,接着输出“multiple Lua VMs detected”,这句话翻译过来就是:检测到多个Lua虚拟机。这就是我前面说的那个坑了!!!网上搜资料,然后自己摸索,总结出的经验如下:自定义扩展Lua的DLL时,要求其DLL构建时,引用的是Lua动态库,而不是静态库(在这里,我们现在引用的是静态库)。如果我们自己的宿主也要支持扩展DLL的话,宿主也必须是引用Lua的动态库。生成Lua动态库时要定义宏LUA_LIB和LUA_BUILD_AS_DLL,以便导出Lua函数符号,如果没定义这两个宏,那么生成DLL时,将没有.lib文件生成。为什么是这两个宏呢?请以这两个宏为关键字搜索src目录下luaconf.h文件,即可找到答案。既然知道原因了,我们来修改CMakeLists.txt,使之生成动态库,并且在其中定义这两个宏,结构如下:

image

重新生成即可,然后在我们的项目中导入,具体步骤及效果如下图:

image 

tst.lua内容如下:

image

至此,我们便可以通过DLL的形式扩展Lua了。

总结

通过DLL,我们可以很方便的扩展Lua,从Lua中调用C++的函数,使Lua更加强大了。C++中的类映射到Lua时,比较复杂,要涉及到Lua的metatable。下一篇,我们将一起探索metatable。

posted @ 2014-03-23 12:01  续写,  阅读(9557)  评论(10编辑  收藏  举报