I have a script to C++ callback/functor system that can call any "registered" C++ function using strings and/or variants.
//REMOVED ERROR CHECKS AND ERRONEOUS STUFF FOR THIS POST
int LuaGameObject::LuaCallFunction( lua_State *luaState )
{
if ( lua_isuserdata( luaState, 1 ) == 1 )
{
int nArgs = lua_gettop( luaState );
//Get GameObject
OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
if ( pGameObject )
{
//Get FunctionName
const char* functionNameString = lua_tostring( luaState, 2 );
//Get Args
std::vector<OVariant> args;
for ( int i = 3; i <= nArgs; ++i )
{
OVariant variant;
variant.SetFromLua( luaState, i );
args.push_back( variant );
}
//Call it!
CallGameObjectFunction( luaState, pGameObject, functionNameString, args );
return 1;
}
}
return 0;
}
OVariant LuaGameObject::ExecuteLua()
{
lua_State *lState = luaL_newstate();
luaL_openlibs( lState );
lua_register( lState, "Call", LuaCallFunction );
luaL_loadstring( lState, m_pScript );
//now run it
lua_pcall( lState, 0, 1, 0 );
//process return values
OVariant result;
result.SetFromLua( lState, -1 );
lua_close( lState );
return result;
}
In lua I can do something like this...
local king = Call("EmpireManager","GetKing")
Call("MapCamera","ZoomToActor",king)
However, I am feeling that I can use the __index metamethod to simplify the lua...
local king = EmpireManager:GetKing()
MapCamera:ZoomToActor(king)
I was hoping to achieve the simplified lua by using the following implemenation of the __index metamethod
Here is how I register the __index metafunction... (mostly copied from online examples)
void LuaGameObject::Register( lua_State * l )
{
luaL_Reg sRegs[] =
{
{ "__index", &LuaGameObject::LuaCallFunction },
{ NULL, NULL }
};
luaL_newmetatable( l, "luaL_EmpireManager" );
// Register the C functions into the metatable we just created.
luaL_setfuncs( l, sRegs, 0 );
lua_pushvalue( l, -1 );
// Set the "__index" field of the metatable to point to itself
// This pops the stack
lua_setfield( l, -1, "__index" );
// Now we use setglobal to officially expose the luaL_EmpireManager metatable
// to Lua. And we use the name "EmpireManager".
lua_setglobal( l, "EmpireManager" );
}
Unfortunately, I cant seem to get the callback setup right. Lua correctly calls my LuaGameObject::LuaCallFunction, but the stack does not contain what I would like. From within the LuaGameObject::LuaCallFunction, I can find the function name and EmpireManager object on the stack. But, I cant find the args on the stack. What is the proper way to set this up? Or is it not possible?
It is definitely possible to add methods to a userdata type in Lua, as explained in the Programming in Lua guide from the official website.
When you type the following Lua code:
myUserdata:someMethod(arg1,arg2,arg3)
Assuming myUserdata is a "userdata" object, the interpreter will do the following.
Call getmetatable(myUserdata).__index(myUserdata,"someMethod") to get the value of someMethod.
Call someMethod(myUserdata,arg1,arg2,arg3). someMethod can be anything callable from Lua. Examples: a Lua or C function, or a table/userdata with a __call metamethod.
Your __index metamethod should just return a function (or another object callable from Lua) implementing the method. Something like this:
// IMO, quite a misleading name for the __index metamethod (there is a __call metamethod)
int LuaGameObject::LuaCallFunction( lua_State *l)
{
// todo: error checking
OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
std::string memberName = lua_tostring( luaState, 2 );
int result = 1;
if (memberName == "method1") {
lua_pushcfunction(l,LuaGameObject::luaMethod1);
} else if (memberName == "method2") {
lua_pushcfunction(l,LuaGameObject::luaMethod2);
} else {
result = 0;
}
return result;
}
Basic skeleton of the functions returned by the __index metamethod:
int LuaGameObject::luaMethod1(lua_State* l) {
// todo: error checking.
OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata(l, 1));
float arg1 = lua_tonumber(l, 2);
// get other args
pGameObject->method1(arg1 /*, more args if any.*/);
// optionally push return values on the stack.
return 0; // <-- number of return values.
}
Ok so after more research, I now believe that I cannot use __index metafunction to call a c functor with arguments. It only passes the table name and the key to the callback.
However, for anyone interested, it can be used for table-like objects, but not functions (as arguments are not pushed onto the stack). I will it for my "property" objects. They have no arguments and can be used in lua as follows...
local king = EmpireManager:king
king:name = "Arthur"
local name = king:name
These properly link to and call the appropriate C++ objects.functions
Actor::SetName(std::string name)
std::string Actor::GetName()
I had the same problem to call a method from my object and have used this post to develop the solution.
I hope that the example below can be useful to you.
#include <iostream>
#include <string>
#include <map>
#include <functional>
extern "C" {
#include "lua/lua.h"
#include "lua/lauxlib.h"
#include "lua/lualib.h"
}
//template<class UserdataType> // if will be work with lua garbage collector, use a function like that to delete the this_ptr (1st param)
//int DeletePtr(lua_State *lua_state) { // It's necessary register the metatable.__gc and to trust in gc (create just pointer of LuaObjects
// UserdataType** this_ptr = reinterpret_cast<UserdataType**>(lua_touserdata(lua_state, 1));
// delete (*this_ptr);
// return 0;
//}
template<class UserdataType>
int Closure(lua_State *lua_state) {
UserdataType** ptr = reinterpret_cast<UserdataType**>(lua_touserdata(lua_state, 1)); // This closure is being called by call operator ()
return (*ptr)->CallFunction(lua_state); // To access the function name called use lua stack index with lua_upvalueindex(-1)
} // Call the object method to resolve this called there
template<class UserdataType>
int ReturnClosure(lua_State *lua_state) { // This function is called as a lookup of metatable.__index
lua_pushcclosure(lua_state, Closure<UserdataType>, 1); // then we will return a closure to be called through call operator ()
return 1; // The 1st param (the only one) is the action name of function
} // Then a closure will grant access to ReturnClosure params as upvalues (lua_upvalueindex)
class LuaObject {
public:
LuaObject() : userdata_name("userdata1") {
}
void CreateNewUserData(lua_State* lua_ptr, const std::string& global_name) {
RegisterUserData(lua_ptr);
LuaObject** this_ptr = reinterpret_cast<LuaObject**>(lua_newuserdata(lua_ptr, sizeof(LuaObject*)));
*this_ptr = this;
luaL_getmetatable(lua_ptr, userdata_name.c_str());
lua_setmetatable(lua_ptr, -2); // setmetatable(this_ptr, userdata_name)
lua_setglobal(lua_ptr, global_name.c_str()); // store to global scope
}
int CallFunction(lua_State* lua_state) const {
std::string name = lua_tostring(lua_state, lua_upvalueindex(1)); // userdata:<function>(param2, param3)
auto it = functions.find(name); // <function> lua_tostring(lua_state, lua_upvalueindex(1))
if (it != functions.end()) { // <implicit this> lua_touserdata(l, 1)
return it->second(lua_state); // <param #1> lua_touserdata(l, 2)
} // <param #2> lua_touserdata(l, 3)
return 0; // <param #n> lua_touserdata(l, n+1)
}
void NewFunction(const std::string& name, std::function<int(lua_State*)> func) {
functions[name] = func;
}
private:
void RegisterUserData(lua_State* lua_ptr) {
luaL_getmetatable(lua_ptr, userdata_name.c_str());
if (lua_type(lua_ptr, -1) == LUA_TNIL) {
/* create metatable for userdata_name */
luaL_newmetatable(lua_ptr, userdata_name.c_str());
lua_pushvalue(lua_ptr, -1); /* push metatable */
/* metatable.__gc = DeletePtr<LuaObject> */
//lua_pushcfunction(lua_ptr, DeletePtr<LuaObject>);
//lua_setfield(lua_ptr, -2, "__gc");
/* metatable.__index = ReturnClosure<LuaObject> */
lua_pushcfunction(lua_ptr, ReturnClosure<LuaObject>);
lua_setfield(lua_ptr, -2, "__index");
}
}
std::map<std::string, std::function<int(lua_State*)>> functions;
std::string userdata_name;
};
int main(int argc, char* argv[]) {
lua_State* lua_state = luaL_newstate();
luaL_openlibs(lua_state);
LuaObject luaobj;
luaobj.CreateNewUserData(lua_state, "test_obj");
luaobj.NewFunction("action", [](lua_State* l)->int {
std::string result = "action has been executed";
LuaObject** ptr = reinterpret_cast<LuaObject**>(lua_touserdata(l, 1));
result += "\n #1 param is user_data (self == this) value = " + std::to_string(reinterpret_cast<size_t>(*ptr));
for (int i = 2; i <= lua_gettop(l); ++i) {
result += "\n #" + std::to_string(i)+ " = " + lua_tostring(l, i);
}
result += "\n #n param is passed on call operator () #n = " + std::to_string(lua_gettop(l));
lua_pushfstring(l, result.c_str());
return 1;
});
std::string lua_code;
lua_code += "print(test_obj:unknown_function()) \n";
lua_code += "print(test_obj:action()) \n";
lua_code += "print(test_obj:action(1)) \n";
lua_code += "print(test_obj:action(1, 2)) \n";
lua_code += "print(test_obj:action(1, 2, 'abc'))\n";
if (!(luaL_loadbuffer(lua_state, lua_code.c_str(), lua_code.length(), NULL) == 0 && lua_pcall(lua_state, 0, LUA_MULTRET, 0) == 0)) {
std::cerr << "Lua Code Fail: " << lua_tostring(lua_state, -1) << std::endl;
}
lua_close(lua_state);
return 0;
}
Output:
action has been executed
#1 param is user_data (self == this) value = 13629232
#n param is passed on call operator () #n = 1
action has been executed
#1 param is user_data (self == this) value = 13629232
#2 = 1
#n param is passed on call operator () #n = 2
action has been executed
#1 param is user_data (self == this) value = 13629232
#2 = 1
#3 = 2
#n param is passed on call operator () #n = 3
action has been executed
#1 param is user_data (self == this) value = 13629232
#2 = 1
#3 = 2
#4 = abc
#n param is passed on call operator () #n = 4
Related
I'm trying to manually bind a vector of pointers from C++ to Lua.
I'm limited to a compiler which has partial C++11 support, so can't use one of the existing binding libraries since they all seem to use C++17 now.
For example, I have a class which contains a list of pointers to a child class. The vector of children is read only from the Lua point of view - I don't need add, remove etc. Just read.
class Child
{
public:
std::string name;
};
class Parent
{
public:
std::vector <Child *>children;
};
...
Parent parent;
Child * m = new Child;
m->name = "Mary";
parent.children.push_back(m);
Child * b = new Child;
b->name = "Bob";
parent.children.push_back(b);
...
Child binding.
static int Child_name(lua_State * lua) {
// this should get a point to a Child object and return the name
lua_pushstring(lua, "child name");
return 1;
}
static const struct luaL_Reg Child_FunctionList[] = {
{ "name", Child_name },
{ NULL, NULL }
};
static int Child_tostring(lua_State * lua) {
lua_pushstring(lua, "Child");
return 1;
}
static const struct luaL_Reg Child_MetaList[] = {
{ "__tostring", Child_tostring },
{ NULL, NULL }
};
void Child_Register(lua_State * lua)
{
luaL_newlib(lua, Child_FunctionList);
if(luaL_newmetatable(lua, "ChildMetaTable"))
luaL_setfuncs(lua, Child_MetaList, 0);
lua_setmetatable(lua, -2);
lua_pop(lua, 1);
}
Parent binding.
static int Parent_count(lua_State * lua) {
// used by both the Parent function and metatable __len
lua_pushinteger(lua, parent.children.size());
return 1;
}
static int Parent_children(lua_State * lua)
{
// stack -1=number(1)
int idx = lua_tonumber(lua, -1);
Child ** c = static_cast<Child **>(lua_newuserdata(lua, sizeof(Parent *)));
*c = parent.children[idx];
luaL_getmetatable(lua, "ChildMetaTable"); // [-0, +1, m]
lua_setmetatable(lua, -2);
// return new userdata - does not work
return 1;
}
static const struct luaL_Reg Parent_FunctionList[] = {
{ "count", Parent_count },
{ "children", Parent_children },
{ NULL, NULL }
};
static int Parent_tostring(lua_State * lua) {
lua_pushstring(lua, "Parent");
return 1;
}
static int Parent_index(lua_State * lua) {
// stack -1=number(1) -2=table
int idx = lua_tonumber(lua, -1);
Child * c = parent.children[idx];
// what to return here?
return 0;
}
static const struct luaL_Reg Parent_MetaList[] = {
{ "__tostring", Parent_tostring },
{ "__len", Parent_count },
{ "__index", Parent_index },
{ NULL, NULL }
};
void Parent_Register(lua_State * lua) {
luaL_newlib(lua, Parent_FunctionList);
if(luaL_newmetatable(lua, "ParentMetaTable"))
luaL_setfuncs(lua, Parent_MetaList, 0);
lua_setmetatable(lua, -2);
lua_setglobal(lua, "Parent");
}
The Parent binding results in a global table, which is intentional. Testing the Parent table:
>print(Parent)
Parent
>print(#Parent)
2
>print(Parent.count())
2
But trying to access the children doesn't work as well
>c = Parent[1]
>print(c)
Child
>print(type(c))
userdata
>print(c.name())
[string "main"]:8: attempt to index a ChildMetaTable value (global 'c')
I get lost in Parent_index, where I need a pointer to the C Parent object rather than the Lua table. I understand the method is to use userdata or lightuserdata but can't see how to bind the class to Lua in order to do this. Same for the Child binding, which results in a ChildMetatable but no Lua table.
Edit: I've added in a children function under Parent, but still not working. Also changed some of the indexes for lua_setmetatable from bottom of stack to top of stack (negative)
Edit2: It's because I'm trying to have Parent:children act both as a table and as userdata. So I can return userdata with the C object pointer along with the ChildMetaTable with __index to determine what to do with the child methods.
What I was trying to do was to have both a Lua table and userdata at the same time.
First the parent __index method is best replaced by function in the table that creates new userdata for the child object.
static int Parent_children(lua_State * lua)
{
int idx = luaL_checkinteger(lua, -1);
Parent * p = &parent;
luaL_argcheck(lua, (idx >= 0) && (idx < (int)p->children.size()), 1, "index out of range");
Child ** pc = static_cast<Child **>(lua_newuserdata(lua, sizeof(Child *)));
*pc = parent.children[idx];
luaL_getmetatable(lua, "ChildMetaTable"); // [-0, +1, m]
lua_setmetatable(lua, -2);
return 1;
}
static const struct luaL_Reg Parent_MetaList[] = {
{ "__tostring", Parent_tostring },
{ NULL, NULL }
};
Since the child is not a Lua table, the child functions can be called from the __index method. Child_FunctionList stays the same.
static int Child_index(lua_State * lua)
{
const char * fn_name = luaL_checkstring(lua, -1);
for(const luaL_Reg * fn = Child_FunctionList; fn->name != NULL; fn++)
{
if(strcmp(fn_name, fn->name) == 0)
{
lua_pushcfunction(lua, fn->func);
return 1;
}
}
return 0;
}
static const struct luaL_Reg Child_MetaList[] = {
{ "__tostring", Child_name },
{ "__index", Child_index },
{ NULL, NULL }
};
And the child methods get a C pointer from the userdata.
static int Child_name(lua_State * lua)
{
Child * c = *reinterpret_cast<Child **>(luaL_checkudata(lua, -1, "ChildMetaTable"));
lua_pushstring(lua, c->name.c_str());
return 1;
}
And finally registering tables values for the child doesn't make sense, so can be removed but the metatable needs to be registered.
void Child_Register(lua_State * lua)
{
if(luaL_newmetatable(lua, "ChildMetaTable"))
luaL_setfuncs(lua, Child_MetaList, 0);
lua_pop(lua, 1);
}
This may not be the optimal solution, but it is heading there.
Edit:
The global parent can be passed as an upvalue in the luaL_newlib macro is expanded, rather than using the global parent.
luaL_newlibtable(lua, Parent_FunctionList);
lua_pushlightuserdata(lua, parent);
luaL_setfuncs(lua, Parent_FunctionList, 1);
...
static int Parent_children(lua_State * lua) {
Parent * parent= (Parent *)(lua_topointer(lua, lua_upvalueindex(1)));
...
I'm trying to find a way to share global variables of a specific Lua script(test.lua in the example) between different Lua states.
Here's my simple example code:
In test.lua
num = 2
In main.cpp
#include <iostream>
#include <lua.hpp>
int main()
{
lua_State *L1 = luaL_newstate(); //script A
luaL_openlibs(L1);
lua_settop(L1, 0);
luaL_dostring(L1, "require('test') num = 5");
lua_State *L2 = luaL_newstate(); //script B
luaL_openlibs(L2);
lua_settop(L2, 0);
luaL_dostring(L2, "require('test') print(num)");
lua_close(L1);
lua_close(L2);
}
I expect to get 5 but I get 2.
Is not possible to share global variables between different lua_State* through require?
ADDED :
If it's not possible, would it be a good idea to open test.lua using luaL_loadfile and then create getter/setter methods in C++ to share variable num between script A and B?
For example like this,
Script A:
script = my.Script("test")
script:setVar("num", 5)
Script B:
script = my.Script("test")
print(script:getVar("num"))
I wonder what you think about this design as an alternative to require.
Two distinct lua_States are completely and totally independent. One cannot directly affect anything that happens in another. You can expose some C code to one that allows it to modify the other, or they could both access some external resource (a file, for example) that allows them to share data.
But outside of things like this, no, they cannot interact.
The preferred method for this is to not make them separate lua_States.
Rather than having the global value in a Lua module, you could push a pointer to a C++ value as an upvalue for a metatable to a table which contains those globals. Then you push the globals table with the same metatable to both VMs. When you now access globals.num the getglobal and setglobal metamethods are triggered (depending on whether you read or write). These will update the value on the C++ side, such that it is shared between the two VMs.
N.B.: As you can judge from the lengthy boilerplate this is not a good solution. You should avoid having multiple VMs at the same time. If you require multiple VMs for concurrency purposes, consider using a mature library like Lua Lanes rather than rolling your own (doing this right requires several thousands of lines of code).
#include <string>
#include <lua.hpp>
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int value = luaL_checkinteger(L, 3);
luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
int *num = static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
*num = value;
lua_pop(L, 1);
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int num = *static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
lua_pushinteger(L, num);
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
int num = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &num);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
luaL_dostring(L1, "print('Script A: ' .. globals.num) globals.num = 5");
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &num);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
luaL_dostring(L2, "print('Script B: ' .. globals.num)");
lua_close(L1);
lua_close(L2);
}
As a challange to myself I implemented a complete global table which can communicate values of type nil, bool, int, double, and string between two Lua states. They can be named with everything that has a string representation.
-- To be on the safe side, just use numbers and strings as keys
globals[1] = "x"
globals.num = 5
-- Be careful when using table or function literals as keys
-- Two empty tables don't have the same representation
globals[{}] = 2 -- "table: 0x10d55a0" = 2
globals[{}] = 1 -- "table: 0x10ce2c0" = 1
I haven't checked all sorts of exceptional situations exhaustively, so no refunds!
#include <iostream>
#include <string>
#include <unordered_map>
#include <boost/variant.hpp>
#include <lua.hpp>
enum class nil {};
using Variant = boost::variant<nil, bool, int, double, std::string>;
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto &globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
Variant &v = globals[key];
switch (lua_type(L, 3)) {
case LUA_TNIL:
v = nil{};
break;
case LUA_TBOOLEAN:
v = static_cast<bool>(lua_toboolean(L, 3));
lua_pop(L, 1);
break;
case LUA_TNUMBER:
if (lua_isinteger(L, 3)) {
v = static_cast<int>(luaL_checkinteger(L, 3));
} else {
v = static_cast<double>(luaL_checknumber(L, 3));
}
lua_pop(L, 1);
break;
case LUA_TSTRING:
v = std::string(lua_tostring(L, 3));
lua_pop(L, 1);
break;
default:
std::string error = "Unsupported global type: ";
error.append(lua_typename(L, lua_type(L, 3)));
lua_pushstring(L, error.c_str());
lua_error(L);
break;
}
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
auto search = globals.find(key);
if (search == globals.end()) {
lua_pushstring(L, ("unknown global: " + key).c_str());
lua_error(L);
return 0;
}
Variant const &v = search->second;
switch (v.which()) {
case 0:
lua_pushnil(L);
break;
case 1:
lua_pushboolean(L, boost::get<bool>(v));
break;
case 2:
lua_pushinteger(L, boost::get<int>(v));
break;
case 3:
lua_pushnumber(L, boost::get<double>(v));
break;
case 4:
lua_pushstring(L, boost::get<std::string>(v).c_str());
break;
default: // Can't happen
std::abort();
break;
}
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
std::unordered_map<std::string, Variant> globals;
globals["num"] = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &globals);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
if (luaL_dostring(L1, "print('Script A: ' .. globals.num)\n"
"globals.num = 5") != 0) {
std::cerr << "L1:" << lua_tostring(L1, -1) << '\n';
lua_pop(L1, 1);
}
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &globals);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
if (luaL_dostring(L2, "print('Script B: ' .. globals.num)") != 0) {
std::cerr << "L1:" << lua_tostring(L2, -1) << '\n';
lua_pop(L2, 1);
}
lua_close(L1);
lua_close(L2);
}
While Lua states are separate by default, some binding libraries expose functionality to transfer information from one to the other.
For example, in sol, there are methods to serialize fairly arbitrary Lua data, including functions, to C++ data. You can then de-serialize that data into another Lua state, to effectively copy it (code link).
But you still will have two copies, in the end. You can't modify one Lua state from another directly.
Your last point, about exposing some getter/setter, is valid. You can have some data stored in C/C++ and have two different Lua states able to access it. You still have to bind that data to each VM separately.
I am trying to generate a function call using AsmJit to which I pass an char*. This char* is in itself retrieved from another function call. I tried out this:
typedef
const char* getStr();
const char* getStrImpl() {
return "hello pie";
}
void use_str_impl(int id, const char* c_str) {
// do stuff...
}
int main() {
JitRuntime rt;
CodeHolder code;
code.init(rt.getCodeInfo());
X86Compiler c(&code);
auto jitted_func = c.addFunc(FuncSignature0<const char*>(code.getCodeInfo().getCdeclCallConv()));
auto err = c.getLastError();
auto call = c.call((uint64_t) fooFuncImpl, FuncSignature0<intptr_t>());
X86Gpd res(call->getRet().getId());
auto call2 = c.call((uint64_t) send_input, FuncSignature2<void, int, intptr_t>());
err = !call2->setArg(0, Imm(42));
err = !call2->setArg(1, res);
c.ret();
c.endFunc();
err = c.finalize();
if(err) return 0;
size_t size = code.getCodeSize();
VMemMgr vm;
void* p = vm.alloc(size);
if (!p) return 0;
code.relocate(p);
auto fun = (entrypoint*) p;
fun();
}
It turns out this does not generate any instructions for the second parameter or second call to setArg. I also tried to use .newIntPtr and using move instructions to move the result of call into place. But this generated dec and add instructions which made no sense to me and my small experience with assembly. What is the correct way of doing this type of thing?
Btw I am using the AsmJit next branch.
I have done few corrections to your sample with some comments.
Better Usage of JitRuntime:
JitRuntime rt;
size_t size = code.getCodeSize();
VMemMgr vm;
....
void* p = vm.alloc(size);
if (!p) return 0;
code.relocate(p);
auto fun = (entrypoint*) p;
You have used JitRuntime just to setup the parameters for CodeHolder, but then avoided it and allocated the memory for the function yourself. While that's a valid use case it's not what most people do. Using runtime's add() is sufficient in most cases.
Invalid use of CCFuncCall::getRet():
X86Gpd res(call->getRet().getId());
The call node at this point doesn't have any return register assigned so it would return an invalid id. If you need to create a virtual register you always have to call compiler's newSomething(). AsmJit's compiler provides API to check for that case at runtime, if you are unsure:
// Would print 0
printf("%d", (int)c.isVirtRegValid(call->getRet().getId()));
The solution is to create a new virtual register and ASSIGN it to the function's return value. Assigning return value requires an index (like assigning an argument), the reason is that some functions may return multiple values(like 64-bit value in 32-bit mode), using 0 as index is sufficient most of the time.
X86Gp reg = c.newIntPtr("reg");
call->setRet(0, reg);
You can verify getRet() functionality:
X86Gp reg = c.newIntPtr("reg");
assert(call->getRet(0).isNone());
call->setRet(0, reg);
assert(call->getRet(0) == reg);
Fully working example:
#include <stdio.h>
#include <asmjit/asmjit.h>
const char* func_a() {
printf("func_a(): Called\n");
return "hello pie";
}
void func_b(int id, const char* c_str) {
printf("func_b(%d, %s): Called\n", id, c_str);
}
int main() {
using namespace asmjit;
JitRuntime rt;
CodeHolder code;
code.init(rt.getCodeInfo());
X86Compiler c(&code);
X86Gp reg = c.newIntPtr("reg");
// Compilation step...
c.addFunc(FuncSignature0<void>(code.getCodeInfo().getCdeclCallConv()));
auto call_a = c.call((uint64_t)func_a, FuncSignature0<intptr_t>());
call_a->setRet(0, reg);
auto call_b = c.call((uint64_t)func_b, FuncSignature2<void, int, intptr_t>());
call_b->setArg(0, Imm(42));
call_b->setArg(1, reg);
c.ret();
c.endFunc();
// Finalize does the following:
// - allocates virtual registers
// - inserts prolog / epilog
// - assembles to CodeHolder
auto err = c.finalize();
if (err) {
printf("COMPILER FAILED: %s\b", DebugUtils::errorAsString(err));
return 1;
}
typedef void (*EntryPoint)(void);
EntryPoint entry;
// Adds function to the runtime. Should be freed by rt.release().
// Function is valid until the runtime is valid if not released.
err = rt.add(&entry, &code);
if (err) {
printf("RUNTIME FAILED: %s\b", DebugUtils::errorAsString(err));
return 1;
}
entry();
return 0;
}
I am trying to create a function that receives and returns a double. For the call method I used the approach with Mem. At the end I need to save the result in the variable xmm1.
I can't identify the error. The sine function is called correctly. But for the final assembler generation error occurs.
JitRuntime rt;
CodeHolder code;
code.init(rt.codeInfo());
asmjit::x86::Compiler cc(&code);
asmjit::x86::Gp reg = cc.newIntPtr("reg");
asmjit::Zone zonee(1024);
asmjit::ConstPool constPool(&zonee);
asmjit::Label constPoolLabel = cc.newLabel();
// Compilation step...
// c.addFunc(asmjit::FuncSignatureT<void>(code.codeInfo().getCdeclCallConv()));
cc.addFunc(asmjit::FuncSignatureT<void>());
auto call_a = cc.call((uint64_t)func_a, FuncSignatureT<intptr_t>());
call_a->setRet(0, reg);
auto call_b = cc.call((uint64_t)func_b, FuncSignatureT<void, int, intptr_t>());
call_b->setArg(0, Imm(42));
call_b->setArg(1, reg);
auto seno = [&](double value) {
size_t valueOffset;
double seno = static_cast<double_t>(std::sin(value));
cout << " seno " << seno << endl;
constPool.add(&seno, sizeof(double), valueOffset);
return asmjit::x86::ptr(constPoolLabel, valueOffset);
};
asmjit::x86::Mem mem;
double test = 180.5;
auto call_c = cc.call(seno(test), asmjit::FuncSignatureT<double_t>());
call_c->setArg(0, asmjit::Imm(test));
call_c->_setRet(0, mem);
cc.movsd(asmjit::x86::xmm1, mem);
cc.ret();
cc.endFunc();
// Finalize does the following:
// - allocates virtual registers
// - inserts prolog / epilog
// - assembles to CodeHolder
auto err = cc.finalize();
if (err) {
printf("COMPILER FAILED: %s\b", DebugUtils::errorAsString(err));
return;
}
typedef void (*EntryPoint)(void);
EntryPoint entry;
// Adds function to the runtime. Should be freed by rt.release().
// Function is valid until the runtime is valid if not released.
err = rt.add(&entry, &code);
if (err) {
printf("RUNTIME FAILED: %s\b", DebugUtils::errorAsString(err));
return;
}
entry();
return;
perhaps the memory object should relate to some memory address?
Mem mem = qword_ptr ((uint64_t) &test);
This SO article is the same thing, but the answer is unhelpful because the answer was in Lua and the question was about the C-API. So I'm asking again. Hopefully, others will benefit from this question.
I'm actually having 2 problems (I can't get y an z to work, and I can't get helloworld() to work)
I'm trying to get to this:
local x = MyCBoundLib.GetSomething()
print(x.y)
print(x.z)
Where x is a userdata. I keep getting attempt to index a userdata value
I know that "userdata isn't indexable without a metatable because it's C/C++ data"
In my C-code, I do something like this to try and wrap the object.
int push_Something(lua_State *L, void *object)
{
struct SomethingWrapper *w = (struct SomethingWrapper *)lua_newuserdata(L, sizeof(struct SomethingWrapper));
w->object = object;
luaL_setmetatable(L, "Something");
return 1;
}
Earlier, I tried to register a metatable called Something, like so:
luaL_newmetatable(L, "Something");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, some_funcs, 0);
lua_pop(L, 1);
Where some_funcs has:
static luaL_Reg const some_funcs [] =
{
{ "helloworld", l_helloworld },
{ NULL, NULL }
};
If I try print(x.helloworld()), I get the same error: attempt to index a userdata value
In none of my code do I know how to properly attach the value type y or z.
First, for helloworld your code works:
/* file: hw.c
* on Debian/Ubuntu compile with:
* `gcc -I/usr/include/lua5.2 -fpic -shared -o hw.so hw.c`
*/
#include <lua.h>
#include <lauxlib.h>
struct SomethingWrapper {
void *object;
};
static int l_helloworld(lua_State *L) {
lua_pushliteral(L, "Hello World!");
return 1;
}
static luaL_Reg const some_funcs[] = {
{ "helloworld", l_helloworld },
{ NULL, NULL }
};
int push_Something(lua_State *L, void *object) {
struct SomethingWrapper *w = lua_newuserdata(L, sizeof(*w));
w->object = object;
luaL_setmetatable(L, "Something");
return 1;
}
int luaopen_hw(lua_State *L) {
luaL_newmetatable(L, "Something");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, some_funcs, 0);
lua_pop(L, 1);
push_Something(L, NULL);
return 1;
}
and the test script:
-- file: hwtest.lua
local x = require( "hw" )
print( x.helloworld() )
Output is:
Hello World!
For accessing properties on a userdata you need to set __index to a function instead of a table. The function is called with two arguments (the userdata, and the key) whenever you try to access a field on the userdata, and you can query your C object and push the desired result.
It gets a little more complicated if you intend to support methods and properties at the same time, but the basic approach is the following: You use a function as __index metamethod. This function has access to a table of methods (e.g. via an upvalue or the registry, etc.) and tries to lookup the given key in that table. If you succeed you return that value. If you come up with nothing, you compare the given key to the valid property names of your C object and return the respective values if you get a match. (If you don't get a match you can return nil or raise an error -- that's up to you.)
Here is a reusable implementation of that approach (extracted from the moon toolkit):
static int moon_dispatch( lua_State* L ) {
lua_CFunction pindex;
/* try method table first */
lua_pushvalue( L, 2 ); /* duplicate key */
lua_rawget( L, lua_upvalueindex( 1 ) );
if( !lua_isnil( L, -1 ) )
return 1;
lua_pop( L, 1 );
pindex = lua_tocfunction( L, lua_upvalueindex( 2 ) );
return pindex( L );
}
MOON_API void moon_propindex( lua_State* L, luaL_Reg const methods[],
lua_CFunction pindex, int nups ) {
if( methods != NULL ) {
luaL_checkstack( L, nups+2, "not enough stack space available" );
lua_newtable( L );
for( ; methods->func; ++methods ) {
int i = 0;
for( i = 0; i < nups; ++i )
lua_pushvalue( L, -nups-1 );
lua_pushcclosure( L, methods->func, nups );
lua_setfield( L, -2, methods->name );
}
if( pindex ) {
lua_pushcfunction( L, pindex );
if( nups > 0 ) {
lua_insert( L, -nups-2 );
lua_insert( L, -nups-2 );
}
lua_pushcclosure( L, moon_dispatch, 2+nups );
} else if( nups > 0 ) {
lua_replace( L, -nups-1 );
lua_pop( L, nups-1 );
}
} else if( pindex ) {
lua_pushcclosure( L, pindex, nups );
} else {
lua_pop( L, nups );
lua_pushnil( L );
}
}
Usage:
/* [ -nup, +1, e ] */
void moon_propindex( lua_State* L,
luaL_Reg const* methods,
lua_CFunction index,
int nup );
This function is used for creating an __index metafield. If index is NULL but methods is not, a table containing all the functions in methods is created and pushed to the top of the stack. If index is not NULL, but methods is, the index function pointer is simply pushed to the top of the stack. In case both are non NULL, a new C closure is created and pushed to the stack, which first tries to lookup a key in the methods table, and if unsuccessful then calls the original index function. If both are NULL, nil is pushed to the stack. If nup is non-zero, the given number of upvalues is popped from the top of the stack and made available to all registered functions. (In case index and methods are not NULL, the index function receives two additional upvalues at indices 1 and 2.) This function is used in the implementation of moon_defobject, but maybe it is useful to you independently.
If you try to store arbitrary Lua values in a userdata (like the title suggests) -- you can't. But you can associate an extra table (called "uservalue") with each userdata and store arbitrary values there. The approach is similar to the one above, but instead of matching with predefined property names and accessing the C object directly you first push the uservalue table (using lua_getuservalue) and then lookup your field there.
In Lua Code
Test = {}
function Test:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
local a = Test:new()
a.ID = "abc123"
callCfunc(a)
In C Code
int callCfunc(lua_State * l)
{
void* obj = lua_topointer(l, 1); //I hope get lua's a variable
lua_pushlightuserdata(l, obj);
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}
But My C result is
id = null
Why? How to modify code to work fine ?
PS: I don't hope create C Test Class mapping to lua
==== update1 ====
In addition, I have added the test code to confirm correct incoming parameters.
int callCfunc(lua_State * l)
{
std::string typeName = lua_typename(l, lua_type(l, 1)); // the typeName=="table"
void* obj = lua_topointer(l, 1); //I hope get lua's a variable
lua_pushlightuserdata(l, obj);
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}
the result
typeName == "table"
so incoming parameter type is Correct
I found the reason
Correct c code should is ...
In C Code
int callCfunc(lua_State * l)
{
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, -1); //-1
...
return 0;
}
Maybe this - haven't tested sorry - don't have a compiler handy
Input is the table from lua on top of the stack, so getfield(l,1, "ID") should get the field ID from the table at the top of the stack - which in this case is your input table. It then pushes the result to the top of the stack
int callCfunc(lua_State * l)
{
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}