interrogate2swig.py

To be honest however, what I’m thinking I will probably do for my own project is: leave the Panda build as is, and just link my new classes to the current libpanda.dll, using swig to wrap just my own classes.

The advantage for me and my project is that there is no requirement to build Panda3D itself, or to write patch files for makepanda.py etc.

The advantage for Panda is that I’ll probably stop pushing for Panda to migrate to swig. Of course, this could be a double-edged sword.

In any case, if using wrap-around linking works, and if I’m still thinking along similar lines tomorrow, this basically means that the kit above will not be maintained unless someone else picks it up and uses it.

Nevertheless, as a feasibility study for using Swig, I think it is pretty useful. It’s also not bad as a prototype for how one could create a swig-based build system for Panda.

It shows that:

  • swig can be used successfully to wrap Panda3D
  • swig is much faster than interrogate (30 seconds compared to several hours); and its not memory intensive either
  • swig is mature and well designed, following a KISS philosophy

Lessons learned as far as actually using it in a build system, assuming no modification to actual source-code, or manual interface-file building:

  • passing the header files through the c preprocessor first makes life much easier, especially if one defines the CPPPARSER macro
  • swig really wants to know everything about your class, including protected members, in order to decide whether the class should have a constructor.
  • swig doesnt need to know, doesnt care, about any base classes that you dont care about advertising to your scripting language, even if your advertised classes derive from them
  • swig doesnt like inherited, nested enums; it wont handle nested classes at all
  • swig does need to know about any classes you’ll actually use for parameter passing
  • swig does need the classes in sorted order in the interface file
  • I guess one could also put each class in its own interface file and just use %import statements to load the interfaces of classes on which it depends

Other observations:

  • theres no particular requirement for the c++ wrapper files to be linked statically inside libpanda.dll etc; it could be at least as easy to link them to separate dlls that link to libpanda.dll at runtime
  • this reduces the build time for libpanda.dll, and generally makes the build system easier to manage
  • there’s no particular requirement for all the panda classes to be linked inside the same wrapper dll; all these wrapper dlls will be linking with the same libpanda.dll anyway (same static data).

Final thoughts:

  • swig is definitely much faster than interrogate
  • but there’s a fair wodge of work to migrate cleanly from interrogate to swig
  • it could arguably be easier to just upgrade interrogate a little

Hugh

Note that there is a file missing from the above kit, which is pandatypes.i, which should go in the directory above makepanda, panda etc, and which looks as follows:


// Copyright Hugh Perkins 2005
// This code is public domain.

%typemap(in) string & {
   /* Check if is a string */
   if (PyString_Check($input)) {
      //$1 = (string *)malloc( sizeof( string * ) );
      $1 = new string( PyString_AsString( $input ) );
   } else {
      PyErr_SetString(PyExc_TypeError,"not a string");
      return NULL;
   }
}

%typemap(freearg) string & {
   delete( $1 );
   //free( $1 );
}

%typemap(in) string {
   /* Check if is a string */
   if (PyString_Check($input)) {
      $1 = string( PyString_AsString( $input ) );
   } else {
      PyErr_SetString(PyExc_TypeError,"not a string");
      return NULL;
   }
}

%typemap(freearg) string {
   //delete( $1 );
}

%typemap(in) char ** {
   /* Check if is a list */
   if (PyList_Check($input)) {
      int size = PyList_Size($input);
      int i = 0;
      $1 = (char **) malloc((size+1)*sizeof(char *));
      for (i = 0; i < size; i++) {
         PyObject *o = PyList_GetItem($input,i);
         if (PyString_Check(o))
            $1[i] = PyString_AsString(PyList_GetItem($input,i));
         else {
            PyErr_SetString(PyExc_TypeError,"list must contain strings");
            free($1);
            return NULL;
         }
      }
      $1[i] = 0;
   } else {
      PyErr_SetString(PyExc_TypeError,"not a list");
      return NULL;
   }
}
// This cleans up the char ** array we malloc'd before the function call
%typemap(freearg) char ** {
   free( (char *) $1);
}

%typemap(in) char **& {
   /* Check if is a list */
   if (PyList_Check($input)) {
      int size = PyList_Size($input);
      int i = 0;
    //  cout << "allocating array, for " << size << " elements" << endl;
      $1 = (char ***)malloc( sizeof( char *));
     // cout << "allocated pointer to pointer" << endl;
      *($1) = (char **) malloc((size+1)*sizeof(char *));
    //  cout << "assinged to array variable" << endl;
      char **array = *$1;      
    //  cout << "got array" << endl;
      for (i = 0; i < size; i++) {
         PyObject *o = PyList_GetItem($input,i);
         if (PyString_Check(o)) {
           //  cout << "adding string " << PyString_AsString(PyList_GetItem($input,i)) << endl;
            array[i] = PyString_AsString(PyList_GetItem($input,i));
         }
         else {
            PyErr_SetString(PyExc_TypeError,"list must contain strings");
            free(array);
            free($1);
            return NULL;
         }
      }
      array[i] = 0;
   //   cout << "done" << endl;
   } else {
      PyErr_SetString(PyExc_TypeError,"not a list");
      return NULL;
   }
}
// This cleans up the char ** array we malloc'd before the function call
%typemap(freearg) char **& {
   //   cout << "freeing mem" << endl;
   free( (char *) (*$1));
   free( (char *) ($1));
    //  cout << "done" << endl;
}