diff --git a/ecr/gobject_constructor.ecr b/ecr/gobject_constructor.ecr index 80d8e02..7ce15e6 100644 --- a/ecr/gobject_constructor.ecr +++ b/ecr/gobject_constructor.ecr @@ -11,12 +11,12 @@ def initialize(<%= gobject_constructor_parameter_declaration %>) end <% end %> + GICrystal.crystal_object_being_created = self.as(Void*) ptr = LibGObject.g_object_new_with_properties(self.class.g_type, _n, _names, _values) + LibGObject.<%= object.qdata_set_func %>(ptr, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id)) super(ptr, :full) _n.times do |i| LibGObject.g_value_unset(_values.to_unsafe + i) end - - LibGObject.<%= object.qdata_set_func %>(@pointer, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id)) end diff --git a/spec/c_born_crystal_objects_spec.cr b/spec/c_born_crystal_objects_spec.cr new file mode 100644 index 0000000..e8a6ae6 --- /dev/null +++ b/spec/c_born_crystal_objects_spec.cr @@ -0,0 +1,21 @@ +require "./spec_helper" + +private class UserObj < GObject::Object + @[GObject::Property] + property crystal_prop = "" + getter crystal_attr : Int32 = 42 + + def initialize(@hey = "") + super() + end +end + +describe "Crystal GObjects" do + it "can born in C land" do + ptr = LibGObject.g_object_new(UserObj.g_type, "crystal_prop", "value", Pointer(Void).null) + user_obj = UserObj.new(ptr, :none) + user_obj.crystal_prop.should eq("value") + user_obj.crystal_attr.should eq(42) + user_obj.ref_count.should eq(1) + end +end diff --git a/spec/gc_spec.cr b/spec/gc_spec.cr index 8117c7d..c6d3fd6 100644 --- a/spec/gc_spec.cr +++ b/spec/gc_spec.cr @@ -3,6 +3,11 @@ require "./spec_helper" private class GCResistantObj < GObject::Object property moto : String + def initialize + super + @moto = "" + end + def initialize(@moto) super() end diff --git a/spec/inheritance_spec.cr b/spec/inheritance_spec.cr index 64e047d..096eab5 100644 --- a/spec/inheritance_spec.cr +++ b/spec/inheritance_spec.cr @@ -10,9 +10,18 @@ private class UserObjectWithCtor < GObject::Object def initialize(@string : String) super() end + + def initialize + super + @string = "" + end end private class UserSubject < Test::Subject + def initialize + super + end + def initialize(string : String) super(string: string) end diff --git a/src/bindings/g_object/binding.yml b/src/bindings/g_object/binding.yml index 37ab978..8c8b24a 100644 --- a/src/bindings/g_object/binding.yml +++ b/src/bindings/g_object/binding.yml @@ -279,3 +279,4 @@ types: execute_callback: - g_signal_emitv - g_closure_invoke + - g_object_newv diff --git a/src/bindings/g_object/object.cr b/src/bindings/g_object/object.cr index 4a47838..bf62082 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -128,6 +128,31 @@ module GObject {% end %} end + # :nodoc: + # + # GObject instance initialization, this can be called when a GObject is created from Crystal `MyObj.new` or by + # C `g_object_new(MyObject.g_type)`, in both cases some tasks are always done here: + # + # - INSTANCE_QDATA_KEY is always set here, so Crystal properties can be fetched. + # - Floating refs are sank + # + # So, if the object is created from C + # + # - Set `GICrystal.g_object_being_created` with the C object instance pointer. + # - Call the Crystal object constructor, that will look the flag set above and use it instead of call `g_object_new`. + # + # If the object is created from Crystal + # + # - Set `GICrystal.crystal_object_being_created` with the Crystal object instance pointer. + # - Use the Crystal object instead of calling the Crystal object constructor. + def self._instance_init(instance : Pointer(LibGObject::TypeInstance), type : Pointer(LibGObject::TypeClass)) : Nil + GICrystal.g_object_being_created = instance.as(Void*) + crystal_instance = GICrystal.crystal_object_being_created || {{ @type }}.new.as(Void*) + LibGObject.g_object_set_qdata(instance, GICrystal::INSTANCE_QDATA_KEY, crystal_instance) + GICrystal.crystal_object_being_created = Pointer(Void).null + GICrystal.g_object_being_created = Pointer(Void).null + end + # :nodoc: def self._g_toggle_notify(object : Void*, _gobject : Void*, is_last_ref : Int32) : Nil return if object.null? @@ -422,10 +447,6 @@ module GObject {% end %} end - # :nodoc: - def self._instance_init(instance : Pointer(LibGObject::TypeInstance), type : Pointer(LibGObject::TypeClass)) : Nil - end - # :nodoc: def self._install_ifaces {% verbatim do %} @@ -452,7 +473,6 @@ module GObject # This specific implementation turns a normal reference into a toggle reference. private def _after_init : Nil # Set toggle ref to protect the crystal object from the garbage collector while in C. - self.class._g_toggle_notify(self.as(Void*), @pointer, 0) LibGObject.g_object_add_toggle_ref(@pointer, G_TOGGLE_NOTIFY__, self.as(Void*)) LibGObject.g_object_unref(@pointer) @@ -604,9 +624,16 @@ module GObject end def initialize - @pointer = LibGObject.g_object_newv(self.class.g_type, 0, Pointer(LibGObject::Parameter).null) + GICrystal.crystal_object_being_created = Pointer(Void).new(object_id) + + g_object = GICrystal.g_object_being_created + @pointer = g_object || LibGObject.g_object_newv(self.class.g_type, 0, Pointer(LibGObject::Parameter).null) + GICrystal.g_object_being_created = Pointer(Void).null + + # If object is created by C, the qdata was already set. + LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, self.as(Void*)) unless g_object LibGObject.g_object_ref_sink(self) if LibGObject.g_object_is_floating(self) == 1 - LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id)) + self._after_init end diff --git a/src/generator/method_gen.cr b/src/generator/method_gen.cr index 833d721..1fc8b25 100644 --- a/src/generator/method_gen.cr +++ b/src/generator/method_gen.cr @@ -87,7 +87,8 @@ module Generator private def method_return_type_declaration : String if @method.flags.constructor? - return @method.may_return_null? ? ": self?" : ": self" + type = to_crystal_type(object.as(RegisteredTypeInfo)) + return @method.may_return_null? ? ": #{type}?" : ": #{type}" end return_type = method_return_type diff --git a/src/gi-crystal.cr b/src/gi-crystal.cr index 3d85d06..967d820 100644 --- a/src/gi-crystal.cr +++ b/src/gi-crystal.cr @@ -119,6 +119,15 @@ module GICrystal end end + # When creating an user defined GObject from C, the GObject instance is stored here, so the Crystal + # constructor uses it instead of call `g_object_new` + @[ThreadLocal] + class_property g_object_being_created : Pointer(Void) = Pointer(Void).null + # When creating an user defined GObject from Crystal, the Crystal instance is stored here, so the + # GObject `instance_init` doesn't instantiate another Crystal object. + @[ThreadLocal] + class_property crystal_object_being_created : Pointer(Void) = Pointer(Void).null + # This declare the `new` method on a instance of type *type*, *qdata_get_func* (g_object_get_qdata) is used # to fetch a possible already existing Crystal object. #