可编程的log—初识lua的强大
作者: 马楠
论文摘要
n
本文介绍了使用
lua
实现的一组可用于实现
log
功能的接口。在源代码中,它与普通的
Log
语句相同
(
在固定的地方写上固定的
log
语句
)
,但是,它的最终功能是通过
lua
脚本来实现的。通过
lua
脚本,我们可以得到如下好处:
①
log
内容的可定制性。无需修改源代码中的
log
语句,即可定制不同的
log
内容。
②
可用于
MT
测试。由于
C
的
MT
测试可以用
Cunit
,这个做法用处并不是很大。
③
可用于
IT/ST
测试。对于
IT/ST
的回归测试来说,这个功能应该是个很有用的功能。
关键字
可编程
log lua
参考资料
n Programming in lua, Roberto Ierusalimschy
1.引言
你碰到过这种情况么?在调试的时候,你无法使用IDE的断点/单步调试功能,而不得不借用printf函数在源代码添加调试信息以观察程序运行的结果。但是糟糕的是,当你调试第二个功能的时候,你发现调试第一个功能添加的语句是在是太乱了,所以你又不得不删掉。但是这样一来,更糟糕的结果是,第一个用例就无法运行了。在调试多线程程序和何时间相关的程序的时候,这种情况很常见。本文为这个问题提供了一种解决方案。
本文就是利用lua这个强大的功能来实现一个可编程的log功能。Lua是一个扩展程序设计语言,它有如下有点:
① 它是一个“嵌入式”,它可以嵌入到C/C++程序中使用。
② 和C/C++程序的可交互性。可以在C/C++程序中调用lua的函数和变量,也可以在lua脚本中调用C/C++程序的函数和变量。
③ 安全性。在C/C++中对Lua所进行的所有的操作都是通过lua的栈来进行的,也就是说,在C/C++中使用的所有的和lua相关的变量和函数,其内存都在是由lua来维护,所以使用起来很安全。
关于lua,网上有很多介绍和教程,这里不再细说。
为了方便,我为这个可编程的log起了一个名字:ULD(Using Lua Debug),叫它ULD而不是ULL(Using Lua Log)的原因是实现它的本意是为了调试,而不是为了Log,但是后来发现将它实现为Log功能是再合适不过了。
2.ULD构造
ULD的使用设想如下:
void func()
{
int i;
int j;
uld_docatch(“func”, “enter”);
...//
处理
uld_docatch(“func”,”exit”);
}
|
if(uld_catch(“func”, “enter”))
{
uld_callcatch();
}
|
func = {}
func.enter = function(self)
print(“func”, “enter”)
--do something
end
|
调用
func.enter函数
|
主要原理是在执行一个uld_docatch语句时,先判断该函数是否在lua脚本中存在,如果存在则调用那个函数,否则忽略。
为了能够在lua脚本中做更多的事情,ULD还提供了注册全局变量和局部变量的功能。
2.1 ULD API说明
2.1.1. uld_Handle ULD实例句柄
一个uld_Handle实例句柄对应一个lua脚本文件。
2.1.2. uld_open 初始化ULD
int
uld_open(uld_Handle* h);
【参数】
1.
h
:
ULD
句柄
【返回值】
TRUE
:初始化成功
FALSE
2.1.3. uld_close 关闭ULD
void
uld_close(uld_Handle* h);
【参数】
1.
h
:
ULD
句柄
【返回值】
无
2.1.4. uld_runfile以文件形式执行lua脚本
int
uld_runfile(uld_Handle* h, const char* file);
【参数】
1.
h
:
ULD
句柄
2.
file
:文件名,不能为
NULL
【返回值】
非0:成功
0:失败
2.1.5. uld_catch判断是否捕捉了该log语句
int
uld_catch(uld_Handle* h, const char* func_name, const char* step);
【参数】
1. h:ULD句柄
2.
func_name
:函数名,在
lua
中它对应一个
Table
名
3.
step
:该
log
语句在函数中的位置名称,在
lua
中它对应了
func_name Table
中的一个
key
的名字
【返回值】
非
0:lua
脚本中找到了该
func_name.step
函数
0: lua
脚本中没有找到
func_name.step
函数
【说明】
Ø
ULD
设置了默认的
func_name
和
step
其搜索的顺序如下:
①
func_name.step
②
func_name.__all
③
__catch_all_func.__all
Ø
函数名
func_name
在
lua
脚本中,可以使用该
Table
的
__func
得到,它是一个
string
Ø
Step
名在
lua
脚本中,可以使用
Table
的
__step
得到,它是一个
string
2.1.6. uld_callcatch调用lua中的函数
int
uld_callcatch(uld_Handle* h, const char* str);
【参数】
1.
h
:
ULD
句柄
2.
str
:希望输出的字符串,可以为
NULL
【返回值】
非
0
:成功
0
:失败
【说明】
uld_callcatch
函数在调用
lua
中的
func_name.step
函数的同时,还可以传入一个字符串用于输出,这个字符串存储在
func_name.__msg
中
uld_callcatch
函数必须在
uld_catch
函数成功后调用。
2.1.7. uld_catch_setvalue_str设置一个变量和值
int
uld_catch_setvalue_str(uld_Handle* h, const char* var_name, const char* value)
【参数】
1.
h
:
ULD
句柄
2.
var_name
:变量名
3.
value
:变量值,必须是字符串
【返回值】
非
0
:成功
0
:失败
【说明】
这个函数也必须在
uld_catch
函数成功后调用。设置成功后,可以在
Lua
中使用
func_name.var_name
得到
value
2.1.8. uld_catch_clearvalue清除一个变量
int
uld_catch_clearvalue(uld_Handle* h, const char* var_name)
【参数】
1.
h
:
ULD
句柄
2.
var_name
:变量名
【返回值】
非
0
:成功
0
:失败
【说明】
这个函数也必须在
uld_catch
函数成功后调用。设置成功后,在
Lua
中使用
func_name.var_name = nil
2.1.9. uld_docatch(macro)捕捉并执行一条简单的log语句
#define
uld_docatch(h, func, step) if(uld_catch(h, func, step)){uld_callcatch(h, NULL);}
2.1.10. uld_catch_block_begin(macro)开始一个语句块的捕捉uld_catck_block_end(macro) 结束一个语句块的捕捉
有的时候,我们需要一组比较复杂的语句进行输出,这个时候可以用这两个宏函数来处理。
uld_catch_block_begin(h, func_name, step);
...
uld_catch_block_end(h);
2.2 C程序中变量的引用
在
ULD
中,我们的目标就是如何在
lua
中访问
C
程序中的变量,我们并不考虑在
lua
中修改它。
2.3 类型的声明
ULD
提供了一组宏用来注册类型。
1.
ULD_DECLARE_TYPE(type)
用来声明一个类型描述。
type
是该
struct
的名
2.
ULD_IMPL_BEGIN_TYPE(type)
开始一个
struct
类型的定义
3.
ULD_IMPL_END_TYPE(type)
结束一个
struct
的定义
4.
ULD_IMPL_MEMBER(parent_type, type, member_name, is_pointer)
parent_type
:该结构体的名字
type
:成员变量的类型,如果不是结构体类型,它必须是以下类型的一种
type
值
|
C
程序中的类型
|
char
|
char
|
uchar
|
unsigned char
|
short
|
short
|
ushort
|
unsigned short
|
int
|
int
|
uint
|
unsigned int
|
float
|
float
|
double
|
double
|
string
|
char*(
以
0
结尾的字符串
)
|
member_name
:该成员变量的名字
is_pointer
:非
0
表示该成员变量是一个指针。
这个函数必须在
ULD_IMPL_BEGIN_TYPE
和
ULD_IMPL_END_TYPE
的中间
5.
ULD_IMPL_METHOD
(parent_type, method_name, method)
定义一个成员函数
parent_type
:该成员函数所属的结构体名
method_name
:成员函数的名
method
:成员函数的函数指针,它与
lua_CFunction
类型相同,其中第一个变量就是该结构体的指针。
2.4 类型声明的一个例子
假设
C
代码的结构体定义如下:
//struct define
typedef
struct a_type
{
int b;
}a_type;
typedef
struct g_type
{
int* f;
int* e;
a_type a;
}g_type;
|
则对应的声明如下:
//type's description variable declaration
ULD_DECLARE_TYPE
(g_type);
ULD_DECLARE_TYPE
(a_type);
//definition of a type description
ULD_IMPL_BEGIN_TYPE
(a_type)
ULD_IMPL_MEMBER(a_type, int, b, 0)
ULD_IMPL_END_TYPE
(a_type)
ULD_IMPL_BEGIN_TYPE
(g_type)
ULD_IMPL_MEMBER(g_type, int, f, TRUE)
ULD_IMPL_MEMBER(g_type, int, e, TRUE)
ULD_IMPL_MEMBER(g_type, a_type, a, 0)
ULD_IMPL_METHOD(g_type, getf, g_get_f)
ULD_IMPL_END_TYPE
(g_type)
|
2.5 Lua脚本说明
假设我们在
C
程序中写了下面这样一个函数。
1
|
void
test(uld_Handle* h)
|
2
|
{
|
3
|
int i = 0;
|
4
|
int k = 3;
|
5
|
int *j = &k;
|
6
|
|
7
|
g.f = malloc(sizeof(int));
|
8
|
*g.f=1;
|
9
|
|
10
|
ULD_REGPARAM(h, "func",i, int, FALSE);
|
11
|
ULD_REGPARAM(h, "func",j, int, TRUE);
|
12
|
|
13
|
uld_docatch(h, "func", "first");
|
14
|
|
15
|
i = 100;
|
16
|
*j = 100;
|
17
|
*g.f = 2;
|
18
|
strcpy(p, "a");
|
19
|
uld_docatch(h, "func", "second");
|
20
|
}
|
如果我们需要在第
13
行输出信息的话,那么
lua
脚本应该写成下面这样。
func = {}
function func:first()
print("first") --
打印
first
print("g:f()=", g:f()) --
打印
g.f
变量
print("g:f()._ptr=", g:f():_ptr()) --
打印
g.f
的指针值
print("g:a():b()=", g:a():b()) --
打印
g.a.b
的值
print("g:getf()=", g:getf()) --
调用
getf
函数
end
|
2.6 小结
lua
的强大之处在于,它能够和
C/C++
程序进行交互。这样,我们就可以将很多需要定制的工作交给
lua
脚本来完成,而
lua
的强大的功能库还可以让你做更多的事情。例如,在上面的例子中,我们可以通过修改
lua
脚本将信息写入文件中:
f = io.open(“log.txt”, “w”)
func = {}
function func:first()
f:write("first") --
打印
first
f:write ("g:f()=", g:f()) --
打印
g.f
变量
f:write ("g:f()._ptr=", g:f():_ptr()) --
打印
g.f
的指针值
f:write ("g:a():b()=", g:a():b()) --
打印
g.a.b
的值
f:write ("g:getf()=", g:getf()) --
调用
getf
函数
end
|
如果是非
GUI
测试的话,我们可以利用它来实现
ST
测试。
func = {}
function func:first()
if(g:f() == 2) then
print(“OK”)
else
print(“NG”)
end
end
|
另外,
lua
的执行效率完很高,如果是一些数据处理的话,它甚至比我们自己写代码执行效率还高。在网上有人经过测试,它处理一个
2M
的数据文件时间不超过
2s
!
在做
diameter
协议编译器的时候,如果我们知道
lua
这个语言,那么我们的工作将会轻松的多。
Diameter
协议编译器的输出就是一组编解码器的源文件。在最初的时候,我们也是像
asn1c
编译器那样使用
fprintf
函数将一条条信息写到文件里,但是后来我们发现,这种做法的一个弊端就是生成的文件格式很难控制,要想修改一个缩进都不是那么容易,于是自己发明了一个非常拙劣的脚本语言。虽然这个脚本语言功能有限,设计的也很拙劣,但是它却极大的提高了程序的可理解性和扩展性。在后一期的项目中,也体现出了它的优势。事实上,这个拙劣的脚本语言的目的,就是希望能够和
C
程序交互,如果使用
lua
,它将变得更完美。
lua
是使用标准
C
写的,所以不论是在
windows
下还是在
linux
下,它都能够很好的运行。如果你担心需要携带一个库的话,你也可以直接将它的源代码集成到你的工程中。而且它是完全免费的。
lua
并不是一门新兴的语言,它在
1993
年就已经开始存在了。现在,主要是游戏中使用
lua
,不过,如果我们能够深刻理解它的优点的话,我想它能够极大的提高我们的工作效率。