您的位置:首页 > 编程语言 > Lua

可编程的log—初识lua的强大

2008-05-27 14:41 253 查看
[align=center]可编程的log—初识lua的强大[/align]
[align=right]作者: 马楠 [/align]
论文摘要
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:成员变量的类型,如果不是结构体类型,它必须是以下类型的一种

[align=center]type值[/align]
[align=center]C程序中的类型[/align]
[align=center]char[/align]
[align=center]char[/align]
[align=center]uchar[/align]
[align=center]unsigned char[/align]
[align=center]short[/align]
[align=center]short[/align]
[align=center]ushort[/align]
[align=center]unsigned short[/align]
[align=center]int[/align]
[align=center]int[/align]
[align=center]uint[/align]
[align=center]unsigned int[/align]
[align=center]float[/align]
[align=center]float[/align]
[align=center]double[/align]
[align=center]double[/align]
[align=center]string[/align]
[align=center]char*(以0结尾的字符串)[/align]
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代码的结构体定义如下:

[align=left]//struct define[/align]
[align=left]typedef struct a_type[/align]
[align=left]{[/align]
[align=left] int b;[/align]
[align=left]}a_type;[/align]
[align=left] [/align]
[align=left]typedef struct g_type[/align]
[align=left]{[/align]
[align=left] int* f;[/align]
[align=left] int* e;[/align]
[align=left] a_type a;[/align]
}g_type;
则对应的声明如下:

[align=left]//type's description variable declaration[/align]
[align=left]ULD_DECLARE_TYPE(g_type);[/align]
[align=left]ULD_DECLARE_TYPE(a_type);[/align]
[align=left] [/align]
[align=left]//definition of a type description[/align]
[align=left]ULD_IMPL_BEGIN_TYPE(a_type)[/align]
[align=left] ULD_IMPL_MEMBER(a_type, int, b, 0) [/align]
[align=left]ULD_IMPL_END_TYPE(a_type)[/align]
[align=left] [/align]
[align=left]ULD_IMPL_BEGIN_TYPE(g_type)[/align]
[align=left] ULD_IMPL_MEMBER(g_type, int, f, TRUE) [/align]
[align=left] ULD_IMPL_MEMBER(g_type, int, e, TRUE)[/align]
[align=left] ULD_IMPL_MEMBER(g_type, a_type, a, 0)[/align]
[align=left] ULD_IMPL_METHOD(g_type, getf, g_get_f)[/align]
ULD_IMPL_END_TYPE(g_type)
2.5 Lua脚本说明
假设我们在C程序中写了下面这样一个函数。

1
[align=left]void test(uld_Handle* h)[/align]
[align=left]2[/align]
[align=left]{[/align]
[align=left]3[/align]
[align=left] int i = 0;[/align]
[align=left]4[/align]
[align=left] int k = 3;[/align]
[align=left]5[/align]
[align=left] int *j = &k;[/align]
[align=left]6[/align]
[align=left]7[/align]
[align=left] g.f = malloc(sizeof(int));[/align]
[align=left]8[/align]
[align=left]*g.f=1;[/align]
[align=left]9[/align]
[align=left]10[/align]
[align=left] ULD_REGPARAM(h, "func",i, int, FALSE);[/align]
[align=left]11[/align]
[align=left] ULD_REGPARAM(h, "func",j, int, TRUE);[/align]
[align=left]12[/align]
[align=left]13[/align]
[align=left] uld_docatch(h, "func", "first");[/align]
[align=left]14[/align]
[align=left]15[/align]
[align=left] i = 100;[/align]
[align=left]16[/align]
[align=left] *j = 100;[/align]
[align=left]17[/align]
[align=left] *g.f = 2;[/align]
[align=left]18[/align]
[align=left] strcpy(p, "a");[/align]
[align=left]19[/align]
[align=left] uld_docatch(h, "func", "second");[/align]
[align=left]20[/align]
}
如果我们需要在第13行输出信息的话,那么lua脚本应该写成下面这样。

[align=left]func = {}[/align]
[align=left] [/align]
[align=left]function func:first()[/align]
[align=left] print("first") --打印first[/align]
[align=left] print("g:f()=", g:f()) --打印g.f变量[/align]
[align=left] print("g:f()._ptr=", g:f():_ptr()) --打印g.f的指针值[/align]
[align=left] print("g:a():b()=", g:a():b()) --打印g.a.b的值[/align]
[align=left] print("g:getf()=", g:getf()) --调用getf函数[/align]
end
2.6 小结
lua的强大之处在于,它能够和C/C++程序进行交互。这样,我们就可以将很多需要定制的工作交给lua脚本来完成,而lua的强大的功能库还可以让你做更多的事情。例如,在上面的例子中,我们可以通过修改lua脚本将信息写入文件中:

[align=left]f = io.open(“log.txt”, “w”)[/align]
[align=left] [/align]
[align=left]func = {}[/align]
[align=left] [/align]
[align=left]function func:first()[/align]
[align=left] f:write("first") --打印first[/align]
[align=left] f:write ("g:f()=", g:f()) --打印g.f变量[/align]
[align=left] f:write ("g:f()._ptr=", g:f():_ptr()) --打印g.f的指针值[/align]
[align=left] f:write ("g:a():b()=", g:a():b()) --打印g.a.b的值[/align]
[align=left] f:write ("g:getf()=", g:getf()) --调用getf函数[/align]
end
如果是非GUI测试的话,我们可以利用它来实现ST测试。

[align=left]func = {}[/align]
[align=left] [/align]
[align=left]function func:first()[/align]
[align=left] if(g:f() == 2) then[/align]
[align=left]print(“OK”)[/align]
[align=left]else[/align]
[align=left]print(“NG”)[/align]
[align=left] end[/align]
end
另外,lua的执行效率完很高,如果是一些数据处理的话,它甚至比我们自己写代码执行效率还高。在网上有人经过测试,它处理一个2M的数据文件时间不超过2s!
在做diameter协议编译器的时候,如果我们知道lua这个语言,那么我们的工作将会轻松的多。Diameter协议编译器的输出就是一组编解码器的源文件。在最初的时候,我们也是像asn1c编译器那样使用fprintf函数将一条条信息写到文件里,但是后来我们发现,这种做法的一个弊端就是生成的文件格式很难控制,要想修改一个缩进都不是那么容易,于是自己发明了一个非常拙劣的脚本语言。虽然这个脚本语言功能有限,设计的也很拙劣,但是它却极大的提高了程序的可理解性和扩展性。在后一期的项目中,也体现出了它的优势。事实上,这个拙劣的脚本语言的目的,就是希望能够和C程序交互,如果使用lua,它将变得更完美。
lua是使用标准C写的,所以不论是在windows下还是在linux下,它都能够很好的运行。如果你担心需要携带一个库的话,你也可以直接将它的源代码集成到你的工程中。而且它是完全免费的。
lua并不是一门新兴的语言,它在1993年就已经开始存在了。现在,主要是游戏中使用lua,不过,如果我们能够深刻理解它的优点的话,我想它能够极大的提高我们的工作效率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: