Bubble's blog
Home
About
把C++静态库lib封装到Lua解释器中
Category:
技术
Date:
2024-10-10T11:10:53Z
# 把C++静态库lib封装到Lua解释器中 本文介绍了Lua和C/C++交互的三种方式: 1. C代码中调用执行Lua脚本 2. Lua脚本调用C编写的dll库 3. 把C/C++编写的静态lib库封装进Lua解释器,由Lua脚本调用 前两种方式网上已经有很多现成的文章了,只做粗略的介绍,由于此次我的需求是把代码封装成lib库,再编译链接进lua解释器,最终由lua脚本直接调用,因此本文重点对第三种方式做介绍,并尽量详细记录此过程遇到过的坑。 ## 前置相关知识总结 Lua是一个很小巧的脚本语言,由C编写,源码开放([带注释的源码](https://github.com/lichuang/Lua-5.1.4-codedump)),主要应用于游戏编程领域,上手容易,可以轻松实现热更新。Lua解释器开源,在原生解释器的基础上可以进行扩展编程,如比较有名的luajit、TINN,都是对Lua解释器做封装,赋予了新的可能。既然是要扩展,就免不了需要在Lua和C/C++之间做交互。 Lua与C相互调用的首要问题是如何交换数据,Lua API使用了一个抽象的栈与C语言交换数据,提供了压入元素,查询元素和弹出元素等功能的API操作栈,[这里](https://blog.csdn.net/CoderAldrich/article/details/87723488)可以查看Lua的C api接口,栈中的元素可以通过索引访问,从栈底向上是从1开始递增的正整数,从栈顶向下是从-1开始递减的负整数,栈的元素按照FIFO的规则进出。 ### C调用Lua 在C中嵌入Lua脚本既可以让用户在不重新编译代码的情况下修改Lua代码更新程序,也可以给用户提供一个自由定制的接口,这种方法遵循了机制与策略分离的原则。 执行Lua脚本的C代码如下: ```cpp lua_State* state = luaL_newstate(); luaL_openlibs(state); if (luaL_dostring(state, "print([[lua env is ready]])") != 0) { printf("lua env is bad!\n"); } lua_close(state); ``` - luaL_newstate创建了一个新的lua_State,我称之为Lua的一个会话空间,之后C和Lua的操作都依赖于这个会话空间; - luaL_openlibs把默认模块加载到会话空间,以便执行Lua时使用; - luaL_dostring执行指定的Lua代码,此处也可以是luaL_dofile加载Lua脚本文件并执行。 ### Lua调用C Lua可以调用C函数的能力将极大的提高Lua的可扩展性和可用性。对于有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数。对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即`typedef int (*lua_CFunction)(lua_State* L)`。简单说明一下,该函数类型仅仅包含一个表示Lua环境的指针作为其唯一的参数,实现者可以通过该指针进一步获取Lua代码中实际传入的参数。返回值是整型,表示该C函数将返回给Lua代码的返回值数量,如果没有返回值,则return 0即可。需要说明的是,C函数无法直接将真正的返回值返回给Lua代码,而是通过虚拟栈来传递Lua代码和C函数之间的调用参数和返回值的。这里我们将介绍两种Lua调用C函数的规则。 #### C函数作为应用程序的一部分 ```cpp #include <stdio.h> #include <string.h> #include <lua.hpp> #include <lauxlib.h> #include <lualib.h> //待Lua调用的C注册函数。 static int add2(lua_State* L) { //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。 //如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。 double op1 = luaL_checknumber(L,1); double op2 = luaL_checknumber(L,2); //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。 lua_pushnumber(L,op1 + op2); //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。 return 1; } //另一个待Lua调用的C注册函数。 static int sub2(lua_State* L) { double op1 = luaL_checknumber(L,1); double op2 = luaL_checknumber(L,2); lua_pushnumber(L,op1 - op2); return 1; } const char* testfunc = "print(add2(1.0,2.0)) print(sub2(20.1,19))"; int main() { lua_State* L = luaL_newstate(); luaL_openlibs(L); //将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码 //在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。 lua_register(L, "add2", add2); lua_register(L, "sub2", sub2); //在注册完所有的C函数之后,即可在Lua的代码块中使用这些已经注册的C函数了。 if (luaL_dostring(L,testfunc)) printf("Failed to invoke.\n"); lua_close(L); return 0; } ``` #### C函数dll成为Lua的模块 ```cpp #include <stdio.h> #include <string.h> #include <lua.hpp> #include <lauxlib.h> #include <lualib.h> //待注册的C函数,该函数的声明形式在上面的例子中已经给出。 //需要说明的是,该函数必须以C的形式被导出,因此extern "C"是必须的。 //函数代码和上例相同,这里不再赘述。 extern "C" int add(lua_State* L) { double op1 = luaL_checknumber(L,1); double op2 = luaL_checknumber(L,2); lua_pushnumber(L,op1 + op2); return 1; } extern "C" int sub(lua_State* L) { double op1 = luaL_checknumber(L,1); double op2 = luaL_checknumber(L,2); lua_pushnumber(L,op1 - op2); return 1; } //luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。 //第一个字段为C函数指针。 //结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。 static luaL_Reg mylibs[] = { {"add", add}, {"sub", sub}, {NULL, NULL} }; //该C库的唯一入口函数。其函数签名等同于上面的注册函数。见如下几点说明: //1. 我们可以将该函数简单的理解为模块的工厂函数。 //2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。 //3. 在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。 //4. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定, // 否则将无法调用。 extern "C" __declspec(dllexport) int luaopen_mytestlib(lua_State* L) { const char* libName = "mytestlib"; luaL_register(L,libName,mylibs); return 1; } ``` Lua代码: ```lua require "mytestlib" --指定包名称 --在调用时,必须是package.function print(mytestlib.add(1.0,2.0)) print(mytestlib.sub(20.1,19)) ``` ## Lua使用C++静态库 Lua使用C++静态库本质上与上一节的C函数作为应用程序的一部分由Lua调用一样,关键点是编写静态库以及链接符号识别问题,原因是Lua是纯粹的C代码,而我们想要写的lib库也不希望被限制只能用纯C,C++又会由于生成的符号命名规则不同导致连接时报`无法解析的外部符号`这个错误,可谓一步一个坑,接下来一步一步对过程进行介绍。 ### 用C++为Lua编写静态库 1. 首先正常编写静态库,导出函数声明到头文件中,如下: ```cpp #pragma once #include <Windows.h> #include <string> BOOL fun1(); BOOL fun2(); BOOL fun3(char* str1, char* str2); ``` 编写好lib之后确保能正常被C调用。 2. 使用[tolua++](https://github.com/LuaDist/toluapp)自动生成符合Lua形式的cpp源码 关于tolua++使用的介绍可以移步[这里](https://blog.csdn.net/xingxinmanong/article/details/78137514)。 生成的cpp源码文件添加进静态库的工程,重新生成,有错误的话检查一下生成的cpp源码头文件包含的时候齐全,写package的时候容易漏掉一些头文件,还要注意把该去掉的信息去掉,如`using namespace std;`这句就不该被写入package文件。 3. 生成lib时记得把“链接库依赖项”设置为“是” lib成功生成之后,接下来就要把lib静态库编译进Lua解释器源码中。 ### 把C++静态库编译到Lua解释器 1. 把上一步生成的lib静态库文件复制到Lua代码包的src目录,找到msvcbuild.bat,定位到link命令那行,把静态库的全名添加进去; 2. 观察发现Lua解释器源码后缀名都是.c,是不能直接调用cpp生成的lib库的,因此把luajit.c后缀名改为cpp,luajit.cpp内部包含的头文件用extern “C"{}包住,意思是这些依旧用C的编译风格进行编译; 3. 如果只是到此为止,直接把lib编译链接进luajit.exe的话,会导致lua脚本代码调用lib中接口时报无法找到标识符的错误,因此需要调用注册函数。用tolua++生成的cpp源文件中有一个Open function,作用就是向当前lua_State会话空间注册函数符号,并把函数符号跟对应的C++函数关联起来,此函数需要在调用完`luaL_newstate()`之后调用,因此我们打开luajit.cpp源码,找到main函数,在`luaL_newstate()`后面添加如下代码: ```cpp int tolua_cppfilename_open(lua_State*);// 因为lib库没有带头文件,所以需要声明函数原型,否则会报找不到函数的错误 tolua_cppfilename_open(L); ``` 4. 再次运行msvcbuild.bat,此时生成的luajit.exe就是包含了lib库的程序。 ### Lua调用C++静态库中的接口 由于代码是以静态库的方式编译链接进exe中的,所以不存在模块的说法,因此根本不需要`require()`操作,可以把lib中的接口当做内置函数直接调用: ```lua -- 此lua程序用来测试lua脚本能否成功调用lib中的导出接口 if fun3("str1", "str2") then -- body print("fun3successed"); else -- body print("fun3failed"); end if fun2() then -- body print("fun2 is reachable"); else print("fun2 is un reachable"); end if fun1() then -- body print("fun1 successed"); else -- body print("fun1 failed"); end ``` ## 总结遇到的坑 因为之前对lua完全不了解,所以在开始这个任务的时候绕了很大的弯子,搜到最多的资料是为lua编写dll,现在回头开会觉得很简单。容易出问题的点有以下两个: 1. 编写lib库时要提供注册函数,把函数注册到lua_state中; 2. c和cpp混用问题,由于c和cpp编译方式有区别,而现在写代码一般都是用的cpp后缀,因此命名规则的改变会导致找不到符号; 本文参考了: 1. [https://www.cnblogs.com/chenny7/p/3993456.html](https://www.cnblogs.com/chenny7/p/3993456.html) 2. [https://blog.csdn.net/xingxinmanong/article/details/78137514](https://blog.csdn.net/xingxinmanong/article/details/78137514) 如果想要进一步把stl、C++类封装进lib使用,可以参考这篇内容: - [FFLIB之FFLUA——C++嵌入Lua&扩展Lua利器](https://cloud.tencent.com/developer/article/1056709)
Share
Permalink
×
0 Comments
latest
No comments.
Name
Email
Site
Comment
Human check: 4 + 7 = ?
Links
CSDN
GitHub
WHOIS
吾爱破解
看雪论坛
Categories
Default
0
技术
24
时事
0
Change Theme
Perfect Blue
Black Swan
0 Comments latest
No comments.