[关闭]
@tangyikejun 2016-12-08T16:13:03.000000Z 字数 5942 阅读 3250

Lua 与 C/C++ 的互相调用

via tangyikejun

配置环境: Win7 + Sublimtext 3 + MinGW 5.3.0 (g++ 5.3.0) + Lua 5.1.5

本文介绍 Windows 下命令行方式使得 Lua 与 C/C++ 交互的基本流程。首先在 C/C++ 使用 liblua.a 静态库调用 lua,然后生成 mylib.dll 动态库提供 C/C++ 函数给 lua 使用。

环境配置

1. MinGW 配置

MinGW 是 Windows 下的一个工具集,提供了各种 C/C++ 开发所需要的命令行工具。

下载 mingw-get-setup.exe 并安装后,将 %root%\bin;%root%\msys\1.0\bin; (例如我安装在
C盘根目录,那么就是 C:\MinGW\bin;C:\MinGW\msys\1.0\bin; )添加到环境变量(计算机-属性-高级系统设置-高级-环境变量)的 PATH 中。(PATH 的作用就是让 cmd 控制台知道要执行的 exe 工具放在什么地方。)

打开 cmd 窗口,输入如下命令检查是否安装配置成功

  1. $ g++ --version

如果输出为

  1. g++ (GCC) 5.3.0
  2. Copyright (C) 2015 Free Software Foundation, Inc.
  3. This is free software; see the source for copying conditions. There is NO
  4. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

即安装成功。

1. Lua 配置

这个步骤主要是为了获得一个 liblua.a 的静态链接库文件。

这里建议下载一个 LuaDist,有点类似于 lua 界的 MinGW,下载之后就能直接使用 lua,并且提供了 curl 等常用工具。同样可以把 %root%\bin;放到 PATH 中。

再插一嘴,如果要在 sublime 中使用 lua 那么只要点击 Tools - Build System - New Build System ,在打开的文件中添加如下内容

  1. {
  2. "cmd": ["D:/LuaDist-0.9.8-Windows-x86/bin/lua", "$file"],
  3. "file_regex": "^(?:lua:)?[\t ](...*?):([0-9]*):?([0-9]*)",
  4. "selector": "source.lua"
  5. }

上面的 D:/LuaDist-0.9.8-Windows-x86/bin/lua 替换成你自己的 lua.exe 路径,保存(假设保存为文件名 LuaMe),这时可以在 Tools - Build System 中看到 LuaMe 选项,选中后,打开一个 lua 文件, ctrl+b 就能对该文件进行编译。注意 lua 文件的路径如果包含汉字或者奇怪的字符可能会编译不成功,后缀不是 lua 的lua文件也是无法成功编译的,比如还未保存过的临时文件。之前保存的 LuaMe 配置文件可以在 Preferences - Brows Packages 打开的文件夹下的 User 文件夹中找到。

如果顺利安装了 LuaDist 并配置了 PATH ,那么可以直接打开 cmd 找个目录执行如下命令

  1. curl -R -O http://www.lua.org/ftp/lua-5.1.5.tar.gz
  2. tar zxf lua-5.1.5.tar.gz
  3. cd lua-5.1.5
  4. make mingw test

这个命令其实就是把 lua 的源码下载下来 make 了一下而已。如果没有安装 LuaDist 也可以直接前往 Lua Download 选择合适的 lua Source 下载,解压后在 cmd 控制台 cd 到对应路径后 make mingw test 进行编译。

make 成功后在 src 目录下会生成一个 liblua.a 文件,即是我们所需要的。

我们可以通过 MinGW 提供的 nm 工具来查看 liblua.a 文件内定义的函数签名

例如下面这个命令可以查看文件中是否定义了 luaL_newstate 函数

  1. $ nm liblua.a | grep "luaL_newstate"

输出结果为 000015e0 T _luaL_newstate

在 C/C++ 中使用 Lua

1. 新建文件

新建一个文件夹 Test ,新建文件 test.c ,将 liblua.a 拷贝到该文件夹下,并将 lauxlib.h,lua.h,lua.hpp,luaconf.h,lualib.h 这几个头文件拷贝到 include 文件夹中。

  1. $ mkdir Test
  2. $ cd Test
  3. $ copy E:\Projects\C\lua-5.1.5\lua-5.1.5\src\liblua.a liblua.a
  4. $ mkdir include
  5. $ copy c:\lua-5.1.5\lua-5.1.5\src\lauxlib.h .\include
  6. $ copy c:\lua-5.1.5\lua-5.1.5\src\lua.h .\include
  7. $ copy c:\lua-5.1.5\lua-5.1.5\src\lua.hpp .\include
  8. $ copy c:\lua-5.1.5\lua-5.1.5\src\luaconf.h .\include
  9. $ copy c:\lua-5.1.5\lua-5.1.5\src\lualib.h .\include
  10. $ touch test.c

打开 test.c ,输入如下代码(代码基本沿用 风中老狼 的):

  1. #include <iostream>
  2. using namespace std;
  3. extern "C"
  4. {
  5. #include "include\lua.h"
  6. #include "include\lauxlib.h"
  7. #include "include\lualib.h"
  8. }
  9. int main()
  10. {
  11. //1.创建一个state
  12. lua_State *L = luaL_newstate();
  13. //2.入栈操作
  14. lua_pushstring(L, "I am so cool~");
  15. lua_pushnumber(L,20);
  16. //3.取值操作
  17. if( lua_isstring(L,1)){ //判断是否可以转为string
  18. cout<<lua_tostring(L,1)<<endl; //转为string并返回
  19. }
  20. if( lua_isnumber(L,2)){
  21. cout<<lua_tonumber(L,2)<<endl;
  22. }
  23. //4.关闭state
  24. lua_close(L);
  25. return 0;
  26. }

2. 编译文件

  1. $ g++ -o test test.c -L . -l lua

*注:使用 -l 选项进行链接的时候只需要写链接库的库名即 lua ,对应的就是 liblua.a *

此时会生成一个 test.exe 文件。

期间遇到一个这个问题,把 lialua.a 重新拷贝了一下就好了,原因未知。。

  1. ./liblua.a: file not recognized: File format not recognized
  2. collect2.exe: error: ld returned 1 exit status

3. 运行

  1. $ test

输出

  1. I am so cool~
  2. 20

爽不?

4. 更进一步

前面只是在 C 中对 lua 的栈进行直接的操作,那么如何执行一般意义的 lua 代码呢?

将 test.c 改成如下形式

  1. extern "C"
  2. {
  3. #include "include\lua.h"
  4. #include "include\lauxlib.h"
  5. #include "include\lualib.h"
  6. }
  7. const char* testluaCode = "local t = {3,4,1,3}"
  8. "table.sort( t, function(a,b) return a > b end )"
  9. ""
  10. "for k,v in pairs(t) do"
  11. " print(k,v)"
  12. "end"; // 这是一种跨行写法,如果直接从文件读取的话应该就不需要这么麻烦了。
  13. int main()
  14. {
  15. lua_State *L = luaL_newstate();
  16. luaL_openlibs(L);
  17. if (luaL_dostring(L,testluaCode)) // 这里执行 lua 代码
  18. printf("Failed!!");
  19. lua_close(L);
  20. return 0;
  21. }

编译运行后有如下输出:

  1. 1 4
  2. 2 3
  3. 3 3
  4. 4 1

在 Lua 中使用 C/C++

1. 自产自销

