C 可变长参数运用-----编写Lua的通用调用函数

1.C可变长参数

printf这个使用频繁的C语言函数的参数列表包含一个const char*的描述串,还有一个可变长参数(...) ,如下为printf的函数声明。

int printf(const char * __restrict, ...)

在stdarg.h这个头文件中包含着对可变长参数进行操作的一些宏(x86平台为例):

#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define va_end(ap) ( ap = (va_list)0 )

其中运行va_start(ap, v)可以使得ap指向可变长参数列表的第一个参数的地址。其中v为最后一个固定参数。

va_arg(ap,T)使ap指向可变长参数列表的下一个参数,并且用输入的类型T进行数据强转换,返回一个用户需要的参数值。

va_end(ap),置ap为NULL。

详细的可变长参数解释请参见这个博客:imjacob的专栏——c语言中的printf实现

2.C语言对Lua的通用调用函数

 1 #include <stdarg.h>
 2 
 3 void call_va(const char * func,const char *sig,...)
 4 {
 5     va_list vl;//定义一个可变长参数指针,无初始值
 6     int narg;//可变长参数的数量
 7     int nres;//Lua函数返回值的数量
 8 
 9     va_start(vl,sig);//让指针指向可变长参数的第一个参数首地址
10     lua_getglobal(L,func);//函数入栈
11 
12     //---参数入栈
13     for(narg=0;*sig;narg++)//遍历参数
14     {
15         //检查栈中空间
16         luaL_checkstack(L,1,"too many arguments");
17 
18         switch(*sig++)
19         {
20             //根据参数的约束符,按类型入栈,d-double i-int s-char*
21             case 'd':
22             lua_pushnumber(L,va_arg(vl,double));
23             break;
24             case 'i':
25             lua_pushinteger(L,va_arg(vl,int));
26             break;
27             case 's':
28             lua_pushstring(L,va_arg(vl,char*));
29             break;
30             case '>':
31             //输入参数结束
32             goto endargs;
33             default:
34             error(L,"invalid option (%c)",*(sig-1));
35         }
36     }
37     //---参数入栈
38     endargs:
39     nres=strlen(sig);//期望的Lua函数返回值数量
40 
41     //调用Lua函数
42     if(lua_pcall(L,narg,nres,0)!=0)
43     {
44         error(L,"error calling '%s':%s",func,lua_tostring(L,-1));
45     }
46 
47     //调用之后的返回值检索
48     //栈索引从栈顶往下依次是-1,-2,-3,所以第一个结果的栈索引是-nres
49     nres=-nres;
50     while(*sig)
51     {
52         //依次得到返回值的类型描述符 *sig
53         switch(*sig++)
54         {
55             case 'd':
56             if(!lua_isnumber(L,nres))
57                 error(L,"wrong result type");
58             *va_arg(vl,double*)=lua_tonumber(L,nres);
59             break;
60 
61             case 'i':
62             if(!lua_isnumber(L,nres))
63                 error(L,"wrong result type");
64             *va_arg(vl,int*)=lua_tointeger(L,nres);
65             break;
66 
67             case 's':
68             if(!lua_isstring(L,nres))
69                 error(L,"wrong result type");
70             *va_arg(vl,const char**)=lua_tostring(L,nres);
71             break;
72 
73             default:
74             error(L,"invalid option (%c)",*(sig-1));
75         }
76         //结果的参数索引+1
77         nres++;
78     }
79     va_end(vl);
80 
81 }

这样,如果我们需要调用一个lua函数,只需要如下的调用形式:

call_va("func","dd>ds",x,y,&z,&s);

表示这个lua函数的函数名为func,接受两个double,返回一个double和一个字符串,'>'表示参数和返回值结果的分割符。