编写Lua函数库

一个Lua库实际上是一个定义了一系列Lua函数的代码块,并将这些函数保存在适当的地方,通常作为table的域来保存。Lua的C库就是这样实现的。

作为一个完整的库,我们还需要写一个函数来负责把库中的所有公共函数放到table里,然后注册到Lua全局变量里,就像luaopen_*做的一样。 Lua为这种需求提供了辅助函数luaL_register,它接受一个C函数的列表和他们对应的函数名,并且作为一个库在一个table中注册所有这些 函数。

下例中注册了一个名为Files的库,定义了三个库函数:FindFirst,FindNext,FindClose。

  1. extern "C" {
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. }
  6. #include <iostream>
  7. #include <string>
  8. #include <windows.h>
  9. using namespace std;
  10. //函数库示例,Windows下查找文件功能
  11. //输入:string路径名
  12. //输出:userdata存放Handle(如果没找到,则是nil), string文件名
  13. int findfirst( lua_State *L )
  14. {
  15. WIN32_FIND_DATAA FindFileData;
  16. HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
  17. if(INVALID_HANDLE_VALUE == hFind)
  18. lua_pushnil(L);
  19. else
  20. lua_pushlightuserdata(L, hFind);
  21. lua_pushstring(L, FindFileData.cFileName);
  22. return 2;
  23. }
  24. //输入:userdata:findfirst返回的Handle
  25. //输出:string:文件名,如果没找到,则返回nil
  26. int findnext( lua_State *L )
  27. {
  28. WIN32_FIND_DATAA FindFileData;
  29. if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
  30. lua_pushstring(L, FindFileData.cFileName);
  31. else
  32. lua_pushnil(L);
  33. return 1;
  34. }
  35. //输入:userdata:findfirst返回的Handle
  36. //没有输出
  37. int findclose( lua_State *L )
  38. {
  39. ::FindClose(lua_touserdata(L,1));
  40. return 0;
  41. }
  42. //注册函数库
  43. static const struct luaL_reg lrFiles [] = {
  44. {"FindFirst", findfirst},
  45. {"FindNext", findnext},
  46. {"FindClose", findclose},
  47. {NULL, NULL} /* sentinel */
  48. };
  49. int luaopen_Files (lua_State *L) {
  50. luaL_register(L, "Files", lrFiles);
  51. return 1;
  52. }
  53. int main()
  54. {
  55. char* szLua_code=
  56. "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "
  57. "if hFind then "
  58. " repeat "
  59. " print(sFile) "
  60. " sFile = Files.FindNext(hFind) "
  61. " until sFile==nil; "
  62. " Files.FindClose(hFind) "
  63. "end";
  64. lua_State *L = luaL_newstate();
  65. luaL_openlibs(L);
  66. luaopen_Files(L);
  67. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  68. if(err)
  69. {
  70. cerr << lua_tostring(L, -1);
  71. lua_pop(L, 1);
  72. }
  73. lua_close(L);
  74. return 0;
  75. }

本例运行结果是显示出C盘根目录下所有的文件名。

Lua官方建议把函数库写进动态链接库中(windows下.dll文件,linux下.so文件),这样就可以在Lua代码中使用loadlib函数动 态载入函数库

例如,我们把上面的例子改成动态链接库版本:

DLL代码,假定生成的文件名为fileslib.dll:

  1. extern "C" {
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. }
  6. #include <windows.h>
  7. BOOL APIENTRY DllMain( HMODULE hModule,
  8. DWORD ul_reason_for_call,
  9. LPVOID lpReserved
  10. )
  11. {
  12. return TRUE;
  13. }
  14. //函数库示例,Windows下查找文件功能
  15. //输入:string路径名
  16. //输出:userdata存放Handle(如果没找到,则是nil), string文件名
  17. int findfirst( lua_State *L )
  18. {
  19. WIN32_FIND_DATAA FindFileData;
  20. HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);
  21. if(INVALID_HANDLE_VALUE == hFind)
  22. lua_pushnil(L);
  23. else
  24. lua_pushlightuserdata(L, hFind);
  25. lua_pushstring(L, FindFileData.cFileName);
  26. return 2;
  27. }
  28. //输入:userdata:findfirst返回的Handle
  29. //输出:string:文件名,如果没找到,则返回nil
  30. int findnext( lua_State *L )
  31. {
  32. WIN32_FIND_DATAA FindFileData;
  33. if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))
  34. lua_pushstring(L, FindFileData.cFileName);
  35. else
  36. lua_pushnil(L);
  37. return 1;
  38. }
  39. //输入:userdata:findfirst返回的Handle
  40. //没有输出
  41. int findclose( lua_State *L )
  42. {
  43. ::FindClose(lua_touserdata(L,1));
  44. return 0;
  45. }
  46. //注册函数库
  47. static const struct luaL_reg lrFiles [] = {
  48. {"FindFirst", findfirst},
  49. {"FindNext", findnext},
  50. {"FindClose", findclose},
  51. {NULL, NULL} /* sentinel */
  52. };
  53. //导出,注意原型为typedef int (*lua_CFunction) (lua_State *L);
  54. extern "C" __declspec(dllexport) int luaopen_Files (lua_State *L) {
  55. luaL_register(L, "Files", lrFiles);
  56. return 1;
  57. }

Lua调用代码(或者直接使用Lua.exe调用,dll文件必须处于package.cpath指定的目录中,默认与执行文件在同一目录即可):

  1. extern "C" {
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. }
  6. #include <iostream>
  7. #include <string>
  8. #include <windows.h>
  9. using namespace std;
  10. int main()
  11. {
  12. char* szLua_code=
  13. "fileslib = package.loadlib('fileslib.dll', 'luaopen_Files') "
  14. "fileslib() "
  15. "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "
  16. "if hFind then "
  17. " repeat "
  18. " print(sFile) "
  19. " sFile = Files.FindNext(hFind) "
  20. " until sFile==nil; "
  21. " Files.FindClose(hFind) "
  22. "end";
  23. lua_State *L = luaL_newstate();
  24. luaL_openlibs(L);
  25. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  26. if(err)
  27. {
  28. cerr << lua_tostring(L, -1);
  29. lua_pop(L, 1);
  30. }
  31. lua_close(L);
  32. return 0;
  33. }

Lua代码里使用package.loadlib得到动态链接库中的luaopen_Files函数,然后调用它注册到Lua中,如果动态链接库中的导出 函数名称满足luaopen_<库名>的话,还可以使用require直接载入。

比如,如果把本例中的DLL代码里的导出函数名luaopen_Files改成luaopen_fileslib的话,Lua代码便可以改成:

  1. char* szLua_code=
  2. "require('fileslib') "
  3. "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "
  4. ...