I’m developing a glib-/gobject-/gio-based application written in C.
Sadly, the documentation is not very helpful when it comes to the usage of public functions, especially in more complicated setups with inheritance.
Originally, I expected that virtual public methods from a derivable class would be callable simply using obj->method()
, but that doesn’t seem to be the case.
Instead, the #gtk
IRC channel suggested that the canonical way to do this is to is to create wrapper functions and setting the parent’s vfunc pointer to the desired… well… wrapper function, really.
To this effect, and also for demonstration purposes, I’ve created a simple libtest project.
(Sorry, I would have linked all mentioned files, but Discourse won’t let new users post more than two links in one post. For reference, though, use the the permalink to the file listing at the point I’m referring to.)
Crucially, libtest-abstract.h
declares an abstract base class and a wrapper function called lib_test_abstract_foo
. The actual implementation in libtest-abstract.c
is simple as well: test if the pointer passed in is of an abstract type and call the virtual public function if it’s not purely virtual. Note that it is purely virtual by default.
Then, libtest-final.h
declares a final class, subclassing LibTestAbstract
and an accessor called lib_test_final_foo
. Things get hairy in libtest-final.c
, though. In order to set the vfunc pointer, the parent object struct within the class definition must be accessed using LIB_TEST_ABSTRACT_CLASS(LibTestAbstractClass*)
and then set the foo pointer… well… what? It can’t be set to lib_test_final_foo
because that would take a LibTestFinal
pointer. Since the parent declares the method as taking a parent-object-typed pointer, that would only lead to a compiler warning. I hence created another (static) wrapper function called lib_test_final_parent_foo
which takes a parent-object-typed pointer, checks if it’s actually a final-object-typed pointer though using LIB_TEST_IS_FINAL
, casts the pointer back to a final-object-typed pointer via LIB_TEST_FINAL
and calls the actual implementation using lib_test_final_real_foo
. The “usual” wrapper function lib_test_final_foo
only checks whether the pointer is final-object-typed and calls the actual implementation.
Using that is easy, as shown in test/test.c
.
Something about this approach feels very wrong, though - especially what has been done in the lib_test_final_parent_foo
function. Is such casting even legal?
The whole point of virtual methods is to specialize them in derived classes, but gobject makes that terribly difficult (or technically impossible, because you’ll only ever have a parent-object-typed pointer). I understand that this is a limitation of C because it doesn’t have introspection, but it still feels odd.
I’ve looked around the glib source code to find any such usage, but there seems to be… none. Instead, a lot of gio code just calls functions explicitly, without wrapping them or making use of the vfuncs (c.f., GSocketAddress
and friends). The only examples I was able to find that actually uses the vfuncs probably are IO-related (buffer-based ones) and gapplication/gapplication-glib. The latter calls vfuncs directly via G_OBJECT_CLASS (..._parent_class)->vfunc (object)
instead of using wrappers, though. Interestingly, object
is also parent-object-typed there (i.e., GObject
).
I would have expected such a basic feature to see widespread usage?