简体   繁体   English

如何将用 swig 包裹的 c++ class 的实例推送到 lua 堆栈上?

[英]How do I push An instance of a c++ class wrapped with swig onto a lua stack?

I have a class that is wrapped with swig, and registered with lua.我有一个 class 用 swig 包装,并在 lua 注册。 I can create an instance of this class in a lua script, and it all works fine.我可以在 lua 脚本中创建这个 class 的实例,并且一切正常。

But say I have an instance of a class made in my c++ code with a call to new X, and I have la lua_state L with a function in it that I want to call, which accepts one argument, an instance of X... How do I call that function.但是假设我有一个 class 的实例,该实例是在我的 c++ 代码中创建的,并调用了新 X,并且我有一个带有 function 的 la lua_state L 实例,它接受一个我想要调用的 X...我怎么称呼它为 function。 Here is (some) of the code in question (I've omitted the error handling stuff):这是(一些)有问题的代码(我省略了错误处理的东西):

main.cpp主文件

class GuiInst;
extern "C"
{
    int luaopen_engine (lua_State *L);
}

int main()
{
    GuiInst gui=new GuiInst;
    lua_State *L=luaL_newstate();
    luaopen_engine(L); //this is swigs module
    int error=luaL_loadfile(L,"mainmenu.lua")||
    lua_pcall(L, 0, 0, 0);
    lua_getglobal(L,"Init");
    //Somehow push gui onto lua stack...
    lua_pcall(L, 1, 0, 0));
    lua_close(L);
}

mainmenu.lua主菜单.lua

function Init(gui)
    vregion=gui:CreateComponent("GuiRegionVertical");
end

At the moment all I have found that can work is to expose some functionality from the swig generated cpp file, and call that.目前,我发现可以工作的只是从 swig 生成的 cpp 文件中公开一些功能,然后调用它。 This is bad for a few reasons... It won't work if I have multiple modulles and I had to change the default linkage specification in the swig file (using -DSWIGRUNTIME=).由于几个原因,这很糟糕......如果我有多个模块并且我不得不更改 swig 文件中的默认链接规范(使用 -DSWIGRUNTIME=),它将无法工作。

I add the following to main.cpp我将以下内容添加到 main.cpp

extern "C"
{
    struct swig_module_info;
    struct swig_type_info;
    int luaopen_engine (lua_State *L);
    swig_module_info *SWIG_Lua_GetModule(lua_State* L);
    void SWIG_Lua_NewPointerObj(lua_State* L,void* ptr,swig_type_info *type, int own);
    swig_type_info *SWIG_TypeQueryModule(swig_module_info *start,swig_module_info *end,const char *name);
}
//and then to push the value...
SWIG_Lua_NewPointerObj(L,gui,SWIG_TypeQueryModule(SWIG_Lua_GetModule(L),SWIG_Lua_GetModule(L),"GuiInst *"),0);

That gets a pointer to the module, then a pointer to the type, then calls swigs function to register it.它得到一个指向模块的指针,然后是一个指向类型的指针,然后调用 swigs function 来注册它。 It was an unreasonable thing to have to dig into a file that's not supposed to be human readable (so it says at the top of the file) and is just MESSY!不得不挖掘一个不应该是人类可读的文件(所以它在文件的顶部说)而且只是混乱,这是一件不合理的事情! (but it does work!) (但它确实有效!)

Surely theres a better way to accomplish what I'm trying to do.当然,有更好的方法来完成我正在尝试做的事情。

PS from a high level pov what I want is to have lua not refcount the Gui components which are created by the Object Factory in GuiInst, in case I'm going about this wrong. PS 从高级 pov 我想要的是让 lua 不引用由 GuiInst 的 Object 工厂创建的 Gui 组件,以防我做错了。 This is my first time exposing functionality to a scripting language apart from some very simple (and non-swig) python modules, so I'm prepared to take advice.这是我第一次向脚本语言公开功能,除了一些非常简单(和非 swig)的 python 模块,所以我准备接受建议。

