#ifndef GI_OBJECTCLASS_HPP #define GI_OBJECTCLASS_HPP /** * The purposes of the code and classes here is to perform registration of * a new GType type for a custom class that (suitably) inherits from these. * Setting up a new GType involves registration of a new type (and its signals * and properties). This involves potentially custom overridden implementations * of the parent class(es) (C-style virtual) methods (= entries in class * structure) and likewise so for the interfaces. For either of the latter * cases, pointers to wrapper functions are inserted in the class/interface * struct, that will in turn call generated C++ virtual methods * (which have presumably been overridden by the custom subclass). * * The tricky part in the above is that GType/C methods are "optional", * in that an entry in a class/interface struct can be left as NULL, * and it is a common pattern for calling code to check for such * (rather than unconditionally calling). To preserve such behaviour, * an entry in a class/interface struct should only be "routed" to a * C++ virtual method if the custom SubClass actually has a definition for it. * The approach below tries to detect this (basically by checking if * SubClass::method is a different type than Base::method). But that might * fail (with compilation errors due to overload resolution failure), so there * is also a system to manually specify what is defined, e.g. whether the * corresponding entry in the class/interface struct should be filled * for the SubClass' generated GType. * */ #include "callback.hpp" #include "object.hpp" GI_MODULE_EXPORT namespace gi { // slightly nasty; will be generated namespace repository { namespace GObject { enum class SignalFlags : std::underlying_type<::GSignalFlags>::type; } } // namespace repository // specify construction type when creating ObjectClass based object struct construct_t { const int value; constexpr explicit construct_t(int v = 0) : value(v) {} }; struct construct_auto_t : public construct_t { constexpr construct_auto_t() : construct_t(0) {} }; struct construct_cpp_t : public construct_t { constexpr construct_cpp_t() : construct_t(1) {} }; struct construct_c_t : public construct_t { constexpr construct_c_t() : construct_t(2) {} }; namespace detail { class ObjectBaseClass : public noncopyable { public: virtual ~ObjectBaseClass() {} ObjectBaseClass() : gobject_(nullptr) {} ObjectBaseClass(ObjectBaseClass &&other) { gobject_ = other.gobject_; other.gobject_ = nullptr; } ObjectBaseClass &operator=(ObjectBaseClass &&other) { if (this != &other) { gobject_ = other.gobject_; other.gobject_ = nullptr; } return *this; } protected: typedef void (*interface_register_function)(GType class_type, gpointer init); void request_interface(interface_register_function reg, gpointer init) { itfs.emplace_back(reg, init); } GType base_gtype() { return gobject_ ? g_type_parent(G_OBJECT_TYPE(gobject_)) : G_TYPE_NONE; } protected: // owns 1 ref (possibly managed externally) GObject *gobject_; // GType in case no gobject_ GType gtype_; // additional type setup using interface_init_t = std::pair; using interface_inits_t = std::vector; interface_inits_t itfs; }; //// interface setup //// // virtual inheritance as multiple inheritance will be used // however, only 1 base instance should be around class InterfaceClass : public virtual ObjectBaseClass { protected: static void add_interface(GType class_type, GType itf_type, GInterfaceInitFunc itf_init, gpointer init_data) { GInterfaceInfo itf_info = { itf_init, // interface_init nullptr, // interface_finalize init_data // interface_data }; g_type_add_interface_static(class_type, itf_type, &itf_info); } }; template gpointer forward_make_type_init_data(); template class InterfaceImpl : public InterfaceDef, public InterfaceClass { typedef typename InterfaceDef::instance_type instance_type_t; protected: static void register_interface(GType class_type, gpointer init_data) { add_interface(class_type, InterfaceDef::instance_type::get_type_(), InterfaceDef::interface_init, init_data); } // init data for registration-only use template static ObjectBaseClass::interface_init_t interface_init_data() { return {InterfaceImpl::register_interface, (gpointer)forward_make_type_init_data()}; } InterfaceImpl(gpointer init_data = nullptr) { request_interface(InterfaceImpl::register_interface, init_data); } // conversion to regular interface side instance_type_t interface_(gi::interface_tag) { return gi::wrap( (typename instance_type_t::BaseObjectType *)g_object_ref(gobject_), gi::transfer_full); } instance_type_t interface_() { return gi::wrap( (typename instance_type_t::BaseObjectType *)g_object_ref(gobject_), gi::transfer_full); } }; template class InterfaceClassImpl : public InterfaceImpl { typedef typename InterfaceImpl::instance_type::BaseObjectType *c_type; typedef typename InterfaceImpl::interface_type interface_type_t; // use runtime data to avoid code generation of template // interface_type_t *istruct_; GType klass_; protected: static interface_type_t *get_struct_(GType ktype) { auto klass = g_type_class_peek(ktype); return (interface_type_t *)g_type_interface_peek( klass, InterfaceImpl::instance_type::get_type_()); } interface_type_t *get_struct_() { return get_struct_(klass_); } c_type gobj_() { return (c_type)this->gobject_; } InterfaceClassImpl(GType klass, gpointer itf_init_data = nullptr) : InterfaceImpl(itf_init_data), klass_(klass) {} }; class ObjectClass; class PropertyBase; inline GQuark object_data_quark() { static const char *OBJECT_DATA_KEY = "GIOBJECT_OBJECT"; static GQuark q = g_quark_from_static_string(OBJECT_DATA_KEY); return q; } using repository::GObject::Object; using repository::GObject::ParamSpec; using repository::GObject::SignalFlags; //// init data collection //// // a function pointer that serves as factory for the actual class_init data // (e.g. holds data on which methods have been overridden) // (void return to avoid function cast warning; returns real data otherwise) typedef void (*type_init_data_factory_t)(); template::value>::type * = nullptr> type_init_data_factory_t make_type_init_data() { return nullptr; } template::value>::type * = nullptr> type_init_data_factory_t make_type_init_data() { // hard cast; should return meaningful data return (type_init_data_factory_t)&BaseDef::TypeInitData::template factory< SubClass>; } template gpointer forward_make_type_init_data() { return (gpointer)make_type_init_data(); } // in generated code; // each class/interface member (function) is assocated with a single type // base class for tagged boolean member types // (default to true for legacy case where no init data is captured from type) template struct member_type { bool value; constexpr member_type(bool v = true) : value(v) {} explicit operator bool() { return value; } }; // combine manual Spec with code generated Default template struct Combine : public Spec, public Default { using Default::has_definition; using Spec::defines; // dispatch from query signature to default if no manual specificiation template constexpr static bool defines(const MemberType *m, const SubClass *cl) { return has_definition(m, cl); }; }; template using DefinitionData = Combine; // see objectclass macros in gi_inc for how the above is used //// class setup //// // this collects properties that will be installed on a class type // during the class_init (as opposed to added later on by property members) // this is especially needed to install properties required by an interface // (as those are checked by object_interface_check_properties early on) typedef std::map> properties; template class property; template class signal; struct ParamSpecInit : public ParamSpec { cstring_v name_; ParamSpec spec_; // full spec arguments template::type * = nullptr> ParamSpecInit(property

