Description
Further investigating why Ref<> isn't working properly I found a weird issue with casting. In C++ when inheriting it is possible that data structures shift and there is thus no guarantee that the pointer is the same. So:
BaseClass *base_class = (BaseClass *)sub_class;
Could result in the base_class
pointer not being equal to the sub_class
pointer.
The compiler deals with this nicely, so doing the following on our wrapper classes in GDExtensions:
Ref<XRInterfaceReference> interface;
interface.instantiate();
XRInterfaceReference *xr_interface_reference = interface.ptr();
XRInterfaceExtension *xr_interface_extension = (XRInterfaceExtension *)xr_interface_reference;
XRInterface *xr_interface = (XRInterface *)xr_interface_reference;
RefCounted *ref_counted = (RefCounted *)xr_interface_reference;
Object *object = (Object *)xr_interface_reference;
UtilityFunctions::print("XRInterfaceReference: ", Variant((uint64_t) xr_interface_reference),", owner: ", Variant((uint64_t) xr_interface_reference->_owner));
UtilityFunctions::print("XRInterfaceExtension: ", Variant((uint64_t) xr_interface_extension),", owner: ", Variant((uint64_t) xr_interface_extension->_owner));
UtilityFunctions::print("XRInterface: ", Variant((uint64_t) xr_interface),", owner: ", Variant((uint64_t) xr_interface->_owner));
UtilityFunctions::print("RefCounted: ", Variant((uint64_t) ref_counted),", owner: ", Variant((uint64_t) ref_counted->_owner));
UtilityFunctions::print("Object: ", Variant((uint64_t) object),", owner: ", Variant((uint64_t) object->_owner));
We can see this casts nicely:
XRInterfaceReference: 2226252139088, owner: 2226061244336
XRInterfaceExtension: 2226252139088, owner: 2226061244336
XRInterface: 2226252139096, owner: 2226061244336
RefCounted: 2226252139096, owner: 2226061244336
Object: 2226252139096, owner: 2226061244336
But also notice that from XRInterface our pointer is shifted 8 bytes, something is added to the XRInterfaceExtension data which the compiler puts in front of instead of after the data from the super class.
So for now not a problem, until we use Godots own casting logic:
RefCounted *ref_counted_casted = Object::cast_to<RefCounted>(xr_interface_reference);
UtilityFunctions::print("Casted: ", Variant((uint64_t) ref_counted_casted),", owner: ", Variant((uint64_t) ref_counted_casted->_owner));
Now our output is:
Casted: 2226252139088, owner: 140723262435304
We see that our original pointer is used but because this is incorrect for our RefCounted
class we're reading the wrong memory for owner causing a crash if we attempt to use this object.
The reason for this is that our Object::cast_to<T>
uses the owner to ask Godot to perform the cast, and then lookup the wrapper pointer belonging to that owner, which is still our subclass' pointer.
This approach to casting thus only works if there is no shift in the data.
So far my theory is that the shift is caused by the introduction of a vtable for the new virtual functions that were introduced and the pointer to this vtable precedes the subclass struct.