Thanks for any advice!感谢您的任何建议!


Response to comment by RBerteig RBerteig 对评论的回应

GuiInst's contructor is #defined to private when swig runs to prevent lua constructing instances of it, so that won't work for me.当 swig 运行以防止 lua 构造它的实例时,GuiInst 的构造函数被#defined 私有,所以这对我不起作用。 What I was trying to prevent was the following (in lua):我试图阻止的是以下(在lua中):

r=engine.GuiRegionVertical()
r:Add(engine.GuiButton())

which would call "g=new GuiButton" then register it with the GuiRegionVertical (which needs to store a pointer for various reasons), then call "delete g", and the GuiRegionVertical is left with a dangling pointer to g.它将调用“g=new GuiButton”,然后将其注册到 GuiRegionVertical(由于各种原因需要存储一个指针),然后调用“delete g”,并且 GuiRegionVertical 留下了一个指向 g 的悬空指针。

I suspect what really needs to happen is that GuiRegionVertical::Add(GuiButton*) should increment the ref count of the GuiButton*, and then GuiRegionVertical's destructor should decrement the refcounts of all of its contents, though i'm not sure how this should be done with swig.我怀疑真正需要发生的是 GuiRegionVertical::Add(GuiButton*) 应该增加 GuiButton* 的引用计数,然后 GuiRegionVertical 的析构函数应该减少其所有内容的引用计数,尽管我不确定这应该如何痛饮就完事了。

That would remove the need for the private constructors, the Gui Object Factory and the nasty externs.这将消除对私人构造函数、 Gui Object 工厂和讨厌的外部构造器的需要。

Am I going about this Wrong?我要解决这个错误吗?

Thanks.谢谢。

Better late then never, and this solution will help other people.迟到总比不好,这个解决方案将帮助其他人。

void handle_web_request(WebRequest *request, WebResponse *response)
{
  lua_getfield(rackam->lua_state, LUA_GLOBALSINDEX, "handle_web_request");
  SWIG_Lua_NewPointerObj(rackam->lua_state, request, SWIGTYPE_p_WebRequest, 0);
  SWIG_Lua_NewPointerObj(rackam->lua_state, response, SWIGTYPE_p_WebResponse, 0);
  lua_call(rackam->lua_state, 2, 0);
}

this code must be inside %{}% blocks in your.i file, because SWIGTYPE_p_WebRequest is此代码必须在 your.i 文件中的 %{}% 块内,因为 SWIGTYPE_p_WebRequest 是

#define SWIGTYPE_p_WebResponse swig_types[6]

and swig_types[6] is和 swig_types[6] 是

static swig_type_info *swig_types[12];

which means that swig_types is only accessable from the C++ file from which it is defined.这意味着 swig_types 只能从定义它的 C++ 文件中访问。

this particular snippet is sending in two of my wrappered pointers, so calling handle_web_request(request, response) from the C++ side of things will run the global lua function "handle_web_request" and pass it my two pointers, with the SWIG magic applied. this particular snippet is sending in two of my wrappered pointers, so calling handle_web_request(request, response) from the C++ side of things will run the global lua function "handle_web_request" and pass it my two pointers, with the SWIG magic applied.

There is a simple and direct answer, that may not be the most efficient answer.有一个简单直接的答案,这可能不是最有效的答案。 SWIG produces wrappers for manipulating objects from the scripting language side. SWIG 生成用于从脚本语言端操作对象的包装器。 For objects, it also synthesizes a wrapped constructor.对于对象,它还合成一个包装的构造函数。 So, the direct solution is to just let the Lua interpreter call SWIG's constructor to create the new object.因此,直接的解决方案是让 Lua 解释器调用 SWIG 的构造函数来创建新的 object。

For the wrapped engine.GuiInst class, you almost certainly can do something like:对于封装的engine.GuiInst class,您几乎可以肯定可以执行以下操作:

