有关 Lua 调用 C++ 编译动态库程序

2022-01-06 11:08:39 +08:00
 kanhongj

主要目标

希望能够将 cpp 文件编译成动态库, 以 lua 作为主要运行逻辑(main 函数)

希望解决的问题

  1. 刚入门 Lua 不太明白,若编译了一个 cpp 文件里包含了一个 Worker 对象,Lua 中如何调用生成这个对象并根据公有函数对私有变量进行操作。
  2. 查找到的一些示例代码,大多是将 main 逻辑放在 cpp 文件里,生成元表作为全局变量传入 Lua 脚本进行操作。但是将 Lua 作为程序运行入口,C++ 库作为辅助函数,又该如何操作。
  3. Lua 调试工具各位 Lua 大佬能否介绍一下,或者推荐一些调试方法,lua 的 debug 调试使用我还是有点懵。

自身尝试代码

稍微有点多,感谢耐心观看

classstudy.h

#pragma once
extern "C" {
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
}

#include <string>

class Worker{
    public:
        Worker();
        ~Worker();

        int SetName(std::string &name);
        int SetAge(int &age);
        int SetHight(float &hight);

        std::string GetName();
        int GetAge();
        float GetHight();
    private:
        std::string name;
        int age;
        float hight;
};

extern "C" int luaopen_cstudy(lua_State *L);

classstudy.cpp

#include "classstudy.h"

#include <iostream>


/*
Class Worker 的函数定义省略
*/

static int CreateNewWorker(lua_State *L){
    //ligth_userdata
    // int n = luaL_checkany(L, 1);
    Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*));
    *w1 = new Worker();
    if(luaL_getmetatable(L, "WorkerClass") == false){
        printf("getmetatable nil\n");
    }
    lua_setmetatable(L, -2);
    return 1;
}

static int SetWorkerName(lua_State *L){

    luaL_checktype(L, -1, LUA_TSTRING);
    std::string var_name = lua_tostring(L, -1);
    printf("%s\n", var_name.c_str());

    Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*));
    luaL_argcheck(L, w1 != NULL, 1, "invalid user data");
    (*w1)->SetName(var_name);

    return 0;
}

static int SetWorkerAge(lua_State *L){

	luaL_checktype(L, -1, LUA_TNUMBER);
    int var_age = lua_tointeger(L, -1);
    Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*));
    luaL_argcheck(L, w1 != NULL, 1, "invalid user data");
    (*w1)->SetAge(var_age);
    
    return 0;
}

static int SetWorkerHight(lua_State *L){

    luaL_checktype(L, -1, LUA_TNUMBER);
    float var_hight = lua_tonumber(L, -1);
    Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*));
    luaL_argcheck(L, w1 != NULL, 1, "invalid user data");
    (*w1)->SetHight(var_hight);

    return 0;
}

static int GetWorkerInfo(lua_State *L){

    Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*));
    luaL_argcheck(L, w1 != NULL, 1, "invalid user data");
    printf("Name: %s\n", ((*w1)->GetName()).c_str());
    printf("Age: %d\n", (*w1)->GetAge());
    printf("Hight: %f\n", (*w1)->GetHight());
    return 0;
}

static int DestoryInfo(lua_State* L)
{
    // 释放对象
    delete *(Worker**)lua_topointer(L, 1);
    return 0;
}

const static luaL_Reg mylib[] = {
    // {"NewWorker", CreateNewWorker},
    {"SetName", SetWorkerName},
    {"SetAge", SetWorkerAge},
    {"SetHight", SetWorkerHight},
    {"PrintInfo", GetWorkerInfo},
    {NULL,NULL}
};

int luaopen_cstudy(lua_State *L){
    
    // C\++对象     = 私有数据 + 类(公共数据 + 公共方法)  
    // Lua Table  = 私有数据 + 元表(元数据 + 元函数)

    //  luaL_newlib(L, mylib);
    lua_pushcfunction(L, CreateNewWorker);    // 注册用于创建类的全局函数
    lua_setglobal(L,  "CWorker");
    luaL_newmetatable(L, "WorkerClass");
    
    // 设置自身
    lua_pushstring(L, "__gc");
    lua_pushcfunction(L, DestoryInfo);
    lua_settable(L, -3);
    
    // 设置元表
    lua_pushstring(L, "__index"); // 设置元表为自己
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    lua_pushstring(L, "SetName");
    lua_pushcfunction(L, SetWorkerName);
    lua_settable(L, -3);

    lua_pushstring(L, "SetAge");
    lua_pushcfunction(L, SetWorkerAge);
    lua_settable(L, -3);

    lua_pushstring(L, "SetHight");
    lua_pushcfunction(L, SetWorkerHight);
    lua_settable(L, -3);

    lua_pushstring(L, "PrintInfo");
    lua_pushcfunction(L, GetWorkerInfo);
    lua_settable(L, -3);
    // lua_pop(L, 1);

    return 1;
}

classstudy.lua

local csl = require("cstudy")
local Worker = CWorker()
print(debug.getregistry())

Worker.SetName("hello")
Worker.PrintInfo()
-- csl.SetAge(23)
-- csl:SetHight(180.0)
-- csl:PrintInfo()
3288 次点击
所在节点    Lua
16 条回复
anytk
2022-01-06 11:32:58 +08:00
元表是对象,也就是你的 worker 的元表,包含对象的方法,在方法中检查第一个参数为对应元表名的 userdata, 其他为参数。
模块本身只提供创造对象的方法和必要全局参数。
参考:

