前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lua学习笔记:在Lua中调用C/C++函数示例

Lua学习笔记:在Lua中调用C/C++函数示例

原创
作者头像
晨星成焰
发布2024-09-10 10:43:18
910
发布2024-09-10 10:43:18
举报
文章被收录于专栏:Lua学习笔记C++入门基础知识

前文须知

Lua的VS安装参考此文:

本文会通过一些示例展示如何用lua调用C/C++传递过来的函数,并辅以部分解释语句:


Lua中调用C/C++函数简介:

  • 任何在Lua中注册的C函数必须有同样的原型, typedef int (lua_CFunction) (lua_State L); 其定义在"lua.h"中。 被注册的C函数接收一个单一的lua_State类型的参数,同时返回一个表示返回值个数的数字。
  • 而Lua利用一个虚拟的堆栈来给C传递值或从C获取值。每当Lua调用C函数,都会获得一个新的堆栈,该堆栈初始包含所有的调用C函数所需要的参数值(Lua传给C函数的调用实参),并且C函数执行完毕后,会把返回值压入这个栈(Lua从中拿到C函数调用结果)。
  • 对lua堆栈不太理解的可以去搜 Lua初学者(四)--Lua调用原理展示(lua的堆栈)这篇文章

c/c++注册函数给lua调用

C/C++注册函数给lua的方式有多种

  1. 使用lua_register通过 _G 表间接地将函数注册到全局环境中
  2. lua_pushcfunction到栈里再通过lua_setglobal取出注册到_G表里或者通过
  3. 使用lua_rawsetfield /lua_setfield注册到特定的表里

1.函数注册到全局环境的方式

无参函数

代码语言:cpp
复制
#include <stdio.h>
#include <lua.hpp>
extern "C" {
	// 一个Lua函数的标准模型
	LUALIB_API int lua_TestFunc2(lua_State* L)
	{
		printf("lua调用C函数\n");
		// 表示有0个返回值
		return 0;
	}
}

int main()
{
	// 创建一个虚拟机
	lua_State* L = luaL_newstate();

	// 加载一些常用的系统库
	luaL_openlibs(L);

	// 注册一个函数给lua  以k v 的形式注册
	lua_register(L, "testFunc", lua_TestFunc2);

     /* 因为其宏定义为
     * #define lua_register(L,n,f) 
     * (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
     * 所以等价于
     * 向栈中压入一个函数至栈顶
     *  lua_pushcfunction(L, lua_TestFunc);	
     * 将栈顶的名称元素设置名称为testFunc 在lua中可以范围该名称
     *  lua_setglobal(L, "testFunc");
     */

	// 加载lua文件并执行
	luaL_dofile(L, "Test2.lua");

	// 关闭虚拟机
	lua_close(L);
	return 0;
}
代码语言:lua
复制
-- Test2.lua
print("Lua_Hello World!")
testFunc()
  • 对于LUALIB_API 这是一个为了确保函数能够被正确地导出并在 Lua 中调用的宏
  • extern "C"是为了确保以C的编译器去编译,避免C++的编译器导致的错误,毕竟lua是纯C的
  • 一般要暴露给lua的函数都可以如上标注以防奇怪的错误。

有参函数的注册互动

代码语言:cpp
复制
#include <stdio.h>
#include <lua.hpp>

extern "C" {
	// 一个Lua函数的标准模型
	LUALIB_API int lua_TestFunc3(lua_State* L)
	{
		// 获取有几个参数
		int top = lua_gettop(L);
		printf("传入几个参数 top:[%d]\n", top);
		// 下标从1开始  检查第一个参数是否为整型,是则返回整型
		int num1 = luaL_checkinteger(L, 1);
		int num2 = luaL_checkinteger(L, 2);
		int num3 = luaL_checkinteger(L, 3);
		printf("从栈底由下到上开始取数据:%d %d %d\n", num1,num2,num3);

		int num4 = luaL_checkinteger(L, -1);
		int num5 = luaL_checkinteger(L, -2);
		int num6 = luaL_checkinteger(L, -3);
		printf("从栈顶由上到下开始取数据:%d %d %d\n", num4, num5, num6);

		lua_pushinteger(L, 999);// 压入第一个参数 整型
		lua_pushstring(L, "hello World!!!!!");// 压入地二个参数 字符串
		// 表示有2个返回值
		return 2;
	}
}

int main()
{
	// 创建一个虚拟机
	lua_State* L = luaL_newstate();

	// 加载一些常用的系统库
	luaL_openlibs(L);
	// 注册一个函数给lua全局环境
	lua_register(L, "testFunc", lua_TestFunc3);

	// 加载lua文件并执行
	if (luaL_dofile(L, "Test3.lua"))
	{
		// 在lua中 -1表示栈顶 如果出错 出错结果会放置在栈顶中
		printf("%s\n", lua_tostring(L, -1));
	}
	// 关闭虚拟机
	lua_close(L);
	return 0;
}

通过从lua的栈里取出数据作为函数的参数使用

在push数据到lua的栈里后,需要函数的返回值告诉lua有几个返回值

