使用SWIG在C和Lua模块之间共享数据指针

我需要向主程序提供数据结构指针,其中我定义了Lua状态,以将由使用SWIG包装的C++代码创建的动态加载Lua模块。

这是我的代码示例:

在SimpleStruct.h中:

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

在exmaple.h(这个是使用SWIG编译成Lua库)中:

#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;
    }

};

仅在example.cpp中:

 #include "example.h"

这是我的主程序(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(从SWIG的Lua示例中复制):

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

%{
#include "example.h"
%}

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

我编译所有内容如下:

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

在Ubuntu 16.04上(在OSX上,路径不同且结果相同)。

在Lua脚本的最后一行,我得到了分段错误(当我尝试在"mc:ReadSimpleStruct(myptr)"中访问pp->a时)。

所以我的问题是:如何使用Lua轻型用户数据向加载的Lua库提供C++对象指针?

一般来说:我在我的主程序中有一个具有游戏参数和对象的类,并且我想将该类的指针提供给使用SWIG编译的其他加载的Lua库。

原文链接 https://stackoverflow.com/questions/40683578

点赞
stackoverflow用户168175
stackoverflow用户168175

使用调试器(或在 TestClass::ReadSimpleStruct 中打印一些额外信息)可以很快地看到段错误的表面原因。在我的测试环境中,tmp 参数的值为 0x20。显然这不对,但要理解为什么错了以及如何修复需要更深入的调查。

作为起点,我添加了一个调用 luaL_dostring(L, "print(myptr)"),并使用调试器检查全局变量是否按预期工作。为了保险起见,我在每次调用 luaL_dostring 后添加了一些断言语句,因为实际上你只检查了最后一个的返回值,但这在这里并没有真正产生影响。

由于我一生中并没有写过太多 Lua,我查看了 ‘Light userdata’ 的文档,我看到你在使用它,但不知道它是什么。它听起来很理想:

轻量用户数据是表示 C 指针(即 void* 值)的值。

但问题是,如果我们检查生成的 example_wrap.cxx 文件,我们会发现 SWIG 实际上试图比这更聪明一些,如果我们在通过 (arg1)->ReadSimpleStruct(arg2) 生成的调用之前跟踪 arg2 的代码,我们会发现它正在调用 SWIG_ConvertPtr(最终调用 SWIG_Lua_ConvertPtr),然后执行以下操作:

  lua_touserdata(L, index);
  //...宏定义的一些类型相关处理
  *ptr=usr->ptr; // BOOM!

也就是说,你正在做的不是 SWIG 希望看到的 void*,SWIG 希望通过其类型系统管理它们作为其他函数的返回值或 SWIG 管理的全局变量。(我稍微惊讶 SWIG 让这种情况导致了段错误却没有引发错误,但我认为这是因为 void* 在某种程度上被特别处理了)

这个旧问题提供了一个很好的例子,以确认我的理解 lua_pushlightuserdata。基本上,我们需要编写自己的类型映射,以便让此函数参数以你试图使用的方式进行处理(如果你确实不想让 SWIG 管理它)。我们要做的事情非常简单。这里的用例也与我链接的示例非常相似,只不过我们调用 lua_touserdata 时所需的变量是一个函数参数。这意味着它在堆栈中的一个正偏移,而不是负偏移。事实上,SWIG 可以用 $input 替换告诉我们在类型映射中的偏移量,因此我们的类型映射不仅适用于成员函数的第一个参数。

因此,我们在修改后的 example.i 文件内的任何函数参数 void* tmp 中执行此操作的类型映射如下:

%module example

%{
#include "example.h"
%}

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

%include "example.h"

然后我们可以使用以下命令进行编译和运行:

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
2016-11-22 21:54:58