3

I am building a Swig interface to a templated class. In my pyinterface.i file, I declare

%template (myclass) MyClass<int>;

Now what I want to do, is add a field variable to my new class, which I assume should be done as

%extend MyClass<int>{
    double x;
}

However, this complains that there is no

myclass_get_x

method defined. So if I try to define this by modifying the above to:

%extend MyClass<int>{
    double x;
    double MyClass<int>_get_x(MyClass<int> *f){
            return (*f)->x;
    }
}

Then I get syntax errors.

I tried doing this as:

%extend myclass{
    double x;
    double myclass_get_x(myclass *f){
            return (*f)->x;
    }
}

But this also throws errors, as myclass doesn't seem to be understood yet.

GeorgeWilson
  • 562
  • 6
  • 17
  • Unrelated to my answer, but is `myclass *f` meant to be like a `this` pointer here? You don't need to explicitly write that argument, but you do need to use `$self` instead of `this` with `%extend`. – Flexo Jul 05 '12 at 08:25
  • Yes that is essentially how I was trying to use it. I figured this format was an alternative to using '$self' instead of 'this'. Perhaps I can't do this. – GeorgeWilson Jul 05 '12 at 22:35

1 Answers1

1

When you extend a class with %extend you can't really add any new member variables, only member functions. It will assume the existence of a get/set function if you do this in %extend.

The reason for this is that the storage (i.e. memory) for the new member variables would have to live somewhere, but there's no obvious safe place to put it. You can't modify the original C++ class, because if you do you'll end up with undefined behaviour when some other, pre-compiled code uses the old definition of the class; there's no way to retrospectively apply the new definition. You can't put it in the proxy that gets generated in the target language, because there isn't a 1:1 mapping between instances of C++ classes and proxies in the target language. (Consider two functions that both return the same global instance via a pointer for the trivial example of how this may occur).

If you wanted to implement the get/set functions to do something useful (e.g. here I used a global map to store the extra data) you don't want to implement them inside %extend For example, given test.hh with just:

template <typename T>
struct Foo {};

You can use a std::map to do what you're trying to do by writing your own gets and sets as free functions in the wrapper - %{ %} just passes the code straight through:

%module test

%include "test.hh"

%{
#include "test.hh"
%}

%{
#include <map>
static std::map<Foo<int>*, double> extra_stuff;

const double& Foo_Sl_int_Sg__x_get(Foo<int>* f) {
  return extra_stuff[f];
}

void Foo_Sl_int_Sg__x_set(Foo<int>* f, const double& d) {
  extra_stuff[f] = d;
}
%}

%template(FooInt) Foo<int>;

%extend Foo<int> {
  double x;
}

The get/set functions are mangled by SWIG's own mangling system because it's a template. I just looked in the generated wrapper class to see what they were called. (I think there might be a smarter way to do that but I couldn't figure it out from the documentation yet). If it wasn't a template the name of the functions would be much simpler.

Note that there's no provision here for ever removing entries from this map - it will just grow indefinitely. (You can work around that for example by providing a typemap that calls an extra function at the end of an Object's life). You also need to be careful about threads.

Since you didn't specify what language you were using I tested the above code with Java:

public class run {
  public static void main(String[] argv) {
    System.loadLibrary("test");
    FooInt f = new FooInt();
    f.setX(0.1);
    System.out.println(f.getX());
  }
}

Alternatively if you really want to you can write code to ensure that there is a unique single proxy instance in the the target language, by writing a typemap that checks if an proxy has been created instead of always creating one, but that raises issues with reference counting and thread safety that are hard to address efficiently and generically, which is why it's not the default behaviour.

The simplest workaround in a lot of cases (e.g. iterators in the target language) though is to use %inline to declare and define and wrap all at once an entirely new extra class that that provides the functionality you want.

Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Thanks Flexo, excellent answer! Learnt a lot from what I assumed would be a "you forgot the comma" type question. – GeorgeWilson Jul 05 '12 at 22:28
  • I am confused about one thing though: **You can't put it in the proxy that gets generated in the target language, because there isn't a 1:1 mapping between instances of C++ classes and proxies in the target language. (Consider two functions that both return the same global instance via a pointer for the trivial example of how this may occur).** Can you provide a reference to further explain the lack of a 1:1 mapping? This is quite surprising too me from what I've read so far about swig. I'm sure you're right, I just don't know why. – GeorgeWilson Jul 05 '12 at 22:37
  • @thebigdog SWIG only works off the definitions of the functions not the declarations. As such it's hard to prove if a function can return the same thing twice or not. This is why you need to use `%newobject` to tell SWIG about the ownership semantics of functions which create new objects. If the proxy doesn't own the object then there could be several created during the lifetime of an object. In it's really hard to prove something is the same object - if you call `new` and then `delete` and then `new` again that could legitimately reuse the same address. – Flexo Jul 06 '12 at 06:42
  • The easiest way to see this happen is to look into the code that gets generated. With Java the pointer a function returns gets passed around as a `long`, which is passed to the proxy constructor every time. I gave an example of using a map to find existing proxy objects in Java [in answer to this related but different problem](http://stackoverflow.com/questions/9817516/swig-java-retaining-class-information-of-the-objects-bouncing-from-c/9889597#9889597) if you're interested. – Flexo Jul 06 '12 at 06:46
  • Thanks again. I assume this is related to an issue I'm having with `memory leak detected, no destructor found`. This is occurring for a shared_ptr that a method is returning, and it is true there is no destructor but I assume shared_ptr will take care of this itself. If I understand you correctly, SWIG has no way of knowing this, so is worried that because it doesn't know where the ownership is, I may be letting memory leak. – GeorgeWilson Jul 08 '12 at 01:03