Tips:

  • 正数索引是从栈底开始计数的,索引 1 表示栈底的第一个元素(即最先进入栈的元素),索引 2 表示栈底的第二个元素,依此类推。
  • 负数索引是从栈顶开始计数的,索引 -1 表示栈顶的元素(即最近进入栈的元素),索引 -2 表示栈顶之前的元素,依此类推。 通常建议使用负数索引😄
代码语言:lua
复制
-- Test3.lua
print("Lua_Hello World!")
-- 传入参数 返回多个参数
local a,b = testFunc(123,1,2)
print(a..'===='..b)
  • 参数传递:
    • 参数是从 Lua 到 C 函数的单向传递。在 Lua 中,通过 testFunc(123, 1, 2) 向 C 函数传递了三个参数。
    • C 函数 lua_TestFunc3 通过 luaL_checkinteger(L, 1) 处理了第一个参数。
  • 返回值:
    • C 函数通过 lua_pushinteger 和 lua_pushstring 将两个值压入栈,并通过 return 2 指定返回两个值。
    • Lua 脚本通过 local a, b = testFunc(123, 1, 2) 接收这两个返回值,并将它们赋值给变量 a 和 b。

2.将类里的函数暴露给lua

1.传递类和类的函数

代码语言:cpp
复制
#include <stdio.h>
#include <lua.hpp>

class MyClass {
private:
	int _value;
public:

	// 构造函数
	MyClass(int value) : _value(value) {}

	// 成员函数
	void setValue(int newValue) 
	{
		_value = newValue;
	}

	int getValue() const {
		return _value;
	}
};

extern "C" {
	// 注册 C++ 类的方法
	LUALIB_API  int lua_myClassSetValue(lua_State* L) {
		MyClass* obj = (MyClass*)lua_touserdata(L, 1);
		int newValue = luaL_checkinteger(L, 2);
		obj->setValue(newValue);
		return 0;
	}

	LUALIB_API  int lua_myClassGetValue(lua_State* L) {
		MyClass* obj = (MyClass*)lua_touserdata(L, 1);
		lua_pushinteger(L, obj->getValue());
		return 1;
	}

	// 用于在 Lua 中创建 C++ 类的实例
	LUALIB_API  int lua_createMyClass(lua_State* L) {
		int initialValue = luaL_optinteger(L, 1, 0);
		MyClass* obj = new MyClass(initialValue);
		lua_pushlightuserdata(L, obj);
		return 1;
	}
}

// 导入模块
int main() {
	lua_State* L = luaL_newstate();
	luaL_openlibs(L);

	// 注册 C++ 类的方法
	lua_register(L, "createMyClass", lua_createMyClass);
	lua_register(L, "myClassSetValue", lua_myClassSetValue);
	lua_register(L, "myClassGetValue", lua_myClassGetValue);

	// 加载 Lua 脚本
	luaL_dofile(L, "ClassToLua.lua");

	// 清理资源
	lua_close(L);

	return 0;
}

通过lua_pushlightuserdata将类的指针暴露给lua,然后类成员函数通过lua_touserdata获得lua传过来的类的指针。

代码语言:lua
复制
-- ClassToLua.lua

-- 创建一个 MyClass 实例
local myObject = createMyClass(10)

-- 获取值
local val = myClassGetValue(myObject)
print(val) -- 输出: 10

-- 设置值
myClassSetValue(myObject, 20)

-- 获取值
local val = myClassGetValue(myObject)
print(val) -- 输出: 20

2.借助适配器模式只传递类的某个函数调用

代码语言:cpp
复制
#include <iostream>
#include <lua.hpp>

using namespace std;

class AdapterTest
{
private:
	int _value;
public:
	AdapterTest(int value) :_value(value) {}
	int GetValue() { return _value; }
};
AdapterTest* obj = new AdapterTest(10);

int AdapterFuncToLua(lua_State* L)
{
	// 获取对象的值
	int value = obj->GetValue();

	// 将值压入Lua栈顶,这样Lua才能访问它
	lua_pushinteger(L, value);

	// 返回栈顶元素的数量(这里是1)
	return 1;
}
//AdapterFuncToLua 将类成员函数转变为lua_CFunction的形式供lua使用

int main()
{
	// 创建一个虚拟机
	lua_State* L = luaL_newstate();

	// 加载一些常用的系统库
	luaL_openlibs(L);

	// 注册一个类的函数给lua
	lua_register(L, "GetClassValue", AdapterFuncToLua);

	// 加载lua文件并执行
	luaL_dofile(L, "Test9.lua");

	// 关闭虚拟机
	lua_close(L);
	return 0;
}

这里的关键是使用相应的函数来将C++中的类成员函数转换为Lua可以理解的lua_CFunction形式

代码语言:lua
复制
-- Test9.lua

print(GetClassValue) -- 打印函数地址

local value = GetClassValue()

print(value) -- 打印返回值10

3.C/C++注册函数给lua的表调用

