Search code examples
ccompiler-errorsmacrosc-preprocessorgobject

GObject Compilation Error in Macro Expansion While Defining Class


Problem

Using GObject and C, I am trying to create a subclass of the class GObject named "Bar" in module "Foo". However, the macro "G_DECLARE_FINAL_TYPE" (defined in gobject/gtype.h) expands incorrectly. Instead of expanding into FOO_BAR, it expands into nothing. So instead of expanding into:

... FooBar * FOO_BAR (gpointer ptr) { ...

It instead expands into:

... FooBar * (gpointer ptr) { ...

Error Message

I expect it to compile but instead gcc gives a syntax error:

In file included from /usr/include/glib-2.0/gobject/gobject.h:24,
                 from /usr/include/glib-2.0/gobject/gbinding.h:29,
                 from /usr/include/glib-2.0/glib-object.h:22,
                 from foo-bar.h:4,
                 from foo-bar.c:1:
/usr/include/glib-2.0/gobject/gtype.h:1407:77: error: expected ‘)’ before ‘ptr’
 1407 | USED static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) {                       \
      |                                                                  ^~~

 foo-bar.h:10:1: note: in expansion of macro ‘G_DECLARE_FINAL_TYPE’
   10 | G_DECLARE_FINAL_TYPE (FooBar, foo_bar, FOO, BAR, GObject)
      | ^~~~~~~~~~~~~~~~~~~~

Definition of Macro

This definition comes from the glib 2.66.7 library. I did not define it myself. I include it for the sake of completeness.

#define G_DECLARE_FINAL_TYPE(ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName)  \
  GType module_obj_name##_get_type (void);                                                  \
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS                                                          \
  typedef struct _##ModuleObjName ModuleObjName;                                            \ 
  typedef struct { ParentName##Class parent_class; } ModuleObjName##Class;                  \
                                                                                            \
  _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName)                                  \
  G_DEFINE_AUTOPTR_CLEANUP_FUNC (ModuleObjName##Class, g_type_class_unref)                  \
                                                                                            \
  G_GNUC_UNUSED static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) {          \
    return G_TYPE_CHECK_INSTANCE_CAST (ptr, module_obj_name##_get_type (), ModuleObjName); }\
  G_GNUC_UNUSED static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) {              \
    return G_TYPE_CHECK_INSTANCE_TYPE (ptr, module_obj_name##_get_type ()); }               \
  G_GNUC_END_IGNORE_DEPRECATIONS

Partial Pre-Processor Output

I included the definition that contains the syntax error and formatted it for readability:

__attribute__((__unused__)) static inline FooBar * (gpointer ptr) {
  return (((FooBar*) g_type_check_instance_cast ((GTypeInstance*) (ptr), (foo_bar_get_type ()))));
}

What's odd is that the definition following does get pre-processed correctly:

__attribute__((__unused__)) static inline gboolean FOO_IS_BAR (gpointer ptr) {
  return ((__extension__ ({
    GTypeInstance *__inst = (GTypeInstance*) (ptr);
    GType __t = (foo_bar_get_type ());
    gboolean __r;
    if (!__inst) __r = (0);
    else if (__inst->g_class && __inst->g_class->g_type == __t) __r = (!(0));
    else __r = g_type_check_instance_is_a (__inst, __t); __r; }))); 
}

Full Minimal Example

I have included a minimal example that causes my error below.

foo-bar.h

#ifndef FOO_BAR
#define FOO_BAR

#include <glib-object.h>

G_BEGIN_DECLS

#define FOO_TYPE_BAR (foo_bar_get_type ())

G_DECLARE_FINAL_TYPE (FooBar, foo_bar, FOO, BAR, GObject)

G_END_DECLS

#endif /* FOO_BAR */

foo-bar.c

#include "foo-bar.h"

struct _FooBar
{
    GObject parent_instance;
};

G_DEFINE_TYPE (FooBar, foo_bar, G_TYPE_OBJECT);

static void foo_bar_class_init (FooBarClass *klass)
{

}

static void foo_bar_init (FooBar *app)
{

}

makefile

foo-bar.o: foo-bar.c
    gcc -c foo-bar.c $(shell pkg-config --cflags gobject-2.0) -o foo-bar.o
# included to debug the macro
foo-bar.pp: foo-bar.c
    gcc -E foo-bar.c $(shell pkg-config --cflags gobject-2.0) -o foo-bar.pp

Versions

  • glib 2.66.7
  • gcc 10.2.1
  • Linux (Fedora 33)

What I have tried

I have studied glib's documentation on how to create a new class and as far as I can tell I am doing it correctly. I have also looked up how token pasting and macro expansion work, and also tried googling my error. It must be something obvious, but I cannot figure out what. I also tried declaring a derivable class with private data, but while finding a minimal example I changed it to a final class without private data and still have the same error. I read about the ## operator here and still cannot tell what is going wrong.

Related Stack Overflow Questions

Finally, I have looked at the following related stackoverflow posts:

My error is that concatenated tokens are not being emitted by the pre-processor. In this post the user defines their own macro and asks why one invocation works and the other doesn't. I am using a library and not my own macro.

This question is asking about why a macro expands differently in unicode mode versus ascii mode. I am not dealing with unicode so it is not relevant.

I am not using C++, only C which does not have syntax for generics, so this question does not apply to me.

Since I am not using XCode this question does not solve my problem.

I understand how macro expansion is supposed to work, so this question does tell me anything I don't already know.

I am not defining any new macros and do not have and problems with namespace collision, unlike this question.

In this and this question the user is not aware of the concatenation operator in macros. I am, and my problem is that it does not work the way I expect it too. So saying the operator exists does not help me.

This question is close to my problem, but it is occuring because an argument to a macro was not provided. I am providing all arguments so it does not help me.

This question the user is using the pasting operator when they don't need to.. In my case the pasting operator is necessary.


Solution

  • Well, it was something dumb. Posting this in case anyone runs across this answer.

    The problem is my include guard. It should be FOO_BAR_H since the name of the file is foo-bar.h. However, it is defined as FOO_BAR. When gobject generates the token "FOO_BAR" the pre-processor realizes that it is a macro that has already been defined, but in this case the definition is empty. Nevertheless, it replaces the macro with its definition.

    The solution is just replacing "FOO_BAR" with "FOO_BAR_H" in the include guard.