【问题标题】:Lua C API - Assign and use object member of a class from C++Lua C API - 从 C++ 分配和使用类的对象成员
【发布时间】:2020-03-30 23:43:15
【问题描述】:

我正在尝试从 C++ 实例化和使用 Lua 类的对象。 Lua 类是这样定义的。

myclass = {}

function myclass:new(o)
   o=o or {}
   setmetatable(o,self)
   self.__index=self
   return o
end

function myclass:init()
   self.something = 0
end

function myclass:perform()
   self.something = self.something + 0.5
   return performsomething(self.something)
end

要在 C++ 中实例化对象,我执行以下操作:

lua_getglobal(L,"myclass");
lua_getfield(L, -1, "new");
lua_pcall(L,0,1,0);
lua_newtable(L);
lua_setglobal(L, "objname");

然后进行初始化:

lua_getglobal(L,"myclass");
lua_getfield(L, -1, "init");
lua_getglobal(L,"objname");
lua_pcall(L, 0, 0, 0);

然后执行:

lua_getglobal(L, "myclass");
lua_getfield(L, -1, "perform");
lua_getglobal(L, "objname");
lua_pcall(L, 0, 1, 0);
double res = lua_tonumber(-1);

对于这个例子,我没有包含我在需要时使用的lua_pop() 方法。

看来,通过打印,我可以获得以下信息。使用新方法成功实例化 Lua 对象。它也在init方法中成功初始化。但是,当调用perform方法时,self.something成员并没有改变,它的值被固定为0,这似乎意味着我没有调用对象成员方法。
我很确定我管理 Lua 堆栈以访问对象成员函数的方式有问题。

是否有人已经处理过类似的案例并可以在这里提供帮助? 谢谢 最好的

【问题讨论】:

  • 我不知道该怎么做。
  • 对不起,我的第一条评论是错误的。 lua_pcall(L, 0, 1, 0); 表示有 0 个参数,但是您已将对象 lua_getglobal(L, "objname"); 作为第一个参数推送,因此您应该使用 lua_pcall(L, 1, 1, 0);
  • 另外,您忽略了myclass:new() 的结果,而是将新的空表保存在名称objname 下。在调用 myclass 构造函数后删除 lua_newtable(L)
  • @EgorSkriptunoff 我认为这对于 init 和 perform(我现在修复了)方法是正确的,因为我实际上将 objname 作为 self 参数传递。但是你确定新方法应该接收一个参数,因为我只将它的返回值分配给 objname global 吗?
  • @Vlad 我这样做了,然后在执行方法中 self.something 等于 nil。我认为这里仍然存在实例化问题。

标签: c++ lua lua-api


【解决方案1】:

很可能您没有将 nargs 参数修复为 lua_pcall 函数。

当使用冒号语法 (class:func()) 定义函数时,您必须从 C/C++ 端显式传递 self 参数。在你的例子中没有这样的东西。

对您的代码进行最少的更改,它看起来像这样:

Lua 端:

function performsomething(x)
    print("type:", type(x))
    print("value:", x)
    return x
end

myclass = {}

function myclass:new(o)
   o=o or {}
   setmetatable(o,self)
   self.__index=self
   return o
end

function myclass:init()
   self.something = 0
end

function myclass:perform()
   self.something = self.something + 0.5
   return performsomething(self.something)
end

C/C++端:

#include <stdio.h>
#include <lualib.h>
#include <lauxlib.h>

int main()
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    luaL_dofile(L, "myclass.lua");

    // instantiate
    lua_getglobal(L,"myclass");
    lua_getfield(L, -1, "new");
    lua_getglobal(L, "myclass");
    lua_pcall(L,1,1,0);
    lua_setglobal(L, "objname");

    // init
    lua_getglobal(L,"myclass");
    lua_getfield(L, -1, "init");
    lua_getglobal(L,"objname");
    lua_pcall(L, 1, 0, 0);

    // perform
    lua_getglobal(L, "myclass");
    lua_getfield(L, -1, "perform");
    lua_getglobal(L, "objname");
    lua_pcall(L, 1, 1, 0);

    double res = lua_tonumber(L, -1);
    printf("Result: %f\n", res);

    lua_close(L);

    return 0;
}

【讨论】:

    【解决方案2】:

    在以下示例中,我用 C++ 创建了一个简单的类,可以使用 Lua C API 在 Lua 中使用。完成后,我想其中大部分超出了您的问题范围;但是,我希望我的解释和示例有所帮助。

    当我使用 Lua 的 C API 创建 userdata 时,这就是我通常使用 C++ 编写 main 函数的方式。在本例中,MyClass.h 是我为本示例编写的简单类的头文件。

    #include <iostream>
    #include <stdexcept>
    #include <cstdio>
    #include <cstdlib>
    #include "MyClass.h"
    extern "C" {
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
    }
    
    int main(int argc, char** argv) {
        // Check to make sure you have at least one .lua input file.
        if (argc < 2) {
            std::cerr << "Filename(s) expected from command line" << std::endl;
            std::exit(EXIT_FAILURE);
        }
    
        // Try-catch block to handle any errors encountered when executing the Lua file(s).
        try {
            lua_State* L = luaL_newstate(L);
            luaL_openlibs(L);
            // Call a function to register Lua methods for `MyClass`; its declaration and definition are below.
            lua_MyClass_register(L);
            for (char** cpp = argv + 1; cpp < argv + argc; ++cpp) {
                // Run the Lua script--provided as a command-line arg--and handle errors.
                if (luaL_dofile(L, *cpp)) {
                    const char* err = lua_tostring(L, -1);
                    lua_close(L);
                    throw std::runtime_error(err);
                }
            }
            lua_close(L);
        } catch (const std::runtime_error &err) {
            // Catch fatal errors from the Lua script and exit.
            std::cerr << err.what() << std::endl;
            std::exit(EXIT_FAILURE);
        }
        return 0;
    }
    

    现在是文件MyClass.h,它包含我的简单示例类的声明:

    #ifndef MYCLASS_H
    #define MYCLASS_H
    #define LUA_MYCLASS "MyClass"
    extern "C" {
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
    }
    
    // Class declaration.
    class MyClass {
    public:
        MyClass(int something_in);
        ~MyClass();
        // Setter function to change the value of `something`.
        void set(int something_in);
        // Getter function to return `something`.
        const int & get() const;
    private:
        int something;
    };
    
    // The following declarations are for the C functions that correspond to `MyClass`'s member function.
    int lua_MyClass_new(lua_State* L);
    int lua_MyClass_delete(lua_State* L);
    int lua_MyClass_get(lua_State* L);
    int lua_MyClass_set(lua_State* L);
    int lua_MyClass_newindex(lua_state* L);
    int lua_MyClass_table_newindex(lua_State* L);
    void lua_MyClass_register(lua_State* L);
    
    // `luaL_Reg` is a struct provided in one of the Lua header files used to register userdata methods.
    // This one is for the class methods.
    static const luaL_Reg MyClass_methods[] = {
        {"set", lua_MyClass_set},
        {"get", lua_MyClass_get},
        {nullptr, nullptr}
    };
    
    // This one is for the class metamethods.
    static const luaL_Reg MyClass_metamethods[] = {
        {"__gc", lua_MyClass_delete},
        {"__newindex", lua_MyClass_newindex},
        {nullptr, nullptr}
    };
    
    #endif
    

    这里是MyClass.h对应的.cpp文件:

    #include "MyClass.h"
    
    // These are the class member function definitions.
    MyClass::MyClass(int something_in) {
        something = something_in;
    }
    
    MyClass::~MyClass() {
    
    }
    
    void MyClass::set(int something_in) {
        something = something_in;
        return;
    }
    
    const int & MyClass::get() const {
        return something;
    }
    
    // These are the definitions for the C functions that Lua will use to call the member functions.
    // `MyClass` constructor, which corresponds to `MyClass.new` in Lua.
    int lua_MyClass_new(lua_State* L) {
        // Optional argument to supply to the `MyClass` constructor; using `luaL_optinteger`, it defaults to 0.
        int something_in = static_cast<int>(luaL_optinteger(L, 1, 0));
        MyClass** mcpp = reinterpret_cast<MyClass**>(lua_newuserdata(L, sizeof(MyClass**)));
        *mcpp = new MyClass(something_in);
        luaL_setmetatable(L, LUA_MYCLASS);
        return 1;
    }
    
    // `MyClass` destructor, which corresponds to the `__gc` metamethod in Lua.
    int lua_MyClass_delete(lua_State* L) {
        MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
        delete mcp;
        return 0;
    }
    
    // C function corresponding to `MyClass::set`.
    int lua_MyClass_set(lua_State* L) {
        MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
        int something_in = static_cast<int>(luaL_checkinteger(L, 2));
        mcp->set(something_in);
        return 0;
    }
    
    // C function corresponding to `MyClass::get`.
    int lua_MyClass_get(lua_State* L) {
        MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
        lua_pushinteger(L, mcp->get());
        return 1;
    }
    
    // `__newindex` metamethod for `MyClass` userdata that prevents any members from being added.
    int lua_MyClass_newindex(lua_State* L) {
        return luaL_error(L, "attempt to modify a read-only object");
    }
    
    // `__newindex` metamethod for the `MyClass` table that prevents any methods from being added--I will explain more below.
    int lua_MyClass_table_newindex(lua_State* L) {
        return luaL_error(L, "attempt to modify a read-only table");
    }
    
    // Function to register all the above functions for use in Lua; this gets called in `main.cpp`.
    void lua_MyClass_register(lua_State* L) {
        // Create a global table that will contain all the `MyClass` methods as functions.
        // Include `lua_MyClass_new` as a constructor in the form `MyClass.new`.
        lua_newtable(L);
        lua_pushcfunction(L, lua_MyClass_new);
        lua_setfield(L, -2, "new");
        // Include `MyClass::get` and `MyClass::set` in this table as well.
        luaL_setfuncs(L, MyClass_methods, 0);
    
        // Create a metatable for the global table `MyClass`--which was just created.
        lua_newtable(L);
        // Prevent access to the metatable.
        lua_pushliteral(L, "metatable");
        lua_setfield(L, -2, "__metatable");
        lua_pushcfunction(L, lua_MyClass_table_newindex);
        lua_setfield(L, -2, "__newindex");
        // Set this second table as the metatable for the one created above.
        lua_setmetatable(L, -2);
        // Call the first table "MyClass" and add it to the global environment table (_ENV).
        lua_setglobal(L, LUA_MYCLASS);
    
        // Create a metatable to be used by `MyClass` objects--this is different from the above tables because it will not contain the `new` method.
        luaL_newmetatable(L, LUA_MYCLASS);
        // Same as before, lock the metatable.
        lua_pushliteral(L, "metatable");
        lua_setfield(L, -2, "__metatable");
        // Add metamethods contained in the `luaL_Reg` struct `MyClass_metamethods`.
        luaL_setfuncs(L, MyClass_metamethods, 0);
    
        // Create an index--the `__index` metamethod--for the above table to use for `MyClass` objects.
        lua_newtable(L);
        // Add methods.
        luaL_setfuncs(L, MyClass_methods, 0);
        lua_setfield(L, -2, "__index");
        // This pop operation is probably unnecessary since the Lua stack should be cleaned up when this function returns.
        lua_pop(L, 1);
    
        return;
    }
    

    最后,这是我输入的 Lua 文件,test.lua

    -- `MyClass` constructor with no arguments; `something` will be set to 0.
    m1 = MyClass.new()
    print(m1:get())
    
    -- `MyClass` constructor with an argument; `something` will be set to 5.
    m2 = MyClass.new(5)
    print(m2:get())
    
    -- Use the `set` method to change the value of `something` to 6.
    m2:set(6)
    print(m2:get())
    

    这将输出以下内容:

    0
    5
    6
    

    函数lua_MyClass_register的定义比较复杂,涉及到很多表的创建。我按照我的方式编写它,因为我想创建一个包含 MyClass 构造函数和方法的全局表,其格式与 Lua 中的全局 string 表相同。以函数string.match为例:它可以作为函数调用,与string.match(str, pattern)一样;或者,它可以作为方法调用,如str:match(pattern)。我为MyClass 注册所有函数的方式允许这种行为,除了全局表MyClass 还包含一个构造函数,而MyClass 类型的对象没有。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-26
      • 2022-01-22
      • 2013-03-05
      • 2014-07-11
      • 2021-12-26
      相关资源
      最近更新 更多