Reputation: 3044
I wonder if it's possible to access the variables of the class that runs the Lua script from the bound C++ class that is being used in Lua script.
From the example below, I wonder if it's possible to access name
variable in myLua
class from the bound Test
class somehow.
Here are my codes.
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 : (Used to bind bindings.h
using 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"
Current result:
hello is called by : ClassName
hello is called by : ClassName
Result I want :
hello is called by : Apple
hello is called by : Orange
Maybe I can register the variable to lua_State*
somehow?
I think it would be great if there's something like
lua_registerdata(L, &name);
And then later get it using something like
string name = lua_getregistereddata(L);
Result with the added code:
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
Upvotes: 1
Views: 1612
Reputation: 10939
I suggest that you pass the name
as an argument to setup
and callHello
. That solves the problem with lifetime of objects.
N.B.: Calling a Lua function from C++ which then calls a C++ function from Lua seems very inefficient. Are you sure about your design? Do you really need this extra indirection through 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");
}
As you have requested, you can also push a pointer to the string as lightuserdata into the registry and fetch it in the callHello
function. Using the registry is dangerous for various reason. Keys might collide and you have to absolutely sure that the key hasn't been used elsewhere. The pointers to the C++ data might go dangling and Lua does not and cannot know about that and will happily hand you an invalid pointer. Dereferencing leads to a hard-to-debug segmentation fault.
N.B.: I believe that this is bad design and should be avoided. Giving up memory safety for the convenience of not having to pass a parameter doesn't sound like a good trade-off.
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");
}
The SWIG interface file doesn't need to be adapted and stays the same for either case.
my.i
%module my
%{
#include "bindings.h"
%}
%include <std_string.i>
%include <typemaps.i>
%typemap(default) (lua_State *L)
{
$1 = L;
}
%include "bindings.h"
Compile and run can be done for both cases with (for example)
$ 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
Upvotes: 2