From 8f3a0752b13be7553bf68e188aa8aa13fde7e658 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Sun, 23 Jun 2024 23:33:21 -0300 Subject: [PATCH 1/4] Let Crystal user objects be created from g_gobject_new calls. This is a breaking change, since it now requires all GObject subclasses to have a constructor without arguments. What changed: There are 2 thread local variables to inform if the object is being created in C land or Crystal land, we store the objects instance pointers there. When the object is created in C land, the Crystal instance is created on GObject instance_init method. When the object is created in Crystal land, the GObject instance_init method creates no Crystal instance. This also fixes the use case of tryign to use a Crystal defined GObject property before the Wrapper gets fully initialized. Fixes https://github.com/hugopl/gtk4.cr/issues/69 --- ecr/gobject_constructor.ecr | 4 +-- spec/c_born_crystal_objects_spec.cr | 21 ++++++++++++ spec/gc_spec.cr | 5 +++ spec/inheritance_spec.cr | 9 +++++ src/bindings/g_object/binding.yml | 1 + src/bindings/g_object/object.cr | 52 +++++++++++++++++++++++++---- src/generator/method_gen.cr | 3 +- src/gi-crystal.cr | 9 +++++ 8 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 spec/c_born_crystal_objects_spec.cr 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..2f768dc --- /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 + 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..794cca1 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -128,6 +128,32 @@ 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*) + crystal_instance.as(GObject::Object)._gobj_pointer = instance.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 +448,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 +474,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 +625,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 @@ -616,6 +644,16 @@ module GObject self._after_init end + # :nodoc: + # Set the internal GObject pointer. + # + # When creating crystal objects using property constructors that set Crystal properties we must + # set the @pointer in the GObject instance_init method, because at this point the Crystal instance + # was already created but is inside a call of `g_object_new_with_properties` and would only set the + # @pointer after it returns, however the @pointer is needed to write the properties. + def _gobj_pointer=(@pointer) + end + # Returns GObject reference counter. def ref_count : UInt32 to_unsafe.as(Pointer(LibGObject::Object)).value.ref_count 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. # From d8297ea80eef1c9017df960efac896ff22c9ff35 Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Wed, 26 Jun 2024 12:10:44 -0300 Subject: [PATCH 2/4] Fix use with abstract classes or deeper type inheritance. --- ecr/object.ecr | 5 ++-- spec/c_born_crystal_objects_spec.cr | 34 +++++++++++++++++++++++--- src/bindings/g_object/object.cr | 37 ++++++++++++++++++++++------- src/gi-crystal.cr | 14 ++++++++++- 4 files changed, 76 insertions(+), 14 deletions(-) diff --git a/ecr/object.ecr b/ecr/object.ecr index bd20e0e..e39e230 100644 --- a/ecr/object.ecr +++ b/ecr/object.ecr @@ -22,10 +22,11 @@ module <%= namespace_name %> end <% elsif class_struct %> # :nodoc: - def self._register_derived_type(class_name : String, class_init, instance_init) + def self._register_derived_type(class_name : String, class_init, instance_init, flags : GObject::TypeFlags = GObject::TypeFlags::None) LibGObject.g_type_register_static_simple(g_type, class_name, sizeof(<%= to_lib_type(class_struct) %>), class_init, - sizeof(<%= to_lib_type(object) %>), instance_init, 0) + sizeof(<%= to_lib_type(object) %>), instance_init, + flags) end <% end %> diff --git a/spec/c_born_crystal_objects_spec.cr b/spec/c_born_crystal_objects_spec.cr index 2f768dc..d6ab2f5 100644 --- a/spec/c_born_crystal_objects_spec.cr +++ b/spec/c_born_crystal_objects_spec.cr @@ -2,7 +2,7 @@ require "./spec_helper" private class UserObj < GObject::Object @[GObject::Property] - property crystal_prop = "" + property crystal_prop1 = "" getter crystal_attr : Int32 = 42 def initialize @@ -10,12 +10,40 @@ private class UserObj < GObject::Object end end +private class InheritedUserObj < UserObj + @[GObject::Property] + property crystal_prop2 = "" + + def initialize + super + end +end + +private abstract class AbstractUserObj < GObject::Object +end + +private class NonAbstractUserObj < AbstractUserObj +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) + ptr = LibGObject.g_object_new(UserObj.g_type, "crystal_prop1", "value", Pointer(Void).null) user_obj = UserObj.new(ptr, :none) - user_obj.crystal_prop.should eq("value") + user_obj.crystal_prop1.should eq("value") user_obj.crystal_attr.should eq(42) user_obj.ref_count.should eq(1) end + + it "works with types hierarchy" do + ptr = LibGObject.g_object_new(InheritedUserObj.g_type, "crystal_prop1", "value1", "crystal_prop2", "value2", Pointer(Void).null) + user_obj = UserObj.new(ptr, :none) + user_obj.crystal_prop1.should eq("value1") + inherited_obj = InheritedUserObj.cast?(user_obj) + inherited_obj.should_not eq(nil) + inherited_obj.not_nil!.crystal_prop2.should eq("value2") + end + + it "works with abstract classes in hierarchy" do + NonAbstractUserObj.new + end end diff --git a/src/bindings/g_object/object.cr b/src/bindings/g_object/object.cr index 794cca1..ba498d4 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -86,9 +86,17 @@ module GObject if LibGLib.g_once_init_enter(pointerof(@@_g_type)) != 0 g_type = {{ @type.superclass.id }}._register_derived_type("{{ @type.name.gsub(/::/, "-") }}", ->_class_init(Pointer(LibGObject::TypeClass), Pointer(Void)), - ->_instance_init(Pointer(LibGObject::TypeInstance), Pointer(LibGObject::TypeClass))) - + ->_instance_init(Pointer(LibGObject::TypeInstance), Pointer(LibGObject::TypeClass)), + {% if @type.abstract? %} + GObject::TypeFlags::Abstract, + {% end %} + ) LibGLib.g_once_init_leave(pointerof(@@_g_type), g_type) + + {% unless @type.abstract? %} + ctor = ->{{ @type }}.new + LibGObject.g_type_set_qdata(g_type, GICrystal::INSTANCE_USERTYPE_FACTORY, ctor.pointer) + {% end %} self._install_ifaces end @@ -146,12 +154,25 @@ module GObject # - 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*) - crystal_instance.as(GObject::Object)._gobj_pointer = instance.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 + g_type = type.value.g_type + + crystal_instance = LibGObject.g_object_get_qdata(instance, GICrystal::INSTANCE_QDATA_KEY) + return if crystal_instance + + crystal_instance ||= GICrystal.crystal_object_being_created + {% unless @type.abstract? %} + if !crystal_instance + ctor_ptr = LibGObject.g_type_get_qdata(g_type, GICrystal::INSTANCE_USERTYPE_FACTORY) + GICrystal.g_object_being_created = instance.as(Void*) + crystal_instance = Proc(Void*).new(ctor_ptr, Pointer(Void).null).call + GICrystal.g_object_being_created = Pointer(Void).null + end + {% end %} + if crystal_instance + crystal_instance.as(GObject::Object)._gobj_pointer = instance.as(Void*) + LibGObject.g_object_set_qdata(instance, GICrystal::INSTANCE_QDATA_KEY, crystal_instance) + GICrystal.crystal_object_being_created = Pointer(Void).null + end end # :nodoc: diff --git a/src/gi-crystal.cr b/src/gi-crystal.cr index 967d820..4b4d661 100644 --- a/src/gi-crystal.cr +++ b/src/gi-crystal.cr @@ -32,6 +32,14 @@ module GICrystal # See `declare_new_method`. INSTANCE_FACTORY = LibGLib.g_quark_from_static_string("gi-crystal::factory") + # GICrystal stores the type constructor pointer as a qdata in GType, so when C code request + # the creation of a Type, the instance_init of the base type can create the right Crystal + # instance. + # + # `INSTANCE_FACTORY` OTOH stores the constructor that receives a pointer and the transfer mode, + # it's used when C code returns an base type but we need to create a wrapper for the right type. + INSTANCE_USERTYPE_FACTORY = LibGLib.g_quark_from_static_string("gi-crystal::ut-factory") + # Raised when trying to cast an object that was already collected by GC. class ObjectCollectedError < RuntimeError end @@ -123,6 +131,10 @@ module GICrystal # constructor uses it instead of call `g_object_new` @[ThreadLocal] class_property g_object_being_created : Pointer(Void) = Pointer(Void).null + + @[ThreadLocal] + class_property object_g_type_being_created : UInt64 = 0 + # 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] @@ -151,7 +163,7 @@ module GICrystal ctor_ptr = LibGObject.g_type_get_qdata(instance_g_type, GICrystal::INSTANCE_FACTORY) if ctor_ptr ctor = Proc(Void*, GICrystal::Transfer, {{ type }}).new(ctor_ptr, Pointer(Void).null) - return ctor.call(pointer, transfer) + return ctor.call(pointer, transfer).as({{ type }}) end end From de6a787c2d8f419ace5b701ca2e6409bedb8366f Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Fri, 28 Jun 2024 02:22:30 -0300 Subject: [PATCH 3/4] Update code comments and add more explanations. --- spec/c_born_crystal_objects_spec.cr | 10 +++++++++ src/bindings/g_object/object.cr | 33 ++++++++++++----------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/spec/c_born_crystal_objects_spec.cr b/spec/c_born_crystal_objects_spec.cr index d6ab2f5..7c4fc0b 100644 --- a/spec/c_born_crystal_objects_spec.cr +++ b/spec/c_born_crystal_objects_spec.cr @@ -25,6 +25,12 @@ end private class NonAbstractUserObj < AbstractUserObj end +# private class NonDefaultCtorObj < GObject::Object +# def initialize(int) +# super() +# end +# end + describe "Crystal GObjects" do it "can born in C land" do ptr = LibGObject.g_object_new(UserObj.g_type, "crystal_prop1", "value", Pointer(Void).null) @@ -46,4 +52,8 @@ describe "Crystal GObjects" do it "works with abstract classes in hierarchy" do NonAbstractUserObj.new end + + pending "doesn't require classes to have a default cosntructor" do + # NonDefaultCtorObj.new(42) + end end diff --git a/src/bindings/g_object/object.cr b/src/bindings/g_object/object.cr index ba498d4..3f016f0 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -138,36 +138,31 @@ module GObject # :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. + # GObject instance initialization, creates the Crystal instance if there's no one created yet. def self._instance_init(instance : Pointer(LibGObject::TypeInstance), type : Pointer(LibGObject::TypeClass)) : Nil - g_type = type.value.g_type - + # Return if the Crystal instance is already set up. crystal_instance = LibGObject.g_object_get_qdata(instance, GICrystal::INSTANCE_QDATA_KEY) return if crystal_instance - crystal_instance ||= GICrystal.crystal_object_being_created + # Check if this was called from a Crystal constructor + crystal_instance = GICrystal.crystal_object_being_created + # If not, this comes from a C call, so a Crystal instance needs to be created, however {% unless @type.abstract? %} if !crystal_instance - ctor_ptr = LibGObject.g_type_get_qdata(g_type, GICrystal::INSTANCE_USERTYPE_FACTORY) + # we need to create the right type, and the instance_init from all type hierarchy is called, + # so the instance factory set on GType registration also as a qdata is used. + ctor_ptr = LibGObject.g_type_get_qdata(type.value.g_type, GICrystal::INSTANCE_USERTYPE_FACTORY) + # Set the g_object_being_created, so the Crystal code wont call g_object_new again. GICrystal.g_object_being_created = instance.as(Void*) crystal_instance = Proc(Void*).new(ctor_ptr, Pointer(Void).null).call GICrystal.g_object_being_created = Pointer(Void).null end {% end %} + + # Now we have a Crystal object instance, let's set it up: + # - Set the INSTANCE_QDATA_KEY, so if someone read a property the get_property callback can + # know what's the Crystal instance. + # - Set the Crystal instance @pointer variable, so Crystal code can run without a dangling pointer. if crystal_instance crystal_instance.as(GObject::Object)._gobj_pointer = instance.as(Void*) LibGObject.g_object_set_qdata(instance, GICrystal::INSTANCE_QDATA_KEY, crystal_instance) From 769cdc523197428a5e0a1beb7364b93ead6d83aa Mon Sep 17 00:00:00 2001 From: Hugo Parente Lima Date: Fri, 28 Jun 2024 14:58:02 -0300 Subject: [PATCH 4/4] Allow Crystal User GObjects to not have a default constructor. If the object doens't have a default constructor and someone tries to create it from C, a GLib fatal error is issued. --- spec/c_born_crystal_objects_spec.cr | 16 +++++++++------- src/bindings/g_lib/lib_g_lib.cr | 2 ++ src/bindings/g_object/object.cr | 26 ++++++++++++++++---------- src/gi-crystal.cr | 13 +++++++++++++ 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/spec/c_born_crystal_objects_spec.cr b/spec/c_born_crystal_objects_spec.cr index 7c4fc0b..a9317a0 100644 --- a/spec/c_born_crystal_objects_spec.cr +++ b/spec/c_born_crystal_objects_spec.cr @@ -25,11 +25,11 @@ end private class NonAbstractUserObj < AbstractUserObj end -# private class NonDefaultCtorObj < GObject::Object -# def initialize(int) -# super() -# end -# end +private class NonDefaultCtorObj < GObject::Object + def initialize(int) + super() + end +end describe "Crystal GObjects" do it "can born in C land" do @@ -53,7 +53,9 @@ describe "Crystal GObjects" do NonAbstractUserObj.new end - pending "doesn't require classes to have a default cosntructor" do - # NonDefaultCtorObj.new(42) + it "doesn't require classes to have a default cosntructor" do + NonDefaultCtorObj.new(42) + # The line bellow must issue an GLib error and abort. + # LibGObject.g_object_new_with_properties(NonDefaultCtorObj.g_type, 0, Pointer(Pointer(UInt8)).null, Pointer(LibGObject::Value).null) end end diff --git a/src/bindings/g_lib/lib_g_lib.cr b/src/bindings/g_lib/lib_g_lib.cr index 0a9043c..da0f7b7 100644 --- a/src/bindings/g_lib/lib_g_lib.cr +++ b/src/bindings/g_lib/lib_g_lib.cr @@ -29,4 +29,6 @@ lib LibGLib # To work with old and newer GLibs, these functiosn are lib-ignored and added here manually. fun g_once_init_enter(location : Pointer(Void)) : LibC::Int fun g_once_init_leave(location : Pointer(Void), result : UInt64) : Void + + fun g_log(log_domain : LibC::Char*, log_level : Int32, format : LibC::Char*, ...) end diff --git a/src/bindings/g_object/object.cr b/src/bindings/g_object/object.cr index 3f016f0..c67f244 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -61,6 +61,13 @@ module GObject class Object macro inherited + # :nodoc + def self._create_obj_through_default_constructor : Pointer(Void) + LibGLib.g_log("GICrystal", 4, + {{ "Tried to create an instance of #{@type} from C, but #{@type} doesn't have a default constructor." }}) + Pointer(Void).null + end + {% unless @type.annotation(GICrystal::GeneratedWrapper) %} macro method_added(method) {% verbatim do %} @@ -71,6 +78,13 @@ module GObject {% end %} _register_{{ vfunc_name.id }}_vfunc({{ method.name }}) {% end %} + + {% if method.name == "initialize" && (method.args.empty? || method.args.all?(&.default_value)) %} + # :nodoc + def self._create_obj_through_default_constructor : Pointer(Void) + {{ @type }}.new.as(Void*) + end + {% end %} {% end %} end @@ -94,7 +108,7 @@ module GObject LibGLib.g_once_init_leave(pointerof(@@_g_type), g_type) {% unless @type.abstract? %} - ctor = ->{{ @type }}.new + ctor = ->_create_obj_through_default_constructor LibGObject.g_type_set_qdata(g_type, GICrystal::INSTANCE_USERTYPE_FACTORY, ctor.pointer) {% end %} self._install_ifaces @@ -148,15 +162,7 @@ module GObject crystal_instance = GICrystal.crystal_object_being_created # If not, this comes from a C call, so a Crystal instance needs to be created, however {% unless @type.abstract? %} - if !crystal_instance - # we need to create the right type, and the instance_init from all type hierarchy is called, - # so the instance factory set on GType registration also as a qdata is used. - ctor_ptr = LibGObject.g_type_get_qdata(type.value.g_type, GICrystal::INSTANCE_USERTYPE_FACTORY) - # Set the g_object_being_created, so the Crystal code wont call g_object_new again. - GICrystal.g_object_being_created = instance.as(Void*) - crystal_instance = Proc(Void*).new(ctor_ptr, Pointer(Void).null).call - GICrystal.g_object_being_created = Pointer(Void).null - end + crystal_instance ||= GICrystal.create_user_type_from_c_instance(instance, type) {% end %} # Now we have a Crystal object instance, let's set it up: diff --git a/src/gi-crystal.cr b/src/gi-crystal.cr index 4b4d661..a016c46 100644 --- a/src/gi-crystal.cr +++ b/src/gi-crystal.cr @@ -140,6 +140,19 @@ module GICrystal @[ThreadLocal] class_property crystal_object_being_created : Pointer(Void) = Pointer(Void).null + # This is used on `_instance_init` functions of user defined GObjects to create Crystal instances when the + # object was created from C code, i.e. `g_object_new`. + def create_user_type_from_c_instance(instance : Pointer(LibGObject::TypeInstance), type : Pointer(LibGObject::TypeClass)) : Pointer(Void) + ctor_ptr = LibGObject.g_type_get_qdata(type.value.g_type, GICrystal::INSTANCE_USERTYPE_FACTORY) + return Pointer(Void).null unless ctor_ptr + + # Set the g_object_being_created, so the Crystal code wont call g_object_new again. + GICrystal.g_object_being_created = instance.as(Void*) + Proc(Void*).new(ctor_ptr, Pointer(Void).null).call + ensure + GICrystal.g_object_being_created = Pointer(Void).null + end + # 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. #