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支持操作的数据类型有如下:

见名知意,从他们的参数,我们就可以看出来他们的作用。比如: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文件中代码如下:

运行结果如下:

将函数传入Lua有点复杂,如何确定函数的参数?如何确定函数的返回值及其个数?我们一起去查阅Lua文档,看看文档怎么说。
翻译如下:将一个C函数入栈。这个函数接受一个C函数指针,将其压入作为一个Lua的function类型值压入栈,当这个值被调用时,Lua将调用该值对应的C函数。所有注册在Lua中的C函数必须遵守一个正确的协议来接受参数和返回返回值(查看lua_CFunction)。

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


lua文件内容如下:

结果如下:

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


Lua文件内容如下:

结果如下:

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

这样,即可用一行代码,将函数在Lua中声明。
Lua调用C++中自定义类型
我低估了这么做的复杂度,这个内容我想准备一整个篇幅来描述。请静待下一篇。(请点击这里)
将C++里的数据打包成dll供Lua调用
前面的操作,本质上还是Lua寄宿在C++中的。现在,我们将C++寄宿在Lua中。我们先回顾一下luaopen_*这一系列的API:

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

如果我们要为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;
}
得到的项目结构如下:

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

运行,得到结果如下:

我们观察它的输出,第一句是:“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,使之生成动态库,并且在其中定义这两个宏,结构如下:

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

至此,我们便可以通过DLL的形式扩展Lua了。
总结
通过DLL,我们可以很方便的扩展Lua,从Lua中调用C++的函数,使Lua更加强大了。C++中的类映射到Lua时,比较复杂,要涉及到Lua的metatable。下一篇,我们将一起探索metatable。
C++与Lua交互(四)的更多相关文章
- C++与Lua交互(一)
引言 之前做手游项目时,客户端用lua做脚本,基本所有游戏逻辑都用它完成,玩起来有点不爽,感觉"太重"了.而我又比较偏服务端这边(仅有C++),所以热情不高.最近,加入了一个端游项 ...
- C++与Lua交互之配置&交互原理&示例
|Lua 简介 Lua 是一种轻量小巧的脚本语言,也是号称性能最高的脚本语言,它用C语言编写并以源代码形式开放. 某些程序常常需要修改内容,而修改的内容不仅仅是数据,更要修改很多函数的行为. 而修改函 ...
- Win32下 Qt与Lua交互使用(四):在Lua脚本中自由执行Qt类中的函数
话接上篇.通过前几篇博客,我们实现在Lua脚本中执行Qt类中函数的方法,以及在Lua脚本中连接Qt对象的信号与槽. 但是,我们也能发现,如果希望在Lua脚本中执行Qt类的函数,就必须绑定一个真正实现功 ...
- C++与Lua交互(二)
上一篇我们搭建好了整个的项目环境,现在,我们一起探索一下如何将lua寄宿到C++中. 宿主的实现 我们在LuaWithCPPTest项目下,查看Source.cpp代码如下: #include < ...
- C++与Lua交互(三)
通过上一篇的热身,我们对C++调用lua变量有了一个认识,现在让我们再深入一点,去探索一下如何调用lua的函数.表. Lua与宿主通讯的关键--栈 lua是个动态脚本语言,它的数据类型如何映射到C++ ...
- 最优雅的C++跟lua交互.
我先来吐槽一下我们这个项目. 我是做手机游戏的, cocos2dx引擎, lua编码. 这本来是一件很欢快的事情, 因为不用接触C++. C++写久了的人写lua, 就会感觉任督二脉被打通了, 代码写 ...
- C++与lua交互
项目开发的脚本层用的是Lua,引擎用的是C++.但是经理不给开放引擎层的代码.刚好最近项目空闲,安排了学习C++跟Lua的通信. 一.C++与Lua数据交互 数据交互主要是通过C API来实现 首先, ...
- cocos2d-x - C++/Lua交互
使用tolua++将自定义的C++类嵌入,让lua脚本使用 一般过程: 自定义类 -> 使用tolua++工具编译到LuaCoco2d.cpp中 -> lua调用 步骤一:自定义一个C++ ...
- Linux下C/C++和lua交互-Table
本来这些文章都是在我的个人网站www.zhangyi.studio,目前处在备案状态,暂时访问不了,所以搬到这边. 最近这两天需要弄清楚C++和lua间相互调用和数据传递,废话不多说,直接上过程. ...
随机推荐
- C# 函数覆盖总结学习
覆盖类成员:通过new关键字修饰虚函数表示覆盖该虚函数.一个虚函数被覆盖后,任何父类变量都不能访问该虚函数的具体实现.public virtual void IntroduceMyself(){... ...
- Java中Scanner的使用方法
Scanner是SDK1.5新增的一个类,但是使用该类创建一个对象.Scanner reader=new Scanner(System.in); 然后reader对象调用下列方法(函数),读取用户 ...
- exosip
exosip针对UA是对osip进行扩展,oSIP不提供不论什么高速产生请求消息和响应消息的方法,全部请求消息和响应消息的形成必须调用一组sip message api来手动组装完毕,所以作者在osi ...
- [Jest] Track project code coverage with Jest
Jest comes pre-packaged with the ability to track code coverage for the modules you're testing, but ...
- minify合并js和css文件
压缩 JavaScript 和 CSS,是为减少文件大小,节省流量开销:合并 JavaScript 和 CSS,是为了减少请求数量,减轻服务器压力.而这些枯燥又没有技术含量的工作,我们以前通常会手动处 ...
- 僵尸进程 图解 分布式 LINUX内核
http://blog.csdn.net/chdhust/article/details/11872467 服务器进程为何通常fork()两次
- The internals of Python string interning
JUNE 28TH, 2014Tweet This article describes how Python string interning works in CPython 2.7.7. A fe ...
- python 实现接口测试
接口的类型有很多,但是我们经常遇见经常用的就get和post两种.这两种有什么区别呢?个人理解主要是表现在安全性方面. Python代码POST任意的HTTP数据以及使用Cookie的方法,有需要的朋 ...
- 引用自定义的framework
关于静态库引用文件 如果希望你的工程能在未来能导出成静态库,那么在你编写的时候要遵循静态库引用原则,使用这种方式. 注意:这种引用方式必须在你的Products下静态库成黑色时候,才能编译通过. 使用 ...
- POJ 3069 Saruman's Army(贪心)
Saruman's Army Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Sub ...