GObject面向对象编程语法

3
我正在寻找一个 GObject 的速查表,它可以将常见的面向对象编程概念映射到 GObject 的功能中。例如考虑以下内容:
AnyGObject *o;
o = anygobject_new();

现在,关于以下内容的惯例是什么:
  • 调用方法
  • 调用由基类声明的方法
  • 调用由接口实现的方法
  • 调用类方法
  • 调用由基类声明的类方法
  • 调用虚拟方法
  • 转换为基类
  • 转换为派生类
  • 转换为实现该类的接口
  • 测试对象是否属于特定类型
  • ...
说明: GObject参考手册GObject HowTo详细解释了如何创建新类(类结构、对象结构、私有结构、各种宏、惯例)。这些设施共同支持面向对象编程。然而,似乎没有关于如何一致使用它们的教程。

并非所有这些概念都能很好地映射到GLib。它并不是一个完整的OOP工具,而是一个具有一些类似于对象的特性的类型系统。与您期望的不同,它没有真正的继承 - 只有COM-esque接口外观。按照惯例,“方法”只是结构/类的函数成员,需要将目标对象作为第一个参数进行调用,没有特殊的语法或糖来调用它们。object->obj_method(obj,...); 虽然有RTTI,请参见https://developer.gnome.org/gobject/stable/gobject-Type-Information.html - BadZen
我认为你不会在这方面找到太多的文档,但我希望我能被证明是错误的 :) 无论如何,也许这个问题迟早可以变成一个维基。 - meskobalazs
1个回答

5
这个回答假定你正在使用C语言。其他(通常是面向对象的)语言都有特殊的绑定来使与GObject的工作更加自然。
如果你已经在使用GTK+,那么你已经完成了列表上的大部分工作。
GObject方法本身不是成员(虽然有一种虚表,但只用于在派生类被创建时分配虚方法实现)。相反,在GObject中,所有方法都只是普通函数,通常以方法名称前缀为前缀,并将“this”指针作为第一个参数。
例如,C++方法
namespace Lib { // short for Library; to demonstrate GObject's idiomatic naming conventions
    class Foo {
    public:
        void Bar(int z);
    };
}

这将会是一个在全局命名空间中声明为普通函数的函数

void lib_foo_bar(LibFoo *foo, int z);

如果需要调用,你可以像其他C函数一样直接调用。

GObject中的类派生是通过将父类的完整数据结构作为派生类的数据结构的第一个成员来实现的。由于涉及到C标准中很少讨论的条款(可能还包括System V ABI和gcc、clang甚至Microsoft C编译器的实现),这意味着指向派生类对象的指针等同于指向父类的指针!

因此,如果LibBaz派生自LibFoo,那么你只需要说:

LibFoo *foobaz = (LibFoo *) baz;

同样的道理也适用于反过来:
LibBaz *bazfoo = (LibBaz *) foo;

GTK+使用后一种方法来处理GtkWidget;我不知道其他GObject库是否也是这样做。

惯用的GObject声明包括一堆宏,使类型转换更加简洁,同时添加运行时类型安全检查。我们的LibFoo类将具有以下宏:

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

因此,我们应该这样说:

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

如果baz或者foo不是正确的类型,警告将被记录到标准错误中,您可以在断点处进行调试。

(lib_foo_get_type()函数(和LIB_TYPE_FOO宏)非常重要:它返回一个数字ID,该ID映射到LibFoo的类型,以供将来参考。如果LibFoo没有这样的映射,它将创建该映射,注册类型并创建虚拟方法映射。)

类似的宏允许进行类型检查:

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

这是一个简单的表达式,可以在if语句中使用。

那么如何调用父类方法呢?如果我们将上面所有内容综合起来,就有了答案:

lib_foo_parent_method(LIB_FOO(aLibBazInstance), params);

同样适用于虚方法。虚方法是使用GObject近似vtable实现的,并且对最终程序员透明。你需要做的就是:
lib_foo_virtual_method(LIB_FOO(whatever), params);

如果您实际构建了派生类本身,虚拟方法的“如何”变得很重要。

GObject 中没有静态方法,因为方法与真正的面向对象语言中的类没有那么紧密地联系在一起。只需在您的库中创建一个顶层方法:

void lib_something_common(params);

最后,以上所有内容同样适用于接口。对于最终用户而言,接口的使用方式完全相同;它们使用相同的

void lib_iface_method(LibIface *iface, params);

在方法调用方面,使用相同的转换规则以及相同的LIB_IFACE()LIB_IS_IFACE()辅助宏。

希望这有所帮助!任何进一步的解释都需要涉及如何创建GObject,为了简单起见,我尝试将其保持在本答案范围之外,但这也是一个有用的知识。


感谢您提供如此详尽的解释。我希望这段文本能够以某种方式出现在 GObject 手册中。 - edgar.holleis
我在Bugzilla上提交了一项增强请求:https://bugzilla.gnome.org/show_bug.cgi?id=742587 - edgar.holleis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接