简体   繁体   中英

How to inherit from C++ class in lua using SWIG

For example, there have a class written in C++:

//Say.h
#pragma once

#include <iostream>

class Say
{
public:
    Say() {}
    virtual ~Say() {}
    virtual void SaySomething() { std::cout << "It should not be show..\n"; };
};

inline void CallCppFun(Say& intf) {
    intf.SaySomething();
}

and I write the Say.i:

//Say.i
%module Test

%{
#include "Say.h"
%}

%include "Say.h"

%inline %{
inline void CallCppFun(Say& intf);
%}

and main.cpp:

//main.cpp
#include <iostream>

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

/* the SWIG wrappered library */
extern "C" int luaopen_Test(lua_State*L);

using namespace std;

int main()
{
    lua_State *L;
    L = luaL_newstate();
    luaL_openlibs(L);
    printf("[C] now loading the SWIG wrapped library\n");
    luaopen_Test(L);
    if (luaL_loadfile(L, "Test.lua") || lua_pcall(L, 0, 0, 0)) {
        printf("[C] ERROR: cannot run lua file: %s", lua_tostring(L, -1));
        exit(3);
    }

    return 0;
}

then run the command:

swig -c++ -lua say.i

I compile the auto-genearated file example_wrap.cxx and other cpp file and link successfully.

What I want to do in Test.lua is to inherit from C++ Say class in lua:

-- Test.lua
Test.Say.SaySomething = function(self)
    print("Inherit from C++ in Lua")
end

my = Test.Say()

my:SaySomething() -- doesn't appear to inherit successfully in lua call

Test.CallCppFun(my) -- doesn't appear to inherit successfully in c++ call

The result of print was not appear to inherit successfully both in lua call and c++ call:

[C] now loading the SWIG wrapped library
It should not be show..
It should not be show..

I know it is support in inherit from C++ in Java: generating-java-interface-with-swig

I know there have a similar question in here, but doesn't give answer of the concrete problem I face to: implementing-and-inheriting-from-c-classes-in-lua-using-swig

Does Lua support inherit from C++ class in lua using SWIG or even just use pure lua? Please show some code example. If SWIG can't do this job, does it have some third-party-library support to do it easily?

It seems that SWIG with Lua doesn't actually support directors , which is needed for cross-language polymorphism to work. That's not the end of the world though, it looks like your SaySomething method is the only virtual method in the Say class, so you can substitute a single callback function instead. (And if it were me I'd use std::function in the C++ interface design which simplifies a bit of the rest of this work).

To show how this might work (and learn me some Lua in the process!) I put together a demo, which is slightly simplified over your test case in the question.

In essence what I've ended up doing is modifying the 'in' typemap for a Callback class to be using luaL_ref to retain a 'reference' to an anonymous function, iff the input it is given isn't of the callback type to begin with. My typemap therefore checks what type it has been given and constructs an instance of a temporary, local type that inherits from the Callback type, holding and uses a reference to some Lua defined code until we call it with lua_pcall . Since we only hold that instance for the duration of the call we clean it up inside an argfree typemap later. (I got the idea for this from a Lua mailing list post )

So my interface that demonstrates this all in one place looks like this:

%module test

%{
#include <iostream>
%}

%typemap(in) const Callback& (Callback *tmp=NULL) %{
  if(lua_isuserdata(L,$argnum)) {
    if (!SWIG_IsOK(SWIG_ConvertPtr(L,$input,(void**)&$1,$descriptor,$disown))){
      SWIG_fail_ptr("$symname",$argnum,$descriptor);
    }
  }
  else if (lua_isfunction(L,$argnum)) {
    struct Lua$basetype : $basetype {
      Lua$basetype(lua_State* L, int idx) : L(L) {
        lua_pushvalue(L, idx); // This gets popped by luaL_ref
        // retain our argument
        ref = luaL_ref(L, LUA_REGISTRYINDEX);
      }
      virtual ~Lua$basetype() {
        // release our reference
        luaL_unref(L, LUA_REGISTRYINDEX, ref);
      }
      virtual void run(const std::string& str) const {
         // push our 'reference' onto the stack
         lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
         // manually prepare the function arguments
         lua_pushstring(L, str.c_str());
         // make the actual calll
         lua_pcall(L, 1, 0, 0);
         // TODO: catch error and throw as C++ exception?
      }
    private:
      lua_State* L; // Uh, is keeping this around bad?
      int ref;
    };
    tmp = new Lua$basetype(L,$input);
    $1 = tmp;
  }
  else {
    SWIG_fail_arg("$symname",$argnum,"$1_type");
  }
%}

%typemap(freearg) const Callback& %{
  delete tmp$argnum; // Fine, even if NULL remember
%}

%inline %{
  struct Callback {
    virtual ~Callback() {}
    virtual void run(const std::string& str) const { std::cout << "Got string: " << str << "\n"; }
  };

  void call_me(const Callback& cb) {
    std::cout << "Hello World:\n";
    cb.run("DO IT NOW");
  }
%}

This is enough that I can use it with the following Lua code:

require("test")
cb=test.Callback()
test.call_me(cb)
cb=function(s) print("Lua got string: "..s) end
test.call_me(cb)

Which I can compile and run with:

swig3.0 -lua -c++ test.i
g++ -Wall -Wextra test_wrap.cxx -shared -o test.so -I /usr/include/lua5.1/
lua run.lua 
Hello World:
Got string: DO IT NOW
Hello World:
Lua got string: DO IT NOW

Note that this example has pretty much doubled the total amount of Lua I've written ever, so you really should check what I've done is sensible before using in production. Probably it'd be possible to add proper director support into SWIG using something like this, or emulate it better (ie more than one virtual function in a type) if you're more familiar with Lua.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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