```c
#include <lua.h>
#include <lauxlib.h>

static int xxx_read(lua_State *L)
{
struct XXX **xxx = luaL_checkudata(L, 1, "xxx.xxx");
// ...
}

static int xxx_write(lua_State *L)
{
struct XXX **xxx = luaL_checkudata(L, 1, "xxx.xxx");
// ...
}

static int xxx_close(lua_State *L)
{
struct XXX **xxx = luaL_checkudata(L, 1, "xxx.xxx");
if (*xxx) {
// ...
}
return 0;
}

static luaL_Reg XXX_METHODS[] = {
{ "read", xxx_read },
{ "write", xxx_write },
{ "close", xxx_close },
{ "__gc", xxx_close },
{ NULL, NULL },
};

static int xxx_new(lua_State *L)
{
lua_Integer type = luaL_checkinteger(L, 1);
const char *url = luaL_checkstring(L, 2);

int err = 0;
struct XXX *xxx = create_xxx(...);
if (!xxx) {
lua_pushnil(L);
return 1;
} else {
struct XXX **udata = lua_newuserdata(L, sizeof(struct XXX *));
*udata = NULL;
luaL_setmetatable(L, "xxx.xxx");
*udata = xxx;
return 1;
}
}

static luaL_Reg XXX_LIBS[] = {
{ "new", xxx_new },
{ NULL, NULL },
};

int luaopen_xxx.xxx(lua_State *L)
{
luaL_newmetatable(L, "xxx.xxx");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, XXX_METHODS, 0);
lua_pop(L, 1);
luaL_newlib(L, XXX_LIBS);
return 1;
}

```
fengjianxinghun
2022-01-06 11:36:50 +08:00
何必这么麻烦,改用 luajit 的 ffi 走起。
ysc3839
2022-01-06 11:41:50 +08:00
既然是 C++ 的话推荐找一个封装好的库,lua 使用栈传递参数,手写很麻烦还容易出错。
kanhongj
2022-01-06 11:52:03 +08:00
@anytk 谢谢,我一会尝试看看
kanhongj
2022-01-06 12:26:08 +08:00
@fengjianxinghun 这个我后续看看
@ysc3839 😂我一开始是想着热更新去的,如果用库,还是需要重新编译啊
guo4224
2022-01-06 12:27:45 +08:00
ysc3839
2022-01-06 12:36:43 +08:00
@kanhongj 啥意思?我说的是用封装好的调用 lua api 的库,lua 的 C API 使用栈传递参数,手写很麻烦还容易出错。
paoqi2048
2022-01-06 13:22:26 +08:00
不是有一堆 binding 吗?如果是出于学习的目的就当我没说😶
kanhongj
2022-01-07 10:22:26 +08:00
@paoqi2048 刚接触 lua, 我还没了解到这些 binding, 请问有什么推荐学习的文章吗?
@ysc3839 嗯,记下了
kanhongj
2022-01-07 10:31:16 +08:00
@anytk 这个示例我尝试成功了,受教了,但是还是有些疑惑的地方:

1. luaopen_xxx.xxx 函数里边,为什么生成了一个 xxx.xxx 表,且设定了元表,又给 pop 出去了, 这样 new_xxx 方法里边的 luaL_setmetatable(L, "xxx.xxx") 怎么找到的。

2. lua 代码里又一个 local csl = require("xxx.xxx") 这里的 csl 包含了一个 new 方法调用,csl 是上文的 metatable , 还是 luaL_newlib ?
kanhongj
2022-01-07 10:37:03 +08:00
@kanhongj 这个是我的 lua 代码:

```lua

local csl = require("cstudy")
local Worker = csl.new()
Worker:SetName("hello")
-- Worker:PrintInfo()
Worker:SetAge(23)
Worker:SetHight(180.0)
Worker:PrintInfo()

```
kanhongj
2022-01-07 10:38:02 +08:00
@kanhongj 这个是我的 lua 代码:

```lua

local csl = require("cstudy")
local Worker = csl.new()
Worker:SetName("hello")
-- Worker:PrintInfo()
Worker:SetAge(23)
Worker:SetHight(180.0)
Worker:PrintInfo()

```
anytk
2022-01-07 11:19:25 +08:00
@kanhongj
1. 元表是给对象准备的,设置 __index 实现面向对象,所有的方法都绑定到元表中,对象的方法通过 __index 去执行元表内的方法。luaL_newmetatable 这个 api 会将元表加到 REGISTRY 中,这是个全局 table , 所以设定好后,这个元表就可以 pop 掉了,因为 REGISTRY 保存了引用,所以不用担心被回收。
luaL_setmetatable API 同样是从 REGISTRY 中用名称获取元表并绑定对象,可以看看这几个 luaL_ 开头的 api 注释。
2. new 方法是 luaL_newlib 出来的模块 table 方法,不是元表的。
anytk
2022-01-07 11:22:16 +08:00
@kanhongj lua 的 c api 编程,其实就是利用 api 去仿照 lua 语法语义组织执行语句,尤其是 gc 相关。
kanhongj
2022-01-07 15:12:52 +08:00
@anytk 好的,我明了了,谢谢
asuraa
2023-06-25 01:10:53 +08:00
太麻烦了 为啥不用 Luabridge

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/826528

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX