1

Our code base currently supports a single SWIG interface file (for Python) that has grown over the years to include roughly 300 C++ classes (technically interfaces), all of which inherit from a single base class, and all of which exist in a single global namespace. This allows us, with a minimal amount of SWIG code, to implement dynamic casting among the C++ classes that the SWIG classes represent while at the same time simplifying by keeping the C++ inheritance structure out of SWIG.

As long as we compiled our SWIG interface in a single module, this mechanism worked well -- but as the SWIG interface file has grown it has become difficult to manage, and compile/link times have grown. To address this I split the interface file up into separate modules by the names of the derived classes -- one module for class names beginning with "A" to "G", one for names beginning with "H" to "N", etc., resulting in four derived-class modules and a base class module. I was able to get these modules to compile and link, and exhibit expected behavior for the dynamic casting, following the method outlined here: (http://www.swig.org/Doc3.0/SWIGDocumentation.html#Modules_nn1)

However, breaking the single module into four parts (five parts counting the base class) causes problems with the namespace when containers come into play. Consider the following function, from a class in my v-to-z interface file:

void RemoveIsolated(const std::vector<global::IFoo*> spRemoveIsolated) {

… }

That takes a vector of one of the derived classes that exist in the global namespace. This worked without issue when I had only one module but now class IFoo lives in the a-to-g module -- so if I cast something to an IFoo*, it's an a-to-g.IFoo*. However, the function demands a global::IFoo*.

This seems to be a situation that could be addressed by the SWIG template mechanism. I've seen discussions in which people have had success by means of at one point (possibly in the interface file for the base class??) declaring

%template(FooVector) std::vector<global::Foo*>;

And at another point (possibly in the interface file for the derived class??):

%template () std::vector<global::Foo*>;

But my attempts to implement this have not been successful. The discussions are somewhat ambiguous, it's possible that I'm doing something wrong. Can anyone provide clarification, ideally with an example?

2 Answers2

0

The piece of information it looks like you're missing is the %import directive, which lets modules cooperate with the definition of types, without repeating them and still ending up with a single wrapped type. The documentation suggests using this to reduce module size even.

Probably all you need to do is have your v-to-z module %import the a-to-g module to get this working for you. (Personally I'd have tried to divide them up by functionality rather than alphabetically though, so the dependency between then wouldn't be an issue)

Flexo
  • 87,323
  • 22
  • 191
  • 272
0

Thanks for your suggestion Flexo. Importing the a-to-g module did not work; the C++ compiler complained that all of the classes (interfaces) declared there were not part of the global namespace when it tried to compile to v-to-z wrapper file. However, going through the exercise led me to question why we had been having success previously when we were compiling a single module. It turned out that we were using a typemapping macro in the interface file for the single module that would take a

const std::vector<global::IFoo*>

and map it thusly:

TYPEMAPMACRO(global::IFoo,  SWIGTYPE_p_global__IFoo)

for vector containers. The macro itself, for anyone who's interested, is:

%define TYPEMAPMACRO(type, name) %typemap(in) const std::vector { /*Check if is a list */ std::vector vec; void *pobj = 0; if(PyTuple_Check($input)) { size_t size = PyTuple_Size($input); for (size_t j = 0; j < size; j++) { PyObject *o = PyTuple_GetItem($input, j); void *argp1 = 0 ; int res1 = SWIG_ConvertPtr(o, &argp1, name, 0 | 0 ); if (!SWIG_IsOK(res1)) { SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Typemap of std::vector" "', argument " "1"" of type '" """'"); } vec.push_back(reinterpret_cast< type * >(argp1)); } $1 = vec; } else if (SWIG_IsOK(SWIG_ConvertPtr($input, &pobj, name, 0 | 0 ))) {
PyObject *o = $input; void *argp1 = 0 ; int res1 = SWIG_ConvertPtr(o, &argp1, name, 0 | 0 ); if (!SWIG_IsOK(res1)) { SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Typemap of std::vector" "', argument " "1"" of type '" """'"); } vec.push_back(reinterpret_cast< type * >(argp1)); $1 = vec; } else { PyErr_SetString(PyExc_TypeError, "not a list"); return NULL; } } %typecheck(SWIG_TYPECHECK_POINTER) std::vector { void *pobj = 0; if(!PyTuple_Check($input) && !SWIG_IsOK(SWIG_ConvertPtr($input, &pobj, name, 0 | 0 ))) { $1 = 0; PyErr_Clear(); } else { $1 = 1; } } %enddef

My sense is that this is standard boilerplate stuff, I don't claim to understand it well as it's someone else's code, but what I do understand now that I did not before is that I needed to place the macro for the typemap before the function that uses the typemap (e.g the "RemoveIsolated" example above). That ordering had been broken when I divided my big module up into smaller ones.