Search code examples
coopgtkgobject

GObject OOP Syntax


I'm looking for a GObject cheat sheet, how common OOP concepts map to GObject's facilities. Consider for example:

AnyGObject *o;
o = anygobject_new();

Now, what are the conventions for...

  • calling a method
  • calling a method declared by a base class
  • calling a method declared by an interface the class is implementing
  • calling a class method
  • calling a class method declared by a base class
  • calling a virtual method
  • casting to a base class
  • casting to a derived class
  • casting to an interface the class is implementing
  • testing whether an object is of a particular type
  • ...

Clarification: The GObject Reference Manual and GObject HowTo explain at length how to create a new class (class struct, object struct, private struct, various macros, conventions). Taken together these facilities allow for OOP. Yet there seems to be no tutorial on how to use them consistently.


Solution

  • This answer assumes you are working with C. Other (usually object-oriented) languages have special bindings built to make working with GObject seem more natural.

    If you've worked with GTK+, you already have done much of that list.

    GObject methods are not members themselves (there is a vtable of sorts but it's only used for assigning virtual method implementations in a derived class when the class is first created). Instead, all methods in GObject are just plain functions, usually(?) prefixed by a method name prefix, and with the this pointer as the first argument.

    For example, the C++ method

    namespace Lib { // short for Library; to demonstrate GObject's idiomatic naming conventions
        class Foo {
        public:
            void Bar(int z);
        };
    }
    

    would be a plain function in the global namespace declared as

    void lib_foo_bar(LibFoo *foo, int z);
    

    and you would call it directly, just like any other C function.

    Class derivation in GObject occurs by having the full data structure of the parent class as the first member of the derived class's data structure. For various reasons pertaining to rarely-discussed clauses in the C standard (and possibly the System V ABI and implementation of gcc, clang, and even the Microsoft C compilers), this means that a pointer to an object of the derived class is equivalent to a pointer to the parent class!

    So if LibBaz derives from LibFoo, all you would need to say is

    LibFoo *foobaz = (LibFoo *) baz;
    

    and the same applies in reverse:

    LibBaz *bazfoo = (LibBaz *) foo;
    

    (This latter approach is what GTK+ uses for GtkWidget; I don't know if other GObject libraries do the same thing.)

    Idiomatic GObject declarations include a bunch of macros that make the type conversions more terse while at the same time adding runtime type safety checks. Our LibFoo class would have the following macros:

    #define LIB_TYPE_FOO (lib_foo_get_type())
    #define LIB_FOO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), LIB_TYPE_FOO, LibFoo))
    

    With this, we would instead say

    LibFoo *foobaz = LIB_FOO(baz);
    LibBaz *bazfoo = LIB_BAZ(foo);
    

    and if either baz or foo isn't the correct type for the conversion, a warning will be logged to standard error, which you can break at and investigate using a debugger.

    (The lib_foo_get_type() function (and LIB_TYPE_FOO neatness macro) is important: it returns a numeric ID that maps to the type of LibFoo for future reference. If LibFoo doesn't have such a mapping, it will create the mapping, registering the type and creating the virtual method mappings.)

    A similar macro allows type checking:

    #define LIB_IS_FOO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), LIB_TYPE_FOO))
    

    which is a simple expression that can be used in an if statement.

    So what about calling parent class methods? Well, if we put all of the above together, we have the answer:

    lib_foo_parent_method(LIB_FOO(aLibBazInstance), params);
    

    The same applies to virtual methods. Virtual methods are implemented using the GObject approximation to vtables and are transparent to the end programmer. All you will do is

    lib_foo_virtual_method(LIB_FOO(whatever), params);
    

    (If you actually build the derived class itself, the how of virtual methods becomes important.)

    There are no static methods in GObject, because methods aren't tied to a class as closely as they are in real object-oriented languages. Just create a top-level method in your library:

    void lib_something_common(params);
    

    Finally, all of the above applies to interfaces. Interfaces work the exact same way to the end user; they use the same

    void lib_iface_method(LibIface *iface, params);
    

    approach to method calling, the same casting rules, and the same LIB_IFACE() and LIB_IS_IFACE() helper macros.

    Hopefully that helps! Any further explanation would have to tread into explaining how to create a GObject, which I tried to keep outside the scope of this answer for simplicity's sake, but is a useful thing to know anyway.