Как получить доступ к переменным класса, который запускает скрипт Lua из Lua - PullRequest
0 голосов
/ 06 июля 2018

Интересно, возможно ли получить доступ к переменным класса, который запускает скрипт Lua, из связанного класса C ++, который используется в скрипте Lua.

В приведенном ниже примере мне интересно, можно ли как-нибудь получить доступ к переменной name в классе myLua из связанного класса Test.

Вот мои коды.

main.cpp:

extern "C" 
{
    int luaopen_my(lua_State* L);
}

class myLua {

public:

    struct myData
    {
        std::string name;
        lua_State *L;
    };
    myLua(std::string name)
    {
        data = make_shared<myData>();
        data->name = name;
        data->L = luaL_newstate();
        lua_State *L = data->L;
        luaL_openlibs(L);
        luaopen_my(L);
        lua_settop(L, 0);

        const char *script =
        "function setup() \
           test = my.Test() \
           test:callHello() \
         end \
         function hello(name) \
           print('hello is called by : ' .. name) \
         end";
        //------------Added----------------
        lua_pushlightuserdata(L, data.get());
        myLua::myData *b = static_cast<myLua::myData *>(lua_touserdata(L, 1));
        cout << "RESULT1 : " << b->name << endl;
        //---------------------------------

        const int ret = luaL_loadstring(L, script);
        if (ret != 0 || lua_pcall(L, 0, LUA_MULTRET, 0) != 0)
        {
            std::cout << "failed to run lua script" << std::endl;
            return;
        }
        lua_getglobal(L, "setup");
        if (lua_pcall(L, 0, 0, 0))
        {
            std::cout << "failed to call setup function" << std::endl;
            return;
        }
    }
    shared_ptr<myData> data;
};

void main() 
{
    myLua lua1("Apple");
    myLua lua2("Orange");
}

bindings.h:

class Test
{
public:
    void callHello(lua_State *L) {

        //------------Added----------------
        myLua::myData *b = static_cast<myLua::myData *>(lua_touserdata(L, -1));
        cout << "RESULT2 : " << b->name << endl;
        //---------------------------------

        lua_getglobal(L, "hello");
        lua_pushstring(L, "ClassName");
        if (lua_pcall(L, 1, 0, 0))
        {
            std::cout << "failed to call hello function" << std::endl;
            return;
        }
    };
};

bindings.i: (используется для связывания bindings.h с использованием SWIG)

%module my
%{
    #include "bindings.h"
%}

%include <stl.i>
%include <std_string.i>
%include <std_vector.i>
%include <std_map.i>
%include <typemaps.i>

%typemap(default) (lua_State *L) 
{
    $1 = L;
}
typedef std::string string;

%include "bindings.h"

Текущий результат:

hello is called by : ClassName
hello is called by : ClassName

Результат, который я хочу:

hello is called by : Apple
hello is called by : Orange

Может быть, я могу как-нибудь зарегистрировать переменную в lua_State*

Я думаю, было бы замечательно, если бы было что-то вроде

lua_registerdata(L, &name);

А потом получите его, используя что-то вроде

string name = lua_getregistereddata(L);

Результат с добавленным кодом:

RESULT1 : Apple
RESULT2 : \360n\240\300`\255\276\255\336\336\300ݺ\220\300`DD\255\276\255\336\336\300ݺ\300\217\300`\340_\300`D\376
hello is called by : ClassName
RESULT1 : Orange
RESULT2 : \360n\300`\255\276\255\336\336\300ݺ\200\236\300`DD\255\276\255\336\336\300ݺ@\236\300``w\300`D\376
hello is called by : ClassName

1 Ответ

0 голосов
/ 07 июля 2018

Передать по значению

Я предлагаю вам передать name в качестве аргумента setup и callHello. Это решает проблему с временем жизни объектов.

N.B.: Вызов функции Lua из C ++, которая затем вызывает функцию C ++ из Lua, кажется очень неэффективным. Вы уверены в своем дизайне? Вам действительно нужна эта дополнительная косвенность через Lua?

bindings.h

#pragma once

#include <iostream>
#include <string>

class Test {
public:
    void callHello(std::string const &name, lua_State *L) {
        lua_getglobal(L, "hello");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call hello function\n"
                      << lua_tostring(L, -1) << '\n';
            return;
        }
    }
};

test.cpp

#include <iostream>
#include <string>

#include <lua.hpp>

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

class myLua {
public:
    myLua(std::string const &name) {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        luaopen_my(L);

        const char *script = "function setup(name)\n"
                             "    local test = my.Test()\n"
                             "    test:callHello(name)\n"
                             "end\n"
                             "function hello(name)\n"
                             "    print('hello is called by : ' .. name)"
                             "end";

        if (luaL_dostring(L, script) != 0) {
            std::cout << "failed to run lua script\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_getglobal(L, "setup");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call setup function\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_close(L);
    }
};

int main() {
    myLua lua1("Apple");
    myLua lua2("Orange");
}

Проход по lightuserdata

Как вы и просили, вы можете также вставить указатель на строку в виде lightuserdata в реестр и извлечь ее в функции callHello. Использование реестра опасно по разным причинам. Ключи могут столкнуться, и вы должны быть абсолютно уверены, что ключ не использовался в другом месте. Указатели на данные C ++ могут зависнуть, а Lua не знает и не может знать об этом и с радостью выдаст вам неверный указатель. Разыменование приводит к трудно отлаживаемой ошибке сегментации.

N.B.: Я считаю, что это плохой дизайн и его следует избегать. Отказ от безопасности памяти для удобства отсутствия передачи параметра не выглядит хорошим компромиссом.

bindings.h

#pragma once

#include <iostream>
#include <string>

class Test {
public:
    void callHello(lua_State *L) {
        // Fetch light userdata from the registry with key "name" and
        // pray that it is there
        lua_pushstring(L, "name");
        lua_gettable(L, LUA_REGISTRYINDEX);
        std::string name;
        if (lua_islightuserdata(L, -1) == 1) {
            name = *static_cast<std::string *>(lua_touserdata(L, -1));
            lua_pop(L, 1);
        } else {
            lua_pushstring(L, "userdata corrupted or absent");
            lua_error(L);
            return;
        }

        // Call hello function with fetched name
        lua_getglobal(L, "hello");
        lua_pushstring(L, name.c_str());
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cout << "failed to call hello function\n"
                      << lua_tostring(L, -1) << '\n';
            return;
        }
    }
};

test.cpp

#include <iostream>
#include <string>

#include <lua.hpp>

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

class myLua {
public:
    myLua(std::string name) {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        luaopen_my(L);

        const char *script = "function setup()\n"
                             "    local test = my.Test()\n"
                             "    test:callHello()\n"
                             "end\n"
                             "function hello(name)\n"
                             "    print('hello is called by : ' .. name)"
                             "end";

        if (luaL_dostring(L, script) != 0) {
            std::cout << "failed to run lua script\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        // Push light userdata into the registry with key "name"
        lua_pushstring(L, "name");
        lua_pushlightuserdata(L, static_cast<void *>(&name));
        lua_settable(L, LUA_REGISTRYINDEX);

        lua_getglobal(L, "setup");
        if (lua_pcall(L, 0, 0, 0) != 0) {
            std::cout << "failed to call setup function\n"
                      << lua_tostring(L, -1) << '\n';
            lua_close(L);
            return;
        }

        lua_close(L);
    }
};

int main() {
    myLua lua1("Apple");
    myLua lua2("Orange");
}

Общие биты

Файл интерфейса SWIG не нуждается в адаптации и остается тем же в любом случае.

my.i

%module my
%{
    #include "bindings.h"
%}

%include <std_string.i>
%include <typemaps.i>

%typemap(default) (lua_State *L) 
{
    $1 = L;
}

%include "bindings.h"

Компиляция и запуск могут быть выполнены для обоих случаев с помощью (например)

$ swig -lua -c++ my.i
$ clang++ -Wall -Wextra -Wpedantic -I/usr/include/lua5.2/ my_wrap.cxx test.cpp -llua5.2
$ ./a.out 
hello is called by : Apple
hello is called by : Orange
...