Exposing and using public methods in a library-like fashion

,

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?

This is all you need:

static gboolean
lib_test_final_foo (LibTestAbstract *parent) {
  LibTestFinal *self = LIB_TEST_FINAL (parent);
  gboolean ret = FALSE;

  return ret;
}

Because you will install this function in your class_init, it will be called only on instances of that class (or possibly a subclass if you hadn’t made it final).

The _real_foo convention is used for the default implementation in the base class, which doesn’t exist in your example.

This is all you need:

static gboolean
lib_test_final_foo (LibTestAbstract *parent) {

That would work for installing it in the parent structure of the final class, yes. However, having a public vfunc is useless without being able to call it in actual user code (or any other consumers within the same library).

Other users/consumers shouldn’t even need to know that the vfunc is part of the parent’s interface.

Sure, they (consumers) could use code like this for every call, but that looks… just weird and cumbersome:

  /* Assuming obj is a LibTestFinal pointer. */
  LIB_TEST_ABSTRACT (obj)->foo (LIB_TEST_ABSTRACT (obj));


I guess most of my confusion comes from the OOP concept in C++ and other languages. That is, defining a virtual public method in a(n abstract) C++ base class makes it part of the interface. Any other inheriting class will automatically have that function directly callable as well.

I understand that this is not possible in C, but a common pattern to simulate that is something like this:

[in header file]
rettype derived_type_method (Derivedtype *self);

[in implementation file]
rettype derived_type_method (Derivedtype *self) {
  g_return_val_if_fail (...IS_DERIVEDTYPE (self), ...);
  Parenttype *parent = PARENTTYPE (self);
  ParenttypeClass *parent_class = PARENTTYPE_GET_CLASS (parent);
  return (parent_class->method (parent));
}

static rettype derivedtype_parent_method (Parenttype *parent) {
  g_return_val_if_fail (..._IS_DERIVEDTYPE (parent), ...);
  Derivedtype *self = DERIVEDTYPE (parent);

  /* Actual implementation here. */
  return (...);
}

static void derivedtype_class_init (DerivedtypeClass *klass) {
  ParenttypeClass *parent_class = PARENTTYPE_CLASS (klass);

  parent_class->foo = &derivedtype_parent_foo;
}

I’ve also updated the example to reflect this, including removal of the _real_method () part that is not suitable here.

It doesn’t seem to be a usual “pattern” and I wonder why.

Because that’s not how we have ever done things. GObject/C is not C++, and it definitely does not follow C++ OOP patterns.

We typically expose public functions on the abstract type for derived types to call—which makes it possible to bind things in other languages that don’t have access to the class structure, for instance, and provides a modicum of type safety for C callers as well.

I mainly used C++ because it’s the (multi-paradigm, but also OOP) language I’m most familiar with. These concepts are not exclusive to one programming language but shared by a lot of them.

Apologies if I came around condescending - I didn’t mean to be.

Having an abstract base and a final class was the easiest example I could come up with.

Imagine a hierarchy like this:

Abstract → Derivable → Derivable → Derivable → Final.

Using my approach, every Derivable or Final class would need to define public function wrappers for vfuncs of every derivable class it’s based on. While that is easy/trivial in a scenario such as Abstract → Final, the amount of work necessary scales very badly.

To be fair, with my approach, I would have needed to look up and define wrappers for all vfuncs of GObject(Class) in both my abstract and final classes as well (since both are derived from GObject at some point), but didn’t do this.


One thing I totally failed to realize is that such a pattern is already documented - viewer_file_open () is a wrapper-accessor for the open () vfunc, that has no default implementation, while viewer_file_close () is a wrapper-accessor for the close () vfunc, which points to the implementation provided by viewer_file_real_close ().

The only difference to my code is that I have to introduce a layer of indirection to the parent class object, which sounds like a reasonable extension of the documented concept when working with an inheritance chain.

In addition, to answer one of my previous questions: yes, it’s fine to cast an instance or class pointer to a parent-type one, due to both structures always embedding a parent-type structure as their first element.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.