I came across an interesting discovery related to how SWIG handles the count of references to C structures that contain other structures as members.
I noticed that my python SWIG object objects were garbage collected before I was done using them in situations where I stored data from subordinate structure elements to other python objects (lists / dicts). After honestly digging, I found that the members of the SWIG-ed structure do not seem to have their own independent reference counters, although the interpreter indicates that they are โSwig Objectsโ. Therefore, when I added data from a sub-element of the structure to my list, python did not know that I added a link to this data.
I created a simple demo example. I SWIG-ed the following 3 structures:
SWIG-ed C Structures:
typedef struct { unsigned long source; unsigned long destination; } message_header; typedef struct { unsigned long data[120]; } message_large_body; typedef struct { message_header header; message_large_body body; } large_message;
Then I created a somewhat equivalent python class to compare behavior with a purely SWIG-ed solution.
A somewhat equivalent Python class
class pyLargeMessage(object): def __init__(self): self.header = bar.message_header() self.body = bar.message_large_body()
Then I performed the following test in the interpreter.
Python interpreter results
>>> y = pyLargeMessage() >>> y <__main__.pyLargeMessage object at 0x06C5E6B0> >>> y.header <Swig Object of type 'message_header *' at 0x06C5E700> >>> sys.getrefcount(y.header) 3 >>> z = [y.header] >>> sys.getrefcount(y.header) 3 >>> z += [y.header] >>> sys.getrefcount(y.header) 4 >>> >>> y = bar.large_message() >>> y <Swig Object of type 'large_message *' at 0x06C668E0> >>> y.header <Swig Object of type 'message_header *' at 0x06C66B60> >>> sys.getrefcount(y.header) 1 >>> z = [y.header] >>> sys.getrefcount(y.header) 1 >>> z += [y.header] >>> sys.getrefcount(y.header) 1 >>>
The Python implementation behaved as I expected, but the pure SWIG implementation did not. Can someone explain what is going on here?
I have read various sections of the SWIG documentation many times and cannot find anything that directly explains this. I learned a lot more about how everything works, but I cannot find a clear explanation / workaround for the phenomenon above.
After thinking about this for a long time, re-reading Structures and Classes, Proxy classes and Structural data members, and again and again looking at the generated shell code. I still canโt understand why the link counts are not processed normally.
The generated C code calls SWIG_NewPointerObj , which ultimately (in most cases) calls PyObject_New , which in turn must (as stated in the python documentation) return a new link.
Generated SWIG code for get-er for header element
SWIGINTERN PyObject *_wrap_large_message_header_get(PyObject *self, PyObject *args) { PyObject *resultobj = 0; large_message *arg1 = (large_message *) 0 ; void *argp1 = 0 ; int res1 = 0 ; message_header *result = 0 ; if (args && PyTuple_Check(args) && PyTuple_GET_SIZE(args) > 0) SWIG_fail; res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_large_message, 0 | 0 ); if (!SWIG_IsOK(res1)) { SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "large_message_header_get" "', argument " "1"" of type '" "large_message *""'"); } arg1 = (large_message *)(argp1); result = (message_header *)& ((arg1)->header); resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_message_header, 0 | 0 ); return resultobj; fail: return NULL; }