我们先在 C/C++ 中直接执行使用了 C/C++ 代码的 lua ,代码如下(主要沿用 Stephen_Liu ):

  1. // test.c
  2. #include <stdio.h>
  3. #include <string.h>
  4. extern "C" {
  5. #include "include\lua.h"
  6. #include "include\lauxlib.h"
  7. #include "include\lualib.h"
  8. //待Lua调用的C注册函数。
  9. static int add2(lua_State* L)
  10. {
  11. //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
  12. //如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
  13. double op1 = luaL_checknumber(L,1);
  14. double op2 = luaL_checknumber(L,2);
  15. //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
  16. lua_pushnumber(L,op1 + op2);
  17. //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
  18. return 1;
  19. }
  20. //另一个待Lua调用的C注册函数。
  21. static int sub2(lua_State* L)
  22. {
  23. double op1 = luaL_checknumber(L,1);
  24. double op2 = luaL_checknumber(L,2);
  25. lua_pushnumber(L,op1 - op2);
  26. return 1;
  27. }
  28. }
  29. const char* testfunc = "print(add2(1.0,2.0)) print(sub2(20.1,19))";
  30. int main()
  31. {
  32. lua_State* L = luaL_newstate();
  33. luaL_openlibs(L);
  34. //将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
  35. //在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
  36. lua_register(L, "add2", add2);
  37. lua_register(L, "sub2", sub2);
  38. //在注册完所有的C函数之后,即可在Lua的代码块中使用这些已经注册的C函数了。
  39. if (luaL_dostring(L,testfunc))
  40. printf("Failed to invoke.\n");
  41. lua_close(L);
  42. return 0;
  43. }

编译运行后的结果为:

  1. 3
  2. 1.1

2. 提供链接库

如果能将 C/C++ 编写的函数制成 dll 提供给 lua 使用,可能更贴近实际的需求。

2.1 编写 C/C++ 文件

首先,我们编写 test.c 代码如下(同样基本沿用 Stephen_Liu 的代码):

  1. #include <stdio.h>
  2. #include <string.h>
  3. extern "C" {
  4. #include "include\lua.h"
  5. #include "include\lauxlib.h"
  6. #include "include\lualib.h"
  7. //待Lua调用的C注册函数。
  8. static int add(lua_State* L)
  9. {
  10. //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
  11. //如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
  12. double op1 = luaL_checknumber(L,1);
  13. double op2 = luaL_checknumber(L,2);
  14. //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
  15. lua_pushnumber(L,op1 + op2);
  16. //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
  17. return 1;
  18. }
  19. //另一个待Lua调用的C注册函数。
  20. static int sub(lua_State* L)
  21. {
  22. double op1 = luaL_checknumber(L,1);
  23. double op2 = luaL_checknumber(L,2);
  24. lua_pushnumber(L,op1 - op2);
  25. return 1;
  26. }
  27. }
  28. //luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。
  29. //第一个字段为C函数指针。
  30. //结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。
  31. static luaL_Reg mylibs[] = {
  32. {"add", add},
  33. {"sub", sub},
  34. {NULL, NULL}
  35. };
  36. //该C库的唯一入口函数。其函数签名等同于上面的注册函数。见如下几点说明:
  37. //1. 我们可以将该函数简单的理解为模块的工厂函数。
  38. //2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。
  39. //3. 在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。
  40. //4. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定,
  41. // 否则将无法调用。
  42. extern "C" __declspec(dllexport)
  43. int luaopen_mylib(lua_State* L)
  44. {
  45. const char* libName = "mylib";
  46. luaL_register(L,libName,mylibs);
  47. return 1;
  48. }

2.2 编译链接

使用如下命令进行编译链接,得到 mylib.dll

  1. $ g++ -c -o mylib.obj test.c
  2. $ g++ -shared -o mylib.dll mylib.obj -L . -l lua

注意生成的 dll 名字要与代码中的库名字一致。

2.3 测试

新建一个 test.lua 文件,写入如下代码:

  1. require "mylib" --指定包名称
  2. --在调用时,必须是package.function
  3. print(mylib.add(1.0,2.0))
  4. print(mylib.sub(20.1,19))

得到输出

  1. 3
  2. 1.1

谢谢观赏,感谢风中老狼与Stephen_Liu的博客以及其他参考的博客。

参考资料

Lua和C++交互总结(很详细)[via 风中老狼]:介绍了 lua 与 C 交互的基本原理,
Step By Step(Lua调用C函数)[via Stephen_Liu]:提供了两个 lua 调用 C 函数的代码示例。
绑定Lua和C/C++的库[via 流年]:比较完整的美观的 lua 教程,里面涉及lua参数个数的检查,可以注意下
g++ and make:介绍 g++ 和 make 的使用

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注