T::*, Args &&...args) : spec_(ParamSpec::new_

(std::forward(args)...)) {} // name suffices for override property template ParamSpecInit(property

T::*, const cstring_v name) : name_(name) {} }; struct properties_init : public properties { properties_init(std::initializer_list pd) { for (auto &&e : pd) { // should have either one auto name = e.name_; if (e.spec_) name = e.spec_.name_(); insert({name, {nullptr, e.spec_}}); } } }; struct SignalSpec { GType ret_type; std::vector types; gi::cstring_v name; SignalFlags flags; template SignalSpec(signal T::*, const gi::cstring_v _name, SignalFlags _flags = (SignalFlags)0) : ret_type(gi::traits::gtype::get_type()), types({gi::traits::gtype::get_type()...}), name(_name), flags(_flags) {} }; using SignalSpecInit = SignalSpec; using signals = std::vector; using signals_init = signals; inline void class_init_props_sigs( ObjectClass *impl, gpointer g_class, gpointer props, gpointer sigs); inline gi::cstring_v klass_type_name(const std::type_info &ti, gi::cstring_v klassname) { return (klassname && klassname.at(0)) ? klassname : ti.name(); } template struct custom_class_init : public std::false_type { static constexpr GClassInitFunc value = nullptr; }; template struct custom_class_init::type> : public std::true_type { static constexpr auto value = T::custom_class_init; }; template struct custom_instance_init : public std::false_type { static constexpr GInstanceInitFunc value = nullptr; }; template struct custom_instance_init::type> : public std::true_type { static constexpr auto value = T::custom_init; }; class ObjectClass : public virtual ObjectBaseClass { public: typedef gi::repository::GObject::Object instance_type; typedef GObjectClass class_type; // above init code refers to this inner type unconditionally // so arrange for a fallback in baseclass struct DefinitionData { constexpr static bool defines(...) { return false; } }; // used as single argument in constructor for c-first style subclass struct InitData { mutable Object instance; explicit operator bool() const { return instance.gobj_(); } protected: // only for internal use // and also complicates calling any function/constructor using this type // silly dummy argument prevents ambiguity in ObjectImpl constructors // when specifying all {} explicit InitData(int) {} friend class ObjectClass; }; protected: struct ClassInitNode { GClassInitFunc self; type_init_data_factory_t class_init_data_factory; const ClassInitNode *child; }; private: static const constexpr char *CLASS_PREFIX = "GIOBJECT__"; static std::string canonical_name(const std::string &name) { auto result = name; for (auto &p : result) { if (!(g_ascii_isalnum(p) || p == '_' || p == '-')) p = '+'; } return result; } typedef void (*GObjectConstructed)(GObject *object); typedef GObject *(*GObjectConstructor)(GType type, guint n_construct_properties, GObjectConstructParam *construct_properties); typedef std::vector> class_inits_t; struct class_data_t { std::unique_ptr class_inits; GClassInitFunc custom_class_init; const properties *props; const signals *sigs; ObjectClass *impl; GObjectConstructor constructor; }; struct custom_inits_t { GClassInitFunc custom_class_init{}; GInstanceInitFunc custom_init{}; GObjectConstructor constructor{}; GObjectConstructed constructed{}; }; static void class_init_all(gpointer g_class, gpointer class_data) { std::unique_ptr data((class_data_t *)class_data); // class_init below is called with top-level class_data, // others (= code generated) with their own for (auto &&ci : *data->class_inits) ci.first( g_class, ci.first == &class_init ? class_data : gpointer(ci.second)); } static void class_init(gpointer g_class, gpointer class_data) { class_data_t *data = ((class_data_t *)class_data); // delegate property handling class_init_props_sigs( data->impl, g_class, (gpointer)data->props, (gpointer)data->sigs); // set constructed to invoke constructor if (data->constructor) ((GObjectClass *)(g_class))->constructor = data->constructor; // also call top-level class custom class init if (data->custom_class_init) { data->custom_class_init(g_class, nullptr); } } GType register_type(GType base_type, const gi::cstring_v klassname, const ClassInitNode &init_node, const interface_inits_t &itfs, const properties &props, const signals &sigs, custom_inits_t custom_inits) { // nothing to do if already registered GType custom_type = g_type_from_name(klassname.c_str()); if (custom_type) return custom_type; // otherwise create with same class/instance size as parent type GTypeQuery base_query = { 0, nullptr, 0, 0, }; g_type_query(base_type, &base_query); const guint16 class_size = (guint16)base_query.class_size; const guint16 instance_size = (guint16)base_query.instance_size; // collect chain of class inits std::unique_ptr class_inits(new class_inits_t()); auto node = &init_node; while (node) { if (node->self) class_inits->push_back({node->self, node->class_init_data_factory}); node = node->child; } // assemble class_data std::unique_ptr class_data(new class_data_t()); // class creation will be triggered upon instance creation // which will happen shortly after this class // (so the list has to handled special, but the others will still be // around) class_data->class_inits = std::move(class_inits); class_data->custom_class_init = custom_inits.custom_class_init; class_data->impl = this; class_data->props = &props; class_data->sigs = &sigs; class_data->constructor = custom_inits.constructor; const GTypeInfo derived_info = { class_size, nullptr, // base_init nullptr, // base_finalize class_init_all, nullptr, // class_finalize class_data.release(), // class_data instance_size, 0, // n_preallocs custom_inits.custom_init, // instance_init nullptr, // value_table }; custom_type = g_type_register_static( base_type, klassname.c_str(), &derived_info, GTypeFlags(0)); // handle interfaces for (auto &&itf : itfs) itf.first(custom_type, itf.second); // force/finish class creation, // so a subsequent _peek does not return NULL // (and lists above are still around) g_type_class_unref(g_type_class_ref(custom_type)); return custom_type; } // minor convenience wrap for the above GType register_type(GType parent, const gi::cstring_v klassname, const ClassInitNode *node, const properties &props, const signals &sigs, custom_inits_t custom_inits) { auto gtype = register_type(parent, klassname, {class_init, nullptr, node}, itfs, props, sigs, custom_inits); itfs.clear(); return gtype; } void setup_instance(GType gtype, const repository::GObject::construct_params ¶ms, gpointer instance = nullptr) { // not good if provided instance already has associated C++ instance g_return_if_fail(!instance || !ObjectClass::instance((GObject *)instance)); g_return_if_fail(gtype); // no longer needed` itfs.clear(); // create and link object instance // if needed, that is, otherwise use provided instance and tie onto that one gtype_ = gtype; GObject *obj = gobject_ = (GObject *)(instance ? instance : Object::new_(gtype, params)); // should still be floating, then we assume ownership // if it is no longer, then it has already been stolen (e.g. GtkWindow), // and we need to add one here if (!instance && g_type_is_a(gtype, G_TYPE_INITIALLY_UNOWNED)) g_object_ref_sink(gobject_); // mark this as associated wrapper object as retrieved by .instance() g_object_set_qdata_full(obj, object_data_quark(), this, destroy_notify); } // C++ side construction // always (try to) register type and create instance template void register_setup(GType parent, const gi::cstring_v klassname, const ClassInitNode *node, const repository::GObject::construct_params ¶ms, const properties &props, const signals &sigs, custom_inits_t custom_inits, gpointer instance = nullptr, std::nullptr_t = nullptr) { // sort-of internal prefixed klassname auto custom_name = std::string(CLASS_PREFIX) + canonical_name(klassname); auto gtype = register_type(parent, custom_name, node, props, sigs, custom_inits); setup_instance(gtype, params, instance); } template static GObject *instance_constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { // chain up, but we can skip other parent variations of this function auto gtype = SubClass::baseclass_type::get_type_(); auto klass = G_OBJECT_CLASS(g_type_class_peek(gtype)); auto instance = klass->constructor(type, n_construct_properties, construct_properties); g_assert(instance); // there should be no C++ side yet // as we chain up past any possible C++ parent g_assert(!ObjectClass::instance(instance)); // handle C++ setup auto floating = g_object_is_floating(instance); // avoid inadvertent sink InitData id{0}; id.instance = gi::wrap((GObject *)g_object_ref(instance), transfer_full); #if GI_CONFIG_EXCEPTIONS try { #endif auto self = new SubClass(id); // sanity check on ref if (floating && !g_object_is_floating(instance)) { g_warning("%s constructor sinks instance", typeid(SubClass).name()); // try to unsink if it seems safe and applicable auto obj = (GObject *)instance; // theoretically not MT safe, but if == 1, only 1 thread should be // involved if (obj->ref_count == 1) { g_warning("re-floating instance"); g_object_force_floating(obj); } } (void)self; // self->setup is essentially run below at bottom of constructor chain) // this will then assign ownership of self to instance g_assert(self->gobj_() == (gpointer)instance); #if GI_CONFIG_EXCEPTIONS } catch (const std::exception &exc) { // bad things will happen report_exception(exc); g_critical("constructor failed in instance_init"); } #endif return instance; } // C-side construction // either register type, or finish setup of an instance (triggered on C side_ template void register_setup(GType parent, const gi::cstring_v klassname, const ClassInitNode *node, const repository::GObject::construct_params ¶ms, const properties &props, const signals &sigs, custom_inits_t custom_inits, gpointer instance = nullptr, const InitData *id = {}) { g_assert(id); g_assert(!instance); // no real instance, so used for type registration purpose if (!id->instance) { // these parts are not applicable (void)instance; (void)params; g_assert(!instance); g_assert(params.empty()); // we have our own custom init, which creates the cpp object // so the latter's constructor serves as custom init // custom_inits.custom_init = instance_init; custom_inits.constructor = instance_constructor; // there is no gobject_ instance // make sure to track type for subsequent signal and property registration gtype_ = register_type(parent, klassname, node, props, sigs, custom_inits); g_assert(gtype_); } else { // so this is a new instance as created by instance_init // ensure instance association here at the end of constructor chain // so it is that way for the sequel of the subclass constructor // (and redundant signal and property registration) instance = id->instance.gobj_(); setup_instance(G_OBJECT_TYPE(instance), {}, instance); } } static void destroy_notify(gpointer data) { ObjectClass *impl = (ObjectClass *)data; // sever link with object instance impl->gobject_ = nullptr; delete impl; } protected: template static custom_inits_t make_custom_inits() { return {GClassInitFunc(custom_class_init::value), GInstanceInitFunc(custom_instance_init::value)}; } ObjectClass(GType parent, const gi::cstring_v klassname, const ClassInitNode &node, const repository::GObject::construct_params ¶ms, const properties &props) { register_setup( parent, klassname, &node, params, props, {}, {}, nullptr, nullptr); } template ObjectClass(const SubClass *, GType parent, const gi::cstring_v klassname, const ClassInitNode &node, const repository::GObject::construct_params ¶ms, const properties &props, gpointer instance, InitData id) { register_setup(parent, klassname, &node, params, props, {}, make_custom_inits(), instance, id); } template ObjectClass(const SubClass *, const gi::cstring_v klassname, GType base, const ObjectClass::ClassInitNode &node, const interface_inits_t &itfs, const properties &props, const signals &sigs) { this->itfs = itfs; auto id = InitData{0}; register_setup(base, klassname, &node, {}, props, sigs, make_custom_inits(), nullptr, &id); } ~ObjectClass() { // object destruction should typically be initiated from the // associated object instance based on refcount, // and so pass through destroy_notify, in which case no more gobject_ // but it could come here first for a stack based custom object or alike if (gobject_) { // corresponding object should not have outstanding refs out there // not good otherwise, and why it should not be destructed this way if (gobject_->ref_count != 1) g_error("destroying object with outstanding object refs"); // NOTE the unref might still trigger vmethod calls, // but destruction already happened down to this level, // so derived cast and vmethod call no longer possible // so sever link anyway to make that clear g_object_steal_qdata(gobject_, object_data_quark()); g_object_unref(gobject_); } } public: ObjectClass(const std::type_info &ti, const repository::GObject::construct_params ¶ms = {}, const properties &props = {}) { register_setup(instance_type::get_type_(), ti.name(), nullptr, params, props, {}, {}, nullptr, nullptr); } template ObjectClass(const SubClass *, const repository::GObject::construct_params ¶ms = {}, const properties &props = {}, gpointer instance = nullptr, const gi::cstring_v klassname = nullptr, InitData id = nullptr) { const auto &ti = typeid(SubClass); register_setup(instance_type::get_type_(), klass_type_name(ti, klassname), nullptr, params, props, {}, make_custom_inits(), instance, id); } operator Object() { return gi::wrap(gobject_, transfer_none); } GType gobj_klass_type() { // there should almost always be an object instance // except during initial registration of c-first style return gobject_ ? G_OBJECT_TYPE(gobject_) : gtype_; } GObjectClass *gobj_klass() { return (GObjectClass *)g_type_class_peek(gobj_klass_type()); } static ObjectClass *instance(GObject *gobject) { return (ObjectClass *)g_object_get_qdata(gobject, object_data_quark()); } private: template friend GType register_type(); template static GType register_type_() { // check if the class has defined a get_type_() // rather than nifty compile-time checks, simply use runtme auto btype = T::baseclass_type::get_type_(); auto gtype = T::get_type_(); if (gtype == btype) { // so nothing happened, use constructor to register // instantiate dummy instance that registers type gtype = (T{InitData{0}}).gobj_klass_type(); } return gtype; } }; // interfaces need to go left; constructors need to run first (to request // interface) class constructor deals with class_init last template class ClassTemplate : public ClassDef, public Interfaces..., public BaseClass { typedef typename ClassDef::instance_type::BaseObjectType *c_type; typedef typename ClassDef::class_type class_type_t; typedef typename ClassDef::instance_type instance_type_t; // make private using ClassDef::class_init; protected: class_type_t *get_struct_() { return (class_type_t *)g_type_class_peek(this->base_gtype()); } c_type gobj_() { return (c_type)this->gobject_; } // constructor to be used by custom subclass [[deprecated]] ClassTemplate(const std::type_info &ti, const repository::GObject::construct_params ¶ms = {}, const properties &props = {}) : Interfaces(instance_type_t::get_type_())..., BaseClass(instance_type_t::get_type_(), ti.name(), {&ClassDef::class_init, nullptr, nullptr}, params, props) {} // constructor for inner inheritance chain [[deprecated]] ClassTemplate(GType base, const gi::cstring_v klassname, const ObjectClass::ClassInitNode &node, const repository::GObject::construct_params ¶ms, const properties &props) : Interfaces(instance_type_t::get_type_())..., BaseClass(base, klassname, {&ClassDef::class_init, nullptr, &node}, params, props) {} // as above, new style // constructor to be used by custom subclass template ClassTemplate(const SubClass *sub, const repository::GObject::construct_params ¶ms = {}, const properties &props = {}, gpointer instance = nullptr, const gi::cstring_v klassname = nullptr, InitData id = nullptr) : Interfaces(instance_type_t::get_type_(), gpointer(make_type_init_data()))..., BaseClass(sub, instance_type_t::get_type_(), klass_type_name(typeid(SubClass), klassname), {&ClassDef::class_init, make_type_init_data(), nullptr}, params, props, instance, id) {} // constructor for inner inheritance chain template ClassTemplate(const SubClass *sub, GType base, const gi::cstring_v klassname, const ObjectClass::ClassInitNode &node, const repository::GObject::construct_params ¶ms, const properties &props, gpointer instance = nullptr, InitData id = nullptr) : Interfaces(instance_type_t::get_type_(), gpointer(make_type_init_data()))..., BaseClass(sub, base, klassname, {&ClassDef::class_init, make_type_init_data(), &node}, params, props, instance, id) {} // constructor used in inner inheritance chain for registration collection template ClassTemplate(const SubClass *sub, const gi::cstring_v klassname, GType base, const ObjectClass::ClassInitNode &node, const typename ClassTemplate::interface_inits_t &itfs, const properties &props, const signals &sigs) : Interfaces(instance_type_t::get_type_(), gpointer(make_type_init_data()))..., BaseClass(sub, klassname, base, {&ClassDef::class_init, make_type_init_data(), &node}, itfs, props, sigs) {} public: class_type_t *gobj_klass() { return (class_type_t *)BaseClass::gobj_klass(); } // repeat to disambiguate typedef typename ClassDef::instance_type instance_type; // access to regular object side instance_type_t object_() { auto obj = this->gobject_ ? g_object_ref(this->gobject_) : nullptr; return gi::wrap( (typename instance_type_t::BaseObjectType *)(obj), gi::transfer_full); } }; // NOTE as the impl and regular object side are both inherited here, // ambiguity might not only result from inheriting multiple interfaces, // but also between either of these sides // as such, no operator cast is added on the impl side // instead, use the object_() member to pass to/through regular side template class ObjectImpl : public ObjectT, public ClassT { public: typedef typename ClassT::instance_type baseclass_type; using ObjectT::gobj_; protected: [[deprecated]] ObjectImpl(const std::type_info &ti, const repository::GObject::construct_params ¶ms = repository::GObject::construct_params{}, const properties &props = properties{}) : ClassT(ti, params, props) { // link object ptrs (untracked by ObjectBase) this->data_ = this->gobject_; } // NOTE only 1 gtype will be registered, // so all (subclass) constructors should specify consistent/same data template ObjectImpl(const SubClass *sub, const repository::GObject::construct_params ¶ms = repository::GObject::construct_params{}, const properties &props = properties{}) : ClassT(sub, params, props) { // link object ptrs (untracked by ObjectBase) this->data_ = this->gobject_; } // special advanced case (for internal/override use) by custom subclass // where constructed instance is associated with provided object instance // (rather than the latter created as part of construction, as usual) // if klassname KlassName is specified, // registered typename is GIOBJECT__KlassName // CAUTION the approach below is more likely applicable template ObjectImpl(ObjectT instance, const SubClass *sub, const gi::cstring_v klassname = nullptr, const repository::GObject::construct_params ¶ms = repository::GObject::construct_params{}, const properties &props = properties{}) : ClassT(sub, params, props, instance.gobj_(), klassname) { // link object ptrs (untracked by ObjectBase) this->data_ = this->gobject_; } // this will either; // + register an object GType with an instance_init that // new()'s a corresponding cpp object (and associates suitably) // so, it can be safely created based on GType (e.g. by some C-factory) // (InitData is essentially empty in this case, // and the cpp ObjectImpl instance is a transient dummy) // + construct instance invoked as part of the aforementioned new() // (InitData then holds C object instance) // registered type is klassname (as-is) template ObjectImpl(const SubClass *sub, const ObjectClass::InitData &id, const gi::cstring_v klassname = {}, const properties &props = properties{}) : ClassT(sub, {}, props, nullptr, klassname, &id) { // should have a name if this is used to register a type g_return_if_fail(id || !klassname.empty()); // link object ptrs (untracked by ObjectBase) this->data_ = this->gobject_; } ~ObjectImpl() { // disconnect (avoid ObjectBase management) this->data_ = nullptr; } // registers a type (instead of using that part of the constructor above) // if parent is 0, then it defaults to the type of the immediate parent // (so it should only be specified if subclass'ing a subclass) // itfs: use {I::interface_init_data(), ...} for *immediate* parents I, // *not* parent interfaces of parents // props, signals: use succinct list-initialization (as used elsewhere) template static GType register_type_(const gi::cstring_v klassname, GType parent, const typename ClassT::interface_inits_t &itfs, const properties_init &props, const signals_init &sigs) { // collect all data using constructor chain and register type // MT-safe according to C++11 specs static GType gtype = ObjectImpl((SubClass *)nullptr, klassname, parent, itfs, props, sigs) .gobj_klass_type(); return gtype; } private: // circumvent protected constructors/destructors template ObjectImpl(const SubClass *sub, const gi::cstring_v klassname, GType parent, const typename ClassT::interface_inits_t &itfs, const properties_init &props, const signals_init &sigs) : ClassT(sub, klassname, parent ? parent : baseclass_type::get_type_(), {nullptr, nullptr, nullptr}, itfs, props, sigs) {} }; // wrapper helper to call virtual method // used in implementation, so we can casually use types in default argument template::value, T, void>::type>::type> struct method_wrapper; template struct method_wrapper, CR(CArgs...)> { private: typedef R (C::*member_type)(Args...); struct caller_data { C *this_; const member_type m; }; static R caller(Args &&...args, void *user_data) { auto d = (caller_data *)user_data; return ((d->this_)->*(d->m))(std::forward(args)...); } public: template static CR wrapper( typename traits::ctype::type p, CArgs... args) { ObjectClass *oc = ObjectClass::instance((GObject *)p); C *c = dynamic_cast(oc); if (!oc) { // connection already severed by heap destruction // use refptr instead of stack allocation if this is a problem g_error("missing object"); } else if (!c) { // on our way to crash anyway g_error("wrong object type"); } caller_data d{c, m}; return transform_caller, CR(CArgs...)>::wrapper(args..., caller, &d); } }; // simplified special case for plain/raw fallback with no wrapping/transfer // (pick std::nullptr_t for specialization to represent absence of transfer) template struct method_wrapper { private: typedef R (C::*member_type)(Args...); public: template static R wrapper( typename traits::ctype::type p, Args... args) { ObjectClass *oc = ObjectClass::instance((GObject *)p); C *c = dynamic_cast(oc); if (!oc) { // connection already severed by heap destruction // use refptr instead of stack allocation if this is a problem g_error("missing object"); } else if (!c) { // on our way to crash anyway g_error("wrong object type"); } return ((c)->*(m))(std::forward(args)...); } }; //// property handling //// class PropertyBase { typedef PropertyBase self_type; // subclass handles this virtual void set_property(const GValue *value) = 0; virtual void get_property(GValue *value) = 0; static GQuark get_instance_quark(guint prop_id) { auto str = std::string("GI__") + std::to_string((unsigned long)(&class_init)) + '_' + std::to_string(prop_id); return g_quark_from_string(str.c_str()); } static PropertyBase *get_instance( GObject *object, GParamSpec *pspec, guint prop_id) { auto impl = ObjectClass::instance(object); auto poffset = g_param_spec_get_qdata(pspec, get_instance_quark(prop_id)); return poffset ? (PropertyBase *)((char *)impl + GPOINTER_TO_INT(poffset)) : nullptr; } static void get_property( GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { auto prop = get_instance(object, pspec, prop_id); if (prop) { prop->get_property(value); } else { G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } static void set_property( GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { auto prop = get_instance(object, pspec, prop_id); if (prop) { prop->set_property(value); } else { G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } protected: static GQuark get_prop_quark(const char *name) { auto str = std::string("GI__") + name; return g_quark_from_string(str.c_str()); } public: static void install_property(ObjectClass *impl, GObjectClass *klass, self_type *self, ParamSpec pspec, const gi::cstring_v name = "") { g_return_if_fail(impl); g_return_if_fail(klass); // need at least 1 parameter g_return_if_fail(pspec || name.size()); // identify any property tracked by this PropertyBase code // (may or may not be unique process-wise) static guint prop_id; auto pname = name; if (pspec) { // mind transfer full g_object_class_install_property(klass, ++prop_id, pspec.gobj_copy_()); // normalize name pname = pspec.name_(); } else { g_object_class_override_property(klass, ++prop_id, name.c_str()); } // mark property installed on this klass/type (by whatever code path) // sadly, in case of overridden properties, // glib does not expose sufficient info on whether it is already further up // or the override that we may be trying to add to this/our class // so, setup some parallel tracking using qdata auto gtype = G_OBJECT_CLASS_TYPE(klass); g_type_set_qdata( gtype, get_prop_quark(pname.c_str()), GINT_TO_POINTER(prop_id)); // self may be absent if only registering type if (self) { install_property_offset(impl, klass, self, pname, prop_id); } } static void install_property_offset(ObjectClass *impl, GObjectClass *klass, self_type *self, const gi::cstring_v name, guint prop_id) { g_return_if_fail(self); // add metadata to pspec to retrieve upon set/get // an overridden one will be passed to get/set // (and returned by find as well) auto gpspec = g_object_class_find_property(klass, name.c_str()); g_assert(gpspec); auto quark = get_instance_quark(prop_id); // NOTE no race/issue if multiple threads set this (to same value) // (internal locks are used by glib) if (!g_param_spec_get_qdata(gpspec, quark)) { auto offset = ((char *)self - (char *)impl); g_param_spec_set_qdata(gpspec, quark, GINT_TO_POINTER(offset)); } } virtual ~PropertyBase() {} static void class_init( ObjectClass *impl, gpointer g_class, gpointer class_data) { auto klass = (GObjectClass *)g_class; auto props = (properties *)class_data; klass->get_property = get_property; klass->set_property = set_property; // need to add override properties at this point // (before creation of any instance or class struct) for (auto &&e : *props) install_property(impl, klass, e.second.first, e.second.second, e.first); } }; inline void class_init_props_sigs( ObjectClass *impl, gpointer g_class, gpointer props, gpointer _sigs) { g_return_if_fail(g_class); if (props) PropertyBase::class_init(impl, g_class, props); // handle signals right here if (_sigs) { auto sigs = (signals *)_sigs; for (auto &sig : *sigs) { auto gtype = G_OBJECT_CLASS_TYPE(g_class); g_signal_newv(sig.name.c_str(), gtype, (GSignalFlags)sig.flags, nullptr, nullptr, nullptr, nullptr, sig.ret_type, sig.types.size(), sig.types.data()); } } } template class property : protected property_proxy, public PropertyBase { T val_; void add_property(ObjectClass *impl, gi::cstring_v name) { auto pspec = Object::find_property(impl->gobj_klass_type(), name); // could have been defined already upon prior object creation if (pspec) { this->pspec_ = pspec; // normalize name name = pspec.name_(); // could be defined by a superclass // or already added to this class by prior instance auto gtype = G_OBJECT_CLASS_TYPE(impl->gobj_klass()); if (auto pd = g_type_get_qdata(gtype, get_prop_quark(name.c_str()))) { install_property_offset( impl, impl->gobj_klass(), this, name, GPOINTER_TO_INT(pd)); } else { install_property(impl, impl->gobj_klass(), this, nullptr, name); } } else { pspec = this->pspec_; install_property(impl, impl->gobj_klass(), this, pspec); } // set value to default param value Value value; value.init(); g_param_value_set_default(pspec.gobj_(), &value); val_ = detail::get_value(&value); // avoid circular ref loop if (this->object_) g_object_unref(this->object_.gobj_()); } protected: void set_property(const GValue *value) override { val_ = detail::get_value(value); } void get_property(GValue *value) override { detail::set_value(value, val_); } public: template::type * = nullptr> property(ObjectClass *impl, Args &&...args) : property_proxy( (Object)(*impl), ParamSpec::new_(std::forward(args)...)) { add_property(impl, this->pspec_.get_name()); } property(ObjectClass *impl, const gi::cstring_v name) : property_proxy((Object)(*impl), ParamSpec()) { add_property(impl, name); } ~property() { // clear link this->object_.release_(); } void notify() { g_object_notify_by_pspec(this->object_.gobj_(), this->pspec_.gobj_()); } PropertyBase &operator=(T value) { val_ = value; notify(); return *this; } void set_value(T value, bool _notify = true) { val_ = value; if (_notify) notify(); } T get_value() const { return val_; } operator T() const { return val_; } property_proxy get_proxy() const { return *this; } }; template class property_read : public property { public: template property_read(ObjectClass *impl, Args &&...args) : property( impl, std::forward(args)..., ParamFlags(G_PARAM_READABLE)) {} property_proxy_read get_proxy() const { return {this->object_, this->pspec_}; } }; template class property_write : public property { public: template property_write(ObjectClass *impl, Args &&...args) : property( impl, std::forward(args)..., ParamFlags(G_PARAM_WRITABLE)) {} property_proxy_write get_proxy() const { return {this->object_, this->pspec_}; } }; //// signal handling //// template class signal : public signal_proxy { typedef signal_proxy super; static guint new_(const gi::cstring_v name, GType itype, SignalFlags flags) { // collect array of types GType types[] = {traits::gtype::get_type()...}; const int nparams = sizeof...(Args); const GType ret_type = traits::gtype::get_type(); // TODO accumulator return g_signal_newv(name.c_str(), itype, (GSignalFlags)flags, nullptr, nullptr, nullptr, nullptr, ret_type, nparams, types); } public: signal(ObjectClass *owner, const gi::cstring_v name, SignalFlags flags = (SignalFlags)0) : super(*owner, name) { const GType itype = owner->gobj_klass_type(); if (!g_signal_lookup(name.c_str(), itype)) new_(name, itype, flags); // sneak away ref to avoid ref loop if (this->object_) g_object_unref(this->object_.gobj_()); } ~signal() { // clear link this->object_.release_(); } }; template struct ObjectDeleter { void operator()(T *ob) { if (ob->gobj_()) g_object_unref(ob->gobj_()); } }; template class ref_ptr : public std::unique_ptr> { typedef std::unique_ptr> super; typedef typename T::baseclass_type baseclass_type; public: ref_ptr(T *ptr = nullptr, bool own = true) : super(ptr) { if (ptr && !own) g_object_ref(ptr->gobj_()); } ref_ptr(ref_ptr &&other) = default; ref_ptr(const ref_ptr &other) : super(nullptr) { if (other) g_object_ref(other->gobj_()); this->reset(other.get()); } ref_ptr &operator=(ref_ptr &&other) = default; ref_ptr &operator=(const ref_ptr &other) { if (other && &other != this) g_object_ref(other->gobj_()); this->reset(other.get()); return *this; } operator baseclass_type() { return *this->get(); } }; template ref_ptr make_ref_tagged(construct_cpp_t, Args &&...args) { // move ownership of ref acquired during creation return ref_ptr(new T(std::forward(args)...)); } template::type * = nullptr> ref_ptr ref_ptr_cast(Object ob) { if (ob) { ObjectClass *instance = ObjectClass::instance(ob.gobj_()); if (instance) { auto obj = dynamic_cast(instance); if (obj) { // arrange to obtain an extra ref return ref_ptr(obj, false); } } } return nullptr; } // similar to a typical glib xyz_get_type // as both register the type (once) and return it (many times) template GType register_type() { // C++11 standard specifies this should be MT-safe and occur only once // as such similar to the typical g_once in xyz_get_type static GType gtype = ObjectClass::register_type_(); return gtype; } template ref_ptr make_ref_tagged(construct_c_t, Args &&...args) { auto gtype = register_type(); auto obj = Object::new_<::GObject *>(gtype, std::forward(args)...); return ref_ptr_cast(obj); } // Construct can be specified explicitly; // construct_c_t; also selected in auto case if supported, // Args specify construct properties (see also GObject::new_) // construct_cpp_t; Args are forwarded to any applicable constructor template ref_ptr make_ref(Args &&...args) { using tag_t = typename std::conditional< std::is_same::value, typename std::conditional< std::is_constructible::value, construct_c_t, construct_cpp_t>::type, Construct>::type; return make_ref_tagged(tag_t(), std::forward(args)...); } } // namespace detail // TODO impl namespace ?? using detail::property; using detail::property_read; using detail::property_write; using detail::signal; using detail::make_ref; using detail::ref_ptr; using detail::ref_ptr_cast; using detail::register_type; namespace repository { namespace GObject { namespace impl { // bring into namespaces as in code generation namespace internal { using ObjectClass = detail::ObjectClass; } // namespace internal using ObjectImpl = detail::ObjectImpl; } // namespace impl } // namespace GObject } // namespace repository } // namespace gi #endif // GI_OBJECTCLASS_HPP