Hello again;
here's an observation that I find interesting. Python and C++ use different object storage concepts (e.g. a simple type like integer is actually a reference in Python but a value in C++), so whatever object is constructed in C++ cannot be used 1:1 in Python (no memory mirroring).
That means for the Python API, every object returned by a function cannot be identical to the C++ object that C4D itself uses. Python would not understand the memory allocations there. So, the API function must return a proxy object that provides access for Python to the actual C++ objects. (Right?)
Here's a test (sorry for using the GeListHead
again, this topic has nothing to do with my previous one):
import c4d
def main():
obj = op
prevHead = None
print "---------------"
while obj != None:
head = obj.GetListHead()
print type(head), id(head), head
if prevHead != None:
print prevHead == head, prevHead is head
else:
print "None"
prevHead = head
obj = obj.GetNext()
if __name__=='__main__':
main()
The script goes through some objects in the Object Manager which are all on the same tree level, starting with the selected one, and check the GeListHead
for each. The result is compared with the previous GeListHead
by equality comparison == and identity comparison is.
The output looks like this:
---------------
<type 'c4d.GeListHead'> 1809691147248 <c4d.GeListHead object at 0x000001A559FF7BF0>
None
<type 'c4d.GeListHead'> 1809691146800 <c4d.GeListHead object at 0x000001A559FF7A30>
True False
<type 'c4d.GeListHead'> 1809691146928 <c4d.GeListHead object at 0x000001A559FF7AB0>
True False
<type 'c4d.GeListHead'> 1809691147248 <c4d.GeListHead object at 0x000001A559FF7BF0>
True False
>>>
Theoretically, the GeListHead
should be the same object - identical, at the same address in memory - for all objects, as the objects belong to the same tree structure. That is not the case: the type is correct, but Python's own id()
function returns a different number, and the memory address printed by the plain object printout is also different. These numbers repeat after three iterations, presumably because the garbage collection has destroyed the Python proxy by then and is reusing its properties.
It is logical that the equality test returns True
(as the object is identical on the C++ side) but the identity test returns False
(as the proxy object is different)... at least, it is logical in this interpretation.
Unfortunately, the objects should be identical too. If we were working in C++, the plain pointers would be the same, referencing the same object. The Python API adds an abstraction level which destroys the identity relation. I guess it would be necessary for the API to check for existing proxys and reuse those. (In the sample code, this would prevent the garbage collection from destroying the proxy until the very end of the script, as there would always be a reference to it through prevHead
.)
Is that interpretation of mine even correct? I'm kind of reverse engineering here...
So, this raises first the question how to check for identity of two C4D objects from different references. The BaseObject
has GetGUID()
available for comparisons; a GeListHead
as used here is no BaseObject
though, and doesn't provide a unique ID.
Second, are there any more Python development details we have to be careful with, because the Python/C++ interface may introduce unforeseen difficulties?
e.g. memory allocation: C++ has an explicit allocation, while Python uses a garbage collector. Python will destroy an object when there are no more references to it (which in turn may destroy other objects that become non-referenced). Will that properly happen to a C4D object too? Like, I Remove()
a BaseObject
from a tree and then the variable where I store it goes out of scope... I haven't found evidence to the contrary, but it must be hellishly complicated to count references if the code changes between C++ modifications and Python modifications to the "same" object when methods of a Python plugin are executed.
e.g. destructor calls: when is the C4D object actually destroyed? The garbage collector may destroy the Python proxy later (C++: immediately) when it happens to run. Is the destructor of the C4D object called when the proxy is destroyed, or earlier (when the reference counter drops to 0), or later? If there are dependent objects (like tags for a BaseObject
), when are these destroyed (provided the BaseObject
C++ destructor takes care of them)?
e.g. Generator functions: these keep their state and execution point between calls, so all variables are preserved; including any proxy objects handling C4D objects. These are obviously vulnerable to changes between calls (okay, that's a bad example as Python has the same issue).
I've trying to find details on the C4D/Python interfacing in the Python manual but can't locate any deeper information.