Lua调用C的动态库
C语言可以完成一些lua不好实现的功能,当程序主体使用lua完成时,便需要掌握该技巧调用C来帮助我们达到目的,通过调用C的动态库简化操作流程。
大致流程如下:
- 使用C语言编写方法提供给lua调用
- 将C文件打包成动态库
- lua导入动态库,直接调用里面的函数
准备工作
确保系统安装lua5.4版本。
首先在目录下创建一个func.c文件和一个call.lua文件,Makefile文件可有可无,主要方便后面编译动态库可以直接使用make。
使用C语言编写方法
编写需要加入头文件,如下:
#include <lua.h> // Lua基础函数库,提供lua_前缀函数
#include <lauxlib.h> // 辅助库(可以不加),luaL_前缀函数,利用lua.h实现的更高层的抽象
最重要的是定义一个类似main的主函数:luaopen_*函数,在其中注册所要添加方法
- 在luaopen_func中注册一个函数add,此后lua导入后可以直接通过add调用该函数。
- 函数返回值必须是int类型,返回值是返回参数的个数。
- 函数中固定使用参数lua_State,传入参数和返回参数都需要它。
#include <lua.h>
#include <stdio.h>
// 注意,提供给lua调用的方法必须是static int类型的函数
// 返回值表示该函数返回值的个数
// 函数返回值通过lua_push放入堆栈中
static int add(lua_State *L)
{
printf("C -- add:\n");
// 解析参数
int a = lua_tonumber(L, 1);
int b = lua_tonumber(L, 2);
printf("a is %d\nb is %d\n", a, b);
// 返回给lua的值
lua_pushnumber(L, a + b);
return 1;
}
int luaopen_func(lua_State *L)
{
// 注册方法提供调用,前面是调用名称,后面是调用处理函数
lua_register(L, "add", add);
return 0;
}
需要注意的地方:
- 如例子中使用的是luaopen_func,func就是我们之后在lua中导入的库名称,即require “func”,如果这个名字带有下划线如luaopen_func_test,那么lua中导入方法为require “func.test”。
将C文件打包成动态库
可以使用命令直接打包,也可以写进makefile中,方便之后同时打包多个动态库
- 使用一条命令打包单个动态库gcc func.c -o func.so -fPIC -shared,注意func.so命名跟luaopen_后面的命名有关,并不是跟func.c文件名有关。
- 使用makefile文件
CC := gcc
SRCS := $(wildcard *.c)
TARGETS := $(patsubst %.c, %.so, $(SRCS))
CFLAGS := -fPIC -shared
.PHONY: all clean
all: $(TARGETS)
$(TARGETS):%.so:%.c
$(CC) $< -o $@ $(CFLAGS)
clean:
rm -rf $(TARGETS)
打包后如下图
lua文件中导入动态库并调用其中函数
在同级的call.lua中编写以下代码,直接调用func.so中的函数add
m=require('func')
print("require result:")
print(m)
local a, b = 3, 4
local res = add(a,b)
print("res : " .. res)
运行结果如下:
以上演示的是简单的单个返回值例子,如果有多个不同类型的返回值该如何去执行呢?
多个返回值方法编写示例
注册一个judge方法,传入name和age和now_year,判断出生年份,返回name+出生年份。
在func.c文件中增加一个judge函数,如下
static int judge(lua_State *L)
{
// 解析参数
const char *name = lua_tolstring(L, 1, NULL);
int age = lua_tonumber(L, 2);
int now_year = lua_tonumber(L, 3);
int born_year = now_year - age;
lua_pushstring(L, name);
lua_pushnumber(L, born_year);
// 返回2表示该函数有两个返回值
return 2;
}
// 同时需要在主函数中注册judge方法提供调用
int luaopen_func(lua_State *L)
{
// 注册方法提供调用,前面是调用名称,后面是调用处理方法
lua_register(L, "add", add);
lua_register(L, "judge", judge);
return 0;
}
在call.lua中添加响应的调用语句
local name, age, now_year = "glh", 22, 2022
local newname, born_year = judge(name, age, now_year)
print("newname : " .. newname)
print("born_year : " .. born_year)
运行结果如下:
常用的lua接口
获取函数传入参数
int (lua_isnumber) (lua_State *L, int idx); // 判断传入参数是不是数字
int (lua_isstring) (lua_State *L, int idx); // 判断传入参数是不是字符串
int (lua_iscfunction) (lua_State *L, int idx); // 判断传入参数是不是函数
int (lua_isuserdata) (lua_State *L, int idx); // 判断传入参数是不是table?
int (lua_type) (lua_State *L, int idx); //
const char *(lua_typename) (lua_State *L, int tp); //
int (lua_equal) (lua_State *L, int idx1, int idx2); // 判断传入参数是不是相等
int (lua_rawequal) (lua_State *L, int idx1, int idx2); //
int (lua_lessthan) (lua_State *L, int idx1, int idx2); //
lua_Number (lua_tonumber) (lua_State *L, int idx); // 获取传入的number参数
lua_Integer (lua_tointeger) (lua_State *L, int idx); // 获取传入的int参数
int (lua_toboolean) (lua_State *L, int idx); // 获取传入的bool参数
const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); // 获取传入的string参数,注意是传长度地址!
size_t (lua_objlen) (lua_State *L, int idx);
lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
void *(lua_touserdata) (lua_State *L, int idx);
lua_State *(lua_tothread) (lua_State *L, int idx);
const void *(lua_topointer) (lua_State *L, int idx);
设置函数返回值
void (lua_pushnil) (lua_State *L);
void (lua_pushnumber) (lua_State *L, lua_Number n);
void (lua_pushinteger) (lua_State *L, lua_Integer n);
void (lua_pushlstring) (lua_State *L, const char *s, size_t l);
void (lua_pushstring) (lua_State *L, const char *s);
const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp);
const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
void (lua_pushboolean) (lua_State *L, int b);
void (lua_pushlightuserdata) (lua_State *L, void *p);
int (lua_pushthread) (lua_State *L);
这边只举了一些常用的获取参数和设置返回值的方法,更多详细方法可以参考/usr/include/lua/lua.h头文件。
本文只简单分析了lua.h包含的一些接口并且成功执行调用,更深入的用法可以使用lauxlib.h,此处不多做介绍。