编写C函数的技巧
1. 数组操作:
在Lua中,“数组”只是table的一个别名,是指以一种特殊的方法来使用table。出于性能原因,Lua的C API为数组操作提供了专门的函数,如:
void lua_rawgeti(lua_State* L, int index, int key);
void lua_rawseti(lua_State* L, int index, int key);
以上两个函数分别用于读取和设置数组中的元素值。其中index参数表示待操作的table在栈中的位置,key表示元素在table中的索引值。由于这两个函数均为原始操作,比涉及元表的table访问更快。通常而言,作为数组使用的table很少会用到元表。
见如下代码示例和关键性注释:
1 #include <stdio.h> 2 #include < string .h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern " C " int mapFunc(lua_State* L) 8 { 9 // 检查Lua调用代码中传递的第一个参数必须是table。否则将引发错误。 10 luaL_checktype(L, 1 ,LUA_TTABLE); 11 luaL_checktype(L, 2 ,LUA_TFUNCTION); 12 // 获取table中的字段数量,即数组的元素数量。 13 int n = lua_objlen(L, 1 ); 14 // Lua中的数组起始索引习惯为1,而不是C中的0。 15 for ( int i = 1 ; i <= n; ++ i) { 16 lua_pushvalue(L, 2 ); // 将Lua参数中的function(第二个参数)的副本压入栈中。 17 lua_rawgeti(L, 1 ,i); // 压入table[i] 18 lua_call(L, 1 , 1 ); // 调用function(table[i]),并将函数结果压入栈中。 19 lua_rawseti(L, 1 ,i); // table[i] = 函数返回值,同时将返回值弹出栈。 20 } 21 22 // 无结果返回给Lua代码。 23 return 0 ; 24 }
2. 字符串操作:
当一个C函数从Lua收到一个字符串参数时,必须遵守两条规则:不要在访问字符串时从栈中将其弹出,不要修改字符串。在Lua的C API中主要提供了两个操作Lua字符串的函数,即:
void lua_pushlstring(lua_State *L, const char *s, size_t l);
const char* lua_pushfstring(lua_State* L, const char* fmt, ...);
第一个API用于截取指定长度的子字符串,同时将其压入栈中。而第二个API则类似于C库中的sprintf函数,并将格式化后的字符串压入栈中。和sprintf的格式说明符不同的是,该函数只支持 %% (表示字符%)、 %s (表示字符串)、 %d (表示整数)、 %f (表示Lua中的number)及 %c (表示字符)。除此之外,不支持任何例如宽度和精度的选项。
1 #include <stdio.h> 2 #include < string .h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern " C " int splitFunc(lua_State* L) 8 { 9 const char * s = luaL_checkstring(L, 1 ); 10 const char * sep = luaL_checkstring(L, 2 ); // 分隔符 11 const char * e; 12 int i = 1 ; 13 lua_newtable(L); // 结果table 14 while ((e = strchr(s,*sep)) != NULL) { 15 lua_pushlstring(L,s,e - s); // 压入子字符串。 16 // 将刚刚压入的子字符串设置给table,同时赋值指定的索引值。 17 lua_rawseti(L,- 2 ,i++ ); 18 s = e + 1 ; 19 } 20 // 压入最后一个子串 21 lua_pushstring(L,s); 22 lua_rawseti(L,- 2 ,i); 23 return 1 ; // 返回table。 24 }
Lua API中提供了lua_concat函数,其功能类似于Lua中的".."操作符,用于连接(并弹出)栈顶的n个值,然后压入连接后的结果。其原型为:
void lua_concat(lua_State *L, int n);
参数n表示栈中待连接的字符串数量。该函数会调用元方法。然而需要说明的是,如果连接的字符串数量较少,该函数可以很好的工作,反之,则会带来性能问题。为此,Lua API提供了另外一组函数专门解决由此而带来的性能问题,见如下代码示例:
1 #include <stdio.h> 2 #include < string .h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern " C " int strUpperFunc(lua_State* L) 8 { 9 size_t len; 10 luaL_Buffer b; 11 // 检查参数第一个参数是否为字符串,同时返回字符串的指针及长度。 12 const char * s = luaL_checklstring(L, 1 ,& len); 13 // 初始化Lua的内部Buffer。 14 luaL_buffinit(L,& b); 15 // 将处理后的字符依次(luaL_addchar)追加到Lua的内部Buffer中。 16 for ( int i = 0 ; i < len; ++ i) 17 luaL_addchar(& b,toupper(s[i])); 18 // 将该Buffer及其内容压入栈中。 19 luaL_pushresult(& b); 20 return 1 ; 21 }
使用缓冲机制的第一步是声明一个luaL_Buffer变量,并用luaL_buffinit来初始化它。初始化后,就可通过luaL_addchar将一个字符放入缓冲。除该函数之外,Lua的辅助库还提供了直接添加字符串的函数,如:
void luaL_addlstring(luaL_Buffer* b, const char* s, size_t len);
void luaL_addstring(luaL_Buffer* b, const char* s);
最后luaL_pushresult会更新缓冲,并将最终的字符串留在栈顶。通过这些函数,就无须再关心缓冲的分配了。但是在追加的过程中,缓冲会将一些中间结果放到栈中。因此,在使用时要留意此细节,只要保证压入和弹出的次数相等既可。Lua API还提供一个比较常用的函数,用于将栈顶的字符串或数字也追加到缓冲区中,函数原型为:
void luaL_addvalue(luaL_Buffer* b);
3. 在C函数中保存状态:
Lua API提供了三种方式来保存非局部变量,即注册表、环境和upvalue。
1). 注册表:
注册表是一个全局的table,只能被C代码访问。通常用于保存多个模块间的共享数据。我们可以通过 LUA_REGISTRYINDEX 索引值来访问注册表。
1 #include <stdio.h> 2 #include < string .h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 void registryTestFunc(lua_State* L) 8 { 9 lua_pushstring(L, " Hello " ); 10 lua_setfield(L,LUA_REGISTRYINDEX, " key1 " ); 11 lua_getfield(L,LUA_REGISTRYINDEX, " key1 " ); 12 printf( " %s\n " ,lua_tostring(L,- 1 )); 13 } 14 15 int main() 16 { 17 lua_State* L = luaL_newstate(); 18 registryTestFunc(L); 19 lua_close(L); 20 return 0 ; 21 }
2). 环境:
如果需要保存一个模块的私有数据,即模块内各函数需要共享的数据,应该使用环境。我们可以通过 LUA_ENVIRONINDEX 索引值来访问环境。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 // 模块内设置环境数据的函数 6 extern " C " int setValue(lua_State* L) 7 { 8 lua_pushstring(L, " Hello " ); 9 lua_setfield(L,LUA_ENVIRONINDEX, " key1 " ); 10 return 0 ; 11 } 12 13 // 模块内获取环境数据的函数 14 extern " C " int getValue(lua_State* L) 15 { 16 lua_getfield(L,LUA_ENVIRONINDEX, " key1 " ); 17 printf( " %s\n " ,lua_tostring(L,- 1 )); 18 return 0 ; 19 } 20 21 static luaL_Reg myfuncs[] = { 22 { " setValue " , setValue}, 23 { " getValue " , getValue}, 24 {NULL, NULL} 25 }; 26 27 28 extern " C " __declspec(dllexport) 29 int luaopen_testenv(lua_State* L) 30 { 31 lua_newtable(L); // 创建一个新的表用于环境 32 lua_replace(L,LUA_ENVIRONINDEX); // 将刚刚创建并压入栈的新表替换为当前模块的环境表。 33 luaL_register(L, " testenv " ,myfuncs); 34 return 1 ; 35 }
Lua测试代码如下。
1 require " testenv " 2 3 print (testenv.setValue()) 4 print (testenv.getValue()) 5 -- 输出为:Hello
3). upvalue:
upvalue是和特定函数关联的,我们可以将其简单的理解为函数内的静态变量。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 extern " C " int counter(lua_State* L) 6 { 7 // 获取第一个upvalue的值。 8 int val = lua_tointeger(L,lua_upvalueindex( 1 )); 9 // 将得到的结果压入栈中。 10 lua_pushinteger(L,++ val); 11 // 赋值一份栈顶的数据,以便于后面的替换操作。 12 lua_pushvalue(L,- 1 ); 13 // 该函数将栈顶的数据替换到upvalue(1)中的值。同时将栈顶数据弹出。 14 lua_replace(L,lua_upvalueindex( 1 )); 15 // lua_pushinteger(L,++value)中压入的数据仍然保留在栈中并返回给Lua。 16 return 1 ; 17 } 18 19 extern " C " int newCounter(lua_State* L) 20 { 21 // 压入一个upvalue的初始值0,该函数必须先于lua_pushcclosure之前调用。 22 lua_pushinteger(L, 0 ); 23 // 压入闭包函数,参数1表示该闭包函数的upvalue数量。该函数返回值,闭包函数始终位于栈顶。 24 lua_pushcclosure(L,counter, 1 ); 25 return 1 ; 26 } 27 28 static luaL_Reg myfuncs[] = { 29 { " counter " , counter}, 30 { " newCounter " , newCounter}, 31 {NULL, NULL} 32 }; 33 34 35 extern " C " __declspec(dllexport) 36 int luaopen_testupvalue(lua_State* L) 37 { 38 luaL_register(L, " testupvalue " ,myfuncs); 39 return 1 ; 40 }
Lua测试代码如下。
1 require " testupvalue " 2 3 func = testupvalue.newCounter(); 4 print (func()); 5 print (func()); 6 print (func()); 7 8 func = testupvalue.newCounter(); 9 print (func()); 10 print (func()); 11 print (func()); 12 13 --[[ 输出结果为: 14 1 15 2 16 3 17 1 18 2 19 3 20 -- ]]
分类: Lua编程
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息