可编程的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 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 ,不过,如果我们能够深刻理解它的优点的话,我想它能够极大的提高我们的工作效率。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值