2

I need to provide data structure pointer in my main program, where I have Lua state defined, to the dynamically loaded Lua module created by wrapping a c++ code using SWIG.

This is my code example:

in SimpleStruct.h:

#pragma once
struct SimpleStruct
{
    int a;
    double b;
};

in exmaple.h (this one is compiled with SWIG) to Lua library:

#pragma once

#include "SimpleStruct.h"
#include <iostream>

class TestClass
{

public:
    TestClass()
    {
      std::cout<<"TestClass created"<<std::endl;
    }

    ~TestClass() {}


    void ReadSimpleStruct(void * tmp)
    {

      std::cout<<"reading pointer: "<<std::endl;

      SimpleStruct * pp = reinterpret_cast< SimpleStruct * >(tmp);

      std::cout<<"Simple Struct: " << pp->a << " " << pp->b << std::endl;
    }

};

in example.cpp only:

 #include "example.h"

and this is my main program (LuaTest.cpp):

extern "C" 
{ 
    #include <lua.h> 
    #include <lauxlib.h> 
    #include <lualib.h> 
} 


#include <iostream>
#include "SimpleStruct.h"



int main(int argc, char ** argv)
{

lua_State * L = luaL_newstate();
luaL_openlibs(L);

SimpleStruct * ss = new SimpleStruct();
ss->a = 1;
ss->b = 2;

lua_pushlightuserdata(L,ss);
lua_setglobal( L, "myptr");


int s = luaL_dostring(L, "require('example')");
s = luaL_dostring(L, "mc = example.TestClass()");
s = luaL_dostring(L, "mc:ReadSimpleStruct(myptr)");

if(s)
{
    printf("Error: %s \n", lua_tostring(L, -1));
    lua_pop(L, 1);
}

lua_close(L);

std::cout<<"done"<<std::endl;

return 0;
}

example.i (copied from Lua examples in SWIG):

/* File : example.i */
%module example

%{
#include "example.h"
%}

/* Let's just grab the original header file here */
%include "example.h"

and I compile everything as follows:

swig -c++ -lua example.i
g++ -c -fpic example.cpp example_wrap.cxx -I/usr/local/include -I/usr/include/lua5.2/
g++ -shared example.o example_wrap.o -o example.so
g++ LuaTest.cpp -o luatest -llua5.2 -I/usr/include/lua5.2/ -Wall

on Ubuntu 16.04 (and on osx, with different paths and the same result).

In the last line of Lua script I've got segmentation fault (when I try to access pp->a in "mc:ReadSimpleStruct(myptr)").

So my question is: how can I provide a pointer to c++ object to the loaded Lua library using Lua light userdata?

In general: I have in my main program a class with game parameters and objects, and I would like to provide a pointer to that class to other loaded Lua libraries compiled with a SWIG.

beastian
  • 21
  • 4
  • Can you make this a real, complete minimal example that show cases it? Something I can understand - we need to know how the "other loaded Lua library" works. – Flexo Nov 19 '16 at 21:46
  • I think, this is a full real example: I compile TestClass with SWIG as a lua library called 'example' (and this is 'my other lua library'), next in main program I load this library (by 'require example'). Next I am creating an instance of SimpleClass structure and creating light user data with a pointer to this instance. And finally, I want to be able to read data from that pointer by a method of TestClass object ( 'mc:ReadSimpleStruct(myptr) ). Nothing more, this will enable me having access to global parameters from my own, with SWIG created, Lua library. – beastian Nov 21 '16 at 10:14
  • The code in your question has lots of `....`. It doesn't even say what platform you're targeting. There's a file you've called SimpleStruct.h, yet you include TestClass.h. There's no .i file. I can't even start to answer your question until I write all the missing bits you've not included. I don't know what version of Lua you're targeting. – Flexo Nov 21 '16 at 18:29
  • Ok, I have edited my question and provided the whole code. I do apologise for not giving full information at the first version, but I thought, that the problem is somewhere in my declaration of TestClass. – beastian Nov 22 '16 at 11:10
  • This looks much better - I'll try and play with this in a debugger and see if I can figure out a solution. (And for anyone else who might want to answer they'll find it easier too) – Flexo Nov 22 '16 at 17:35

1 Answers1

0

With use of a debugger (or just printing a little extra inside TestClass::ReadSimpleStruct) we can see at least the superficial cause of the segfault quite quickly. The value of the tmp argument to your function is 0x20 on my test setup. That's clearly not right, but understanding why and how to fix it takes a little more investigation.

As a starting point I added one more call to luaL_dostring(L, "print(myptr)") and used a debugger to check that the global variable was indead working as intended. For good measure I added some assert statements after each call to luaL_dostring, because you're actually only checking the return value of the last one, although here that didn't really make any difference.

Having not exactly written much Lua in my life I looked a the documentation for 'Light userdata', which I saw you were using but didn't know what it was. It sounds ideal:

A light userdatum is a value that represents a C pointer (that is, a void * value)

The problem is though that if we inspect the generated example_wrap.cxx file we can see that SWIG is actually trying to be more clever than that and, if we trace the code for arg2 before the generated call to (arg1)->ReadSimpleStruct(arg2) we can see that it's calling SWIG_ConvertPtr (which eventually calls SWIG_Lua_ConvertPtr), which then does:

  lua_touserdata(L, index);
  //... Some typing stuff from the macro
  *ptr=usr->ptr; // BOOM!

I.e. what you're doing is not what SWIG expects to see for void *, SWIG is expecting to manage them all through its typing system as return values from other functions or SWIG managed globals. (I'm slightly surprised that SWIG let this get as far as a segfault without raising an error, but I think it's because void* is being special cased somewhat)

This old question served as quite a nice example to confirm my understanding of lua_pushlightuserdata. Basically we will need to write our own typemap to make this function argument get handled the way you're trying to use it (if you really do want to not let SWIG manage this?). What we want to do is very simple though. The usage case here is also substantially similar to the example I linked, except that the variable we're after when we call lua_touserdata is a function argument. That means it's at a positive offset into the stack, not a negative one. SWIG in fact can tell us what the offset inside our typemape with the $input substitution, so our typemap doesn't only work for the 1st argument to a member function.

So our typemap, which does this for any function argument void * tmp inside our modified example.i file becomes:

%module example

%{
#include "example.h"
%}

%typemap(in) void * tmp %{
    $1 = lua_touserdata(L, $input);
%}

%include "example.h"

And that then compiles and runs with:

swig -c++ -lua example.i
g++ -fPIC example_wrap.cxx -I/usr/local/include -I/usr/include/lua5.2/ -shared -o example.so && g++ -Wall -Wextra LuaTest.cpp -o luatest -llua5.2 -I/usr/include/lua5.2/ 
./luatest 
TestClass created
userdata: 0x11d0730
reading pointer: 0x11d0730
Simple Struct: 1 2
done
Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272