Lua C++ bindings (2)

这是关于 lua-cpp 的第二篇博客,上一篇在 这里。这里主要是根据 git 的提交日志回忆一下实现过程,有点意识流,想到哪说那了。btw,代码在 这里

设置和获取变量

首先是一般的变量的获取。返回数值比较好办,字符串也可以返回 std::string,但是像 table 和函数这样的类型就不好直接返回了。一个直接的想法就是在外面封装一层,记下 lua_State 和对应的位置。刚开始就是直接记的栈内的 index,但是当某个对象析构之后在这个对象之后创建的对象的 index 都要改变,因此这个方法行不通。后来翻 API 的时候发现可以用 luaL_ref() 和 luaL_unref(),问题就解决了。

一个不算太重要但是也比较基础的问题是这些类型的生存期问题,其实也就是 lua_State 的作用域问题。Lua 本来就要求使用者自己保证,但是这个保证有函数调用来强制(Lua 的函数都需要提供 lua_State),如果封装成 C++ 后每个函数都要求传入 LuaState 就太不专业了,于是想到在每个类里加一个 std::shared_ptr,然后判断引用计数来决定是否析构;后来在查手册的时候发现 shared_ptr 居然还支持自定义析构函数,而 lua_close() 正好符合要求的析构函数形式,这样

阅读全文…

Lua C++ bindings (1)

好几个项目都用了 Lua 作为配置文件语言,但是一直都用官方提供的 C API,写起来十分繁琐。本来大部分的配置文件都是 key-value 式的配置,简单地封装一下就足够了,但是本着完美主义的倾向一直都觉得要么就别封装,要么就写一个功能完善的,而函数调用和自定义类导出这部分一直没想明白怎么弄,因此迟迟没动手。至于为什么不直接使用 LuaBind 这样强大的东东,一方面是不喜欢 LuaBind 依赖 Boost 太臃肿,另一方面也是趁着这个机会学习一下。

从去年 10 月左右就开始磕磕碰碰地写,三两下就把变量部分搞定了,不过之后就停滞了。直到去年年底的时候看了下 C++11 提供的变长参数模板有了些头绪,于是写成了现在这样,总的来说已经基本可用,心里的成就感满满的,于是写点东西记录一下,也算是打个广告。关于这个库准备写两篇博客,第一篇从使用者的角度介绍一下使用方法,第二篇从开发者的角度写写演变过程及设计原则。这里介绍的是第一个提交的版本,后续有变化的话不再同步更新博客了,只会更新相关的文档。

哦,忘了打广告,代码放在 github 上,猛点 这里。需要 C++11 的支持,使用 g++ 编译时加上选项 -std=c++11。

变量设置

先来看一个“Hello, world!”式的程序:

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace 

阅读全文…

使用 C 编写 Lua 模块

Lua 作为一种小巧的语言,一般都是嵌入到 C/C++ 中作为扩展语言,但是也可以作为独立的脚本语言使用,并且可以使用 C/C++ 编写扩展模块。在参考资料 [1] 中有怎样用 C/C++ 编写模块的介绍,但是比较零散,也不是很详细,所以在这里整理一下。

这里使用的 Lua 版本是 5.2.3,系统是 Debian 7。

Hello, world!

不废话,还是先看一下经典的 “Hello, world!” 例子。

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_hello(lua_State* l)
{
    printf("Hello, world!\n");
    return 0;
}

static const 

阅读全文…

使用 Lua 扩展你的程序 (1)

Lua(参考资料[1])是巴西里约热内卢天主教大学的一个研究小组于 1993 年开发的。小组主要由 Roberto Ierusalimschy, Waldemar Celes 和 Luiz Henrique de Figueiredo 组成,最开始的原型为了处理数据而开发的一种脚本语言,后来逐渐演变成现在的样子。Lua 虽然小巧,但是也包含分支循环结构,动态类型,闭包等特性,有兴趣的可以看看参考资料[2]。只用 Lua 提供的原生 C API 写出来的程序和汇编差不多,需要精确控制栈的内容。为了解放程序员,网上有一些封装好了的 Lua 库,用起来也很方便。

这里主要记录自己使用 Lua 对程序进行扩展的一些尝试,用到相关的 Lua 特性时会展开介绍一下。特别说一下,虽然主要使用 Lua 作为配置文件的语言,但是用其它的语言(Python, Perl, Ruby, …)也可以达到同样的效果,只要程序能够解析所使用的语言,甚至 XML 也可以用来实现后文提到的一些功能,不过前提是你的解析器要支持 XML 来定义分支和循环等功能 :)

简单的 key/value 配置文件

需要配置参数取值的一个常见例子是数据源问题。例如程序需要从一个文件获取运行时需要的数据,测试和正式发布所使用的数据不一样,为了验证程序的正确性也准备了多套不同的数据。为了方便测试,程序使用了一个配置文件 …

阅读全文…

programming in lua (12)

第 9 章和第 30 章部分内容,都是讲线程相关的。

基本概念

lua 中的 coroutine 和我们通常说的“线程”概念相近,但是这里的 coroutine 并不是真正的线程,在某个时刻只有一个 coroutine 在执行。lua 将 coroutine 的所有函数都放在一个叫“coroutine”的 table 中,下面来看一下这些函数。

