数码资讯
可编程的log—初识lua的强大
选购提示
关注价格、性能、续航、售后和真实使用场景,理性比较后再下单。
可编程的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 somethingend
调用 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; 67 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 ,不过,如果我们能够深刻理解它的优点的话,我想它能够极大的提高我们的工作效率。
声明:本文内容用于数码产品信息整理与选购参考,具体价格、库存、售后政策以官方渠道和电商页面实时信息为准。