int main()
{
    lua_State *L=lua_open();
    luaopen_engine(L); //this is swigs module
    int error=luaL_loadfile(L,"mainmenu.lua")||
    lua_pcall(L, 0, 0, 0);

    luaL_dostring(L, "Init(engine.new_GuiInst())");

    lua_close(L);
}

For a one-shot case like script startup, the penalty of running a string constant through luaL_dostring() is not bad at all.对于像脚本启动这样的一次性情况,通过 luaL_dostring() 运行字符串常量的惩罚一点也不差。 I'd look harder to avoid it in an event callback or an inner loop, however.但是,在事件回调或内部循环中,我会更加努力地避免它。

It does seem like there ought to be a way to convert a pointer directly into a wrapped object, I'm not spotting it in my own handful of SWIG generated wrappers.似乎应该有一种方法可以将指针直接转换为包装的 object,我没有在我自己的少数 SWIG 生成的包装器中发现它。

Edit: Of course, the Lua fragment can be decomposed to API calls that get the engine table global on the stack, extract from it the new_GuiInst member, call it, then call the global Init , but the little bit of efficiency comes at the cost of some clarity.编辑:当然,Lua 片段可以分解为 API 调用,这些调用在堆栈上获取全局引擎表,从中提取new_GuiInst成员,调用它,然后调用全局Init ,但是一点点效率是以牺牲为代价的有一定的清晰度。

As for dealing with objects that shouldn't be constructed by accident in user code, as the clarified question indicates, my first impulse would be to let SWIG generate the constructor function, keep a private reference if needed later, and remove it from the table.至于处理不应在用户代码中意外构造的对象,正如已澄清的问题所表明的那样,我的第一个冲动是让 SWIG 生成构造函数 function,如果以后需要,保留一个私有引用,并将其从表中删除. Even a C module is (usually) just a table whose members contain function values.即使是 C 模块(通常)也只是一个表,其成员包含 function 值。 Being implemented in C doesn't make them read-only unless extra effort is taken.除非付出额外的努力,否则在 C 中实施不会使它们成为只读的。

So, you could always retrieve the value of engine.new_GuiInst and park it in the registry (see luaL_ref() and the discussion in section 3.5 of the Lua Reference Manual of the pseudo-index LUA_REGISTRYINDEX for the details) for later use.因此,您始终可以检索engine.new_GuiInst的值并将其存放在注册表中(有关详细信息,请参阅luaL_ref()以及伪索引LUA_REGISTRYINDEX参考手册的 Lua 参考手册第 3.5 节中的讨论)以供以后使用。 Then, before letting any user code run, simply do the equivalent of engine.new_GuiInst = nil .然后,在让任何用户代码运行之前,只需执行相当于engine.new_GuiInst = nil的操作。 I should note that for the C data types I've been playing with most recently, SWIG created two constructors for each type, named new_TYPE and TYPE .我应该注意,对于我最近使用的 C 数据类型,SWIG 为每种类型创建了两个构造函数,名为new_TYPETYPE Both were visible in the module's table, and you would want to set both names to nil .两者都在模块的表中可见,您希望将两个名称都设置为nil If have much less experience with SWIG wrapping C++ classes, and the result may differ...如果 SWIG 包装 C++ 类的经验少得多,结果可能会有所不同......

You might want to check and review the whole content of the engine table returned by SWIG, and create a proxy object that contains only the methods you want available to your users.您可能想要检查和查看 SWIG 返回的engine表的全部内容,并创建一个代理 object,其中仅包含您希望用户可用的方法。 You can also change the environment seen by the user script so that it only has the proxy available, and names the proxy engine as well.您还可以更改用户脚本所看到的环境,使其仅具有可用的代理,并命名代理engine There has been a fair amount of discussion of sandboxing user scripts on the Lua list and at the lua-users wiki .Lua 列表lua-users wiki上对沙盒用户脚本进行了大量讨论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM