From: Nick Bowler Date: Thu, 6 Feb 2020 05:08:08 +0000 (-0500) Subject: Implement chaining of interface methods. X-Git-Url: https://git.draconx.ca/gitweb/gob-dx.git/commitdiff_plain/1b42784146ab0d40637c80cf6064bafe679a5a0a Implement chaining of interface methods. GObject interface methods are very similar to virtual methods. A class may wish to override some or all of the interface methods implemented in a parent class. When doing so, it can be useful to call the parent class implementation to offload some of the work. For ordinary virtual methods, using the "override" keyword causes a PARENT_HANDLER macro to be defined which can call up to the parent class implementation. Let's do exactly the same thing for interface methods, and expose the parent interface structures (in case the user wishes to call up to different methods). --- diff --git a/NEWS b/NEWS index b1f9e59..bed51af 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ * Improve build system. * Remove NOINSTGOB functionality as it seems pointless. * Add support for dynamic GObject types. + * Improved support for overriding interfaces in subclasses. * New and improved GOB2_CHECK macro. 2.0.20: diff --git a/doc/gob2.1.in b/doc/gob2.1.in index 9037aa2..560dd8a 100644 --- a/doc/gob2.1.in +++ b/doc/gob2.1.in @@ -1065,6 +1065,21 @@ a specific method of the interface, just add \'interface \' before the method definition. The method can, and probably should be, private. .PP +Interface methods behave very similarly to virtual/override methods. +As with override methods, you can use PARENT_HANDLER in the definition of an +interface method to call the implementation of the same interface method in +a parent class. +.PP +The NAME_parent_iface variable may be used to access the complete interface +structure from a parent class, where NAME is the implemented interface (with +colons replaced by underscores). +This enables access to the full parent implementation of an interface. +For example, if a class implements the Gtk:Tree:Model interface then the +implementation of the same interface in the parent class is available via +the Gtk_Tree_Model_parent_iface pointer. +If an interface is not implemented in the parent class, then the corresponding +parent_iface value will be a null pointer. +.PP The following example implements a new object, that implements the Gtk:Tree:Model interface and implements the get_flags method of that interface. Do note that except for standard (GTK+ and glib) specific diff --git a/src/main.c b/src/main.c index 6a0c791..c7171c5 100644 --- a/src/main.c +++ b/src/main.c @@ -954,6 +954,59 @@ add_signal_prots(Method *m) out_printf (out, "}\n\n"); } +static char * +interface_type(const char *if_name) +{ + char *rawtype = remove_sep(if_name); + char *end = "", *typename; + + if (!gtk3_ok) { + /* + * EEEK! evil, we should have some sort of option + * to force this for arbitrary interfaces, since + * some are Class and some are Iface. Glib is shite + * in consistency. + */ + + if (strcmp (rawtype, "GtkEditable") == 0 + || strcmp (rawtype, "GTypePlugin") == 0) + { + end = "Class"; + } else { + /* We'll assume Iface is the standard ending */ + end = "Iface"; + } + } else { + /* GTK3 doesn't need Iface end */ + end = "Interface"; + } + + typename = g_strconcat(rawtype, end, (char *)NULL); + g_free(rawtype); + + return typename; +} + +static void +define_parent_interface_refs(Class *c) +{ + GList *li; + + if (!c->interfaces) + return; + + out_printf(out, "\n/* parent class interface implementations */\n"); + for (li = c->interfaces; li != NULL; li = li->next) { + char *name = replace_sep(li->data, '_'); + char *type = interface_type(li->data); + + out_printf (out, "static %s *%s_parent_iface;\n", type, name); + + g_free(name); + g_free(type); + } +} + static void add_enums(Class *c) { @@ -1003,7 +1056,9 @@ add_enums(Class *c) "static guint object_signals[LAST_SIGNAL] = {0};\n\n"); out_printf(out, "/* pointer to the class of our parent */\n"); - out_printf(out, "static %sClass *parent_class = NULL;\n\n", ptypebase); + out_printf(out, "static %sClass *parent_class = NULL;\n", ptypebase); + define_parent_interface_refs(c); + out_printf(out, "\n"); } static void @@ -1037,7 +1092,7 @@ add_interface_methods (Class *c, const char *interface) } static void -add_interface_inits (Class *c) +add_interface_inits(Class *c) { GList *li; @@ -1047,39 +1102,19 @@ add_interface_inits (Class *c) out_printf(out, "\n"); for (li = c->interfaces; li != NULL; li = li->next) { - const char *interface = li->data; - const char *end; - char *name = replace_sep (interface, '_'); - char *type = remove_sep (interface); + char *name = replace_sep(li->data, '_'); + char *type = interface_type(li->data); - if(!gtk3_ok) - { - /* EEEK! evil, we should have some sort of option - * to force this for arbitrary interfaces, since - * some are Class and some are Iface. Glib is shite - * in consistency. */ - - if (strcmp (type, "GtkEditable") == 0 || - strcmp (type, "GTypePlugin") == 0) - end = "Class"; - else - // We'll assume Iface is the standard ending - end = "Iface"; - } - else - { - /*GTK3 doesn't need Iface end*/ - end="Interface"; - } - - out_printf (out, "\nstatic void\n" - "___%s_init (%s%s *iface)\n" - "{\n", - name, type, end); + out_printf(out, "static void\n" + "___%s_init (%s *iface)\n" + "{\n", name, type); - add_interface_methods (c, interface); + add_interface_methods(c, li->data); - out_printf (out, "}\n\n"); + out_printf(out, "\t%s_parent_iface\n", name); + out_printf(out, for_cpp ? "\t\t= (%s *)" : "\t\t= ", type); + out_printf(out, "g_type_interface_peek_parent(iface);\n" + "}\n\n"); g_free (name); g_free (type); @@ -3053,6 +3088,35 @@ get_arg_names_for_macro (Method *m) return g_string_free (gs, FALSE); } +static gboolean method_is_void(Method *m) +{ + return !strcmp(m->mtype->name, "void") && !m->mtype->pointer; +} + +static const char *method_err_retval(Method *m) +{ + if (method_is_void(m)) + return "(void)0"; + if (m->onerror) + return m->onerror; + return "0"; +} + +static void +put_interface_parent_handler(Method *m) +{ + const char *errval = method_err_retval(m); + char *name = replace_sep(m->interface, '_'); + char *args = get_arg_names_for_macro(m); + + out_printf(out, "#define PARENT_HANDLER(%s) (%s_parent_iface \\\n" + "\t? %s_parent_iface->%s(%s) \\\n" + "\t: %s)\n", args, name, name, m->id, args, errval); + + g_free(name); + g_free(args); +} + static void put_method(Method *m) { @@ -3068,6 +3132,7 @@ put_method(Method *m) g_free(doc); } } + switch(m->method) { case REGULAR_METHOD: if(m->line_no > 0) @@ -3078,7 +3143,18 @@ put_method(Method *m) else /* PUBLIC, PROTECTED */ print_method(out, "", "\n", "", " ", "", "\n", m, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE); + + if (m->interface) { + out_addline_outfile(out); + put_interface_parent_handler(m); + } + print_method_body(m, TRUE, TRUE); + + if (m->interface) { + out_printf(out, "#undef PARENT_HANDLER\n"); + } + /* the outfile line was added above */ break; case SIGNAL_FIRST_METHOD: diff --git a/tests/interface.at b/tests/interface.at index c8e2a9c..61c42bb 100644 --- a/tests/interface.at +++ b/tests/interface.at @@ -65,6 +65,7 @@ class $1 from $3]m4_default_nblank([ (interface Test:Fooable) { interface Test:Fooable private int foo(G:Object *go) + onerror 88 { $5 abort(); @@ -291,3 +292,97 @@ Test:B foo called ]) AT_CLEANUP + +AT_SETUP([interface method chaining]) +AT_KEYWORDS([runtime])dnl + +TEST_FOOABLE_IFACE() +TEST_FOOABLE_IMPL([Test:A], [G:Object], + [puts("Test:A foo called"); return PARENT_HANDLER(go);]) +TEST_FOOABLE_IMPL([Test:B], [Test:A], + [puts("Test:B foo called"); return PARENT_HANDLER(go);]) +TEST_FOOABLE_IMPL([Test:C], [Test:B], + [puts("Test:C foo called"); return PARENT_HANDLER(go);]) + +AT_DATA([main.c], +[[#include +#include "test-fooable.h" +#include "test-a.h" +#include "test-b.h" +#include "test-c.h" + +int main(void) +{ + int rc; + + rc = test_foo(g_object_new(TEST_TYPE_C, NULL)); + printf("%d\n", rc); + if (rc < 0) + return EXIT_FAILURE; + + return 0; +} +]]) + +TEST_COMPILE_GOBJECT([main.c], [0], [], [ignore]) + +AT_CHECK([$CC $CFLAGS $LDFLAGS $LIBGOBJECT_LIBS -o main \ + test-a.o test-b.o test-c.o test-fooable.o main.o]) +AT_CHECK([./main], [0], [Test:C foo called +Test:B foo called +Test:A foo called +88 +]) + +AT_CLEANUP + +AT_SETUP([interface method chaining (dynamic)]) +AT_KEYWORDS([runtime])dnl + +TEST_FOOABLE_IFACE() +TEST_FOOABLE_IMPL_DYN([Test:A], [G:Object], + [puts("Test:A foo called"); return PARENT_HANDLER(go);]) +TEST_FOOABLE_IMPL_DYN([Test:B], [Test:A], + [puts("Test:B foo called"); return PARENT_HANDLER(go);]) +TEST_FOOABLE_IMPL_DYN([Test:C], [Test:B], + [puts("Test:C foo called"); return PARENT_HANDLER(go);]) + +AT_DATA([main.c], +[[#include +#include "test-fooable.h" +#include "test-a.h" +#include "test-a-mod.h" +#include "test-b.h" +#include "test-b-mod.h" +#include "test-c.h" +#include "test-c-mod.h" + +int main(void) +{ + int rc; + + g_type_module_use(g_object_new(TEST_TYPE_A_MOD, NULL)); + g_type_module_use(g_object_new(TEST_TYPE_B_MOD, NULL)); + g_type_module_use(g_object_new(TEST_TYPE_C_MOD, NULL)); + + rc = test_foo(g_object_new(TEST_TYPE_C, NULL)); + printf("%d\n", rc); + if (rc < 0) + return EXIT_FAILURE; + + return 0; +} +]]) + +TEST_COMPILE_GOBJECT([main.c], [0], [], [ignore]) + +AT_CHECK([$CC $CFLAGS $LDFLAGS $LIBGOBJECT_LIBS -o main \ + test-a.o test-a-mod.o test-b.o test-b-mod.o test-c.o test-c-mod.o \ + test-fooable.o main.o]) +AT_CHECK([./main], [0], [Test:C foo called +Test:B foo called +Test:A foo called +88 +]) + +AT_CLEANUP