co = coroutine.create(function () print("hello") end)
print(co) -- thread: 0x11fa810
print(coroutine.status(co)) -- suspended
coroutine.resume(co) -- hello
print(coroutine.status(co)) -- dead

create() 接收一个函数作为参数,创建一个对应的 …

阅读全文…

programming in lua (11)

第 29 章和 31 章。这两章都是讲资源管理的,就放一起看了。

userdata 可能是用户自己分配的一段内存(内存里可能有一个域是文件描述符,可能是一个指向另外一段内存的指针,等等),当这段 userdata 被释放时,其中包含的其它资源也需要被释放(如需要关闭文件,释放另一段内存等)。某些语言提供了一种叫做“finalizer”的机制来完成这件事,在 lua 中对应的是一个“__gc”的域,当某个 userdata 被 lua 回收时,如果它对应的 metatable 中有“__gc”这个域,则这个域所指向的函数(通常指向的是函数)将会被调用,这样用户就能释放和 userdata 相关联的内容。

目录遍历迭代器

第 26 章实现过一个目录遍历的程序,程序每次返回一个包含所有目录项的 table。这里我们将要实现一个迭代器,每次只返回一个目录:

for dname in dir(".") do
    print(dname)
end

c 语言中遍历目录的步骤:首先使用 opendir() 函数打开目录返回一个指向 DIR 结构体的指针,然后使用 readdir() 每次读取一个目录,读取完所有目录后使用 closedir() …

阅读全文…

programming in lua (10)

第 28 章。这章主要是讲怎样在 lua 中使用 c 语言中自定义的数据结构。

先看一个简单的例子:boolean 数组。在 lua 中可以使用 table 来存储,在 c 语言中由于每个变量只占一个 bit,使用的内存只占用 table 实现的 3%。

程序使用如下的数据结构和相关的宏定义:

#include <limits.h>

#define BITS_PER_WORD (CHAR_BIT*sizeof(unsigned int))
#define I_WORD(i)     ((unsigned int)(i) / BITS_PER_WORD)
#define I_BIT(i)      (1 << ((unsigned int)(i) % BITS_PER_WORD))


阅读全文…

programming in lua (9)

第 27 章,主要介绍 c 语言和 lua 交互的一些技巧。这一章涉及到第 2 部分的一些内容。

数组操作

在 lua 中,数组是一种特殊的 table,除了可以使用 lua_settable() 和 lua_gettable() 等操作 table 的函数外,另外也有一些专门用来操作数组的函数。使用这些有针对性的函数有两个好处:一是性能上的提升,例如我们经常在循环访问数组的所有元素;二是像整数数组,字符串等常见的类型使用一些有针对性的操作会比较方便。

lua 提供了两个函数来访问指定下标的数组元素:

void lua_rawgeti (lua_State *L, int index, int key);
void lua_rawseti (lua_State *L, int index, int key);

其中 index …

阅读全文…

项目总结 (2)

很累的一个月,为了赶项目天天码代码,第一次在看到代码的时候有想吐的感觉。

用 lua 作为配置文件

因为在之前的一个项目中师兄采用了网络作为前后端交互的方式,这样两部分分开调试比较方便,从此以后实验室的项目不管是否需要联网都采用这种方法,结果在项目中除了根据甲方的需求把控件拖来拖去之外剩下的就是解析各种各样的报文格式。

前段时间另一个项目需要写一个报文测试的小工具,就是有一堆自定义的报文格式,界面上有下拉条选择或者让用户自己输入某个字段的值,然后把报文发出去,收到返回信息并且把内容解析一下。我已经不怎么碰这个项目了,但是这次时间有点紧,于是哥又“被”当仁不让地充当临时工把活接了下来。在前一个项目中师兄使用了 xml 作为报文格式配置文件,这个小工具我打算用 lua 来试一下,也算是一个实际应用的机会。界面用了 qt,因为现在 qt 俨然成为本人实验室项目界面开发的默认工具了。

先说说使用 lua 作为配置文件吧。报文格式是常见的 TLV 格式(“T”表示 type,“L”表示长度,“V”表示数据),有些字段会有限定的范围:

packet = {
    "报文名称",
    {"字段名称1", "字段类型", 个数, {可选值1, "说明1"}, {可选值2}, {起始值, 结束值, 步长}, ...},
    ......
}

其中字段类型定义了一些常见的类型并规定了长度,例如“int”就是 4 字节,“double”就是 8 个字节等。个数表示该字段中包含了多少个这样的类型。例如…

阅读全文…

programming in lua (8)

第 26 章。

扩展 lua 的一个基本方法是使用 c 语言编写可供 lua 使用的函数。

当我们说 lua“调用”c 函数时,其实并不是真正地调用 c 函数,而是 c 函数遵循一定的约定从 lua 中获取参数并且把结果返回给 lua,或许叫“使用”更合适。在 lua 调用 c 函数前需要先注册 c 函数,即把 c 函数的地址传递给 lua。事实上,lua 使用 c 函数也是通过类似的栈机制,为了避免取了错误的返回值,c 函数需要返回它返回给 lua 的返回值个数(也就是说 lua 应该从栈里取几个值)。

和在 c 中使用 …

阅读全文…