注册函数给表分为逐个注册和批量注册的方式

  • 逐个注册适合于函数数量较少或需要动态注册的情况。
  • 批量注册通过一个数组就可以完成多个函数的注册,减少了重复代码。

1.逐个注册

代码语言:cpp
复制
#include <stdio.h>
#include <lua.hpp>

// 定义一个Lua函数
extern "C" {
	LUALIB_API int lua_TestFuncTable(lua_State* L) {
		printf("lua调用C函数\n");
		return 0;
	}
}

// 定义另一个Lua函数
extern "C" {
	LUALIB_API int lua_TestFuncTable2(lua_State* L) {
		int num1 = luaL_checkinteger(L, 1);
		printf("传入参数: %d\n", num1);
		lua_pushinteger(L, num1 * 2); // 返回参数的两倍
		return 1;
	}
}

int RgFuncToTable() {
	// 创建一个虚拟机
	lua_State* L = luaL_newstate();

	// 加载一些常用的系统库
	luaL_openlibs(L);

	// 创建一个表
	lua_newtable(L);

	// 将函数注册到表中
	lua_pushcfunction(L, lua_TestFuncTable);
	lua_setfield(L, -2, "func1"); // -2 表示栈中倒数第二个元素,即刚创建的表

	lua_pushcfunction(L, lua_TestFuncTable2);
	lua_setfield(L, -2, "func2"); // -2 表示栈中倒数第二个元素,即刚创建的表

	// 将表压入全局环境
	lua_setglobal(L, "myTable");

	// 加载lua文件并执行
	if (luaL_dofile(L, "RgFuncToTable.lua")) {
		// 在lua中 -1表示栈顶 如果出错 出错结果会放置在栈顶中
		printf("%s\n", lua_tostring(L, -1));
	}

	// 关闭虚拟机
	lua_close(L);
	return 0;
}
  • 创建表
  • 注册函数到表中:首先使用lua_pushcfunction()将函数压入栈中,然后使用lua_setfield()将函数添加到表中。
  • 在Lua脚本中调用函数
代码语言:lua
复制
-- RgFuncToTable.lua
print("RgFuncToTable.lua")

-- 调用表中的函数
myTable.func1()
local numTwo = myTable.func2(5)

print('num*2='..numTwo)

2.通过luaL_Reg批量注册

代码语言:cpp
复制
#include <stdio.h>
#include <lua.hpp>
#include <lauxlib.h>

// 定义Lua函数
extern "C" {
    LUALIB_API int lua_TestFuncTable(lua_State* L) {
        printf("lua调用C函数\n");
        return 0;
    }

    LUALIB_API int lua_TestFuncTable2(lua_State* L) {
        int num1 = luaL_checkinteger(L, 1);
        printf("传入参数: %d\n", num1);
        lua_pushinteger(L, num1 * 2); // 返回参数的两倍
        return 1;
    }
}

// 使用luaL_Reg批量注册函数
static const luaL_Reg methods[] = {
    {"func1", lua_TestFuncTable},
    {"func2", lua_TestFuncTable2},
    {NULL, NULL} // 结束标志
};

LUAMOD_API int Lua_ModelTest(lua_State* L) {
    // 创建一个表
    lua_newtable(L);

    // 批量注册函数到表中
    luaL_setfuncs(L, methods, 0); // 第二个参数是methods数组,第三个参数是upvalues的数量,这里为0
    return 1;
}

int main() {
    // 创建一个虚拟机
    lua_State* L = luaL_newstate();

    // 加载一些常用的系统库
    luaL_openlibs(L);

    luaL_requiref(L, "myTable", Lua_ModelTest, 1);
    //or
    // lua_newtable(L);
    // luaL_setfuncs(L, methods, 0); 
    // lua_setglobal(L, "myTable");
    

    // 加载lua文件并执行
    if (luaL_dofile(L, "RgFuncToTable.lua")) {
        // 在lua中 -1表示栈顶 如果出错 出错结果会放置在栈顶中
        printf("%s\n", lua_tostring(L, -1));
    }

    // 关闭虚拟机
    lua_close(L);
    return 0;
}
代码语言:lua
复制
-- RgFuncToTable.lua
print("RgFuncToTable.lua")

-- 调用表中的函数
myTable.func1()
local numTwo = myTable.func2(5)

print('num*2='..numTwo)

附加总结

本文列出的是一些简单的Lua调用C/C++函数的示例,还有许多方式例如将C函数库变成一个dll模块让Lua调用等等,本文暂不收录示例,感兴趣可以自行搜索。

参考文章

编译成DLL模块可参考文章:

Lua中调用C函数

有疑惑的地方可以参考:

Lua5.3参考手册

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前文须知
  • c/c++注册函数给lua调用
    • 1.函数注册到全局环境的方式
      • 无参函数
      • 有参函数的注册互动
    • 2.将类里的函数暴露给lua
      • 1.传递类和类的函数
      • 2.借助适配器模式只传递类的某个函数调用
    • 3.C/C++注册函数给lua的表调用
      • 1.逐个注册
      • 2.通过luaL_Reg批量注册
  • 附加总结
    • 参考文章
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档