为什么,以及三个vala构造函数是什么?
更具体地说,为什么在从Gtk.Builder文件中使用它时从不调用第3个结构?
答案 0 :(得分:8)
简短的回答:因为那是GObject的工作方式。答案很长很长一点:
C没有对象或构造函数,它有结构和函数。结构非常简单;它们包含字段,就是它。没有方法,构造函数,析构函数等。它们看起来像这样:
typedef struct Foo_ {
int bar;
char* baz;
} Foo;
To"实例化"一个结构,你分配必要的内存(在堆栈或堆上,我将关注堆的其他问题)并设置字段。
即使对于我们简单的结构,这很快就会变得很痛苦,所以你通常会看到函数来帮助分配和释放结构。像这样:
Foo* foo_new (int bar, char* baz) {
Foo* foo = malloc (sizeof (Foo));
/* malloc() can fail. Some libraries would return null, some would
just assume it never does. GLib-based software generally just exits
with an error, which is what we'll do here. */
if (NULL == foo) {
fprintf (stderr, "%s:%d: Unable to allocate room for struct Foo\n",
__FILE__, __LINE__);
exit (EXIT_FAILURE);
}
foo->bar = bar;
foo->baz = (NULL != baz) ? strdup (baz) : NULL;
return foo;
}
void foo_free (Foo* foo) {
if (NULL == foo)
return;
if (NULL != foo->baz)
free (foo->baz);
free (foo);
}
在Vala中,*_new
函数映射到命名构造函数。 Vala绑定可能类似于:
[Compact]
public class Foo {
public Foo ();
public int bar;
public string? baz;
}
这一切都非常简单,但是当你想要"延伸" Foo
并添加新字段? C没有任何语言级支持"扩展"结构。 C程序员通过在子结构中嵌入基础结构来解决这个问题:
typedef struct Qux_ {
struct Foo parent;
int quux;
} Qux;
这是C级别的相当不错的解决方案; Qux结构的第一部分与Foo结构完全相同,所以当我们想要使用Qux
作为Foo
时,我们所要做的就是强制转换:
void qux_set_bar_and_qux (Qux* qux, int bar, int quux) {
((Foo*) qux)->bar = bar;
qux->quux = quux;
}
不幸的是,在创建新实例时它崩溃得非常糟糕。请记住,我们的foo_new
函数在堆上分配sizeof(Foo)
个字节(使用malloc
) - 所有quux
字段都有空间!这意味着我们无法调用foo_new
函数。
如果你正在调用用Vala编写的库,可以解决这个问题:除foo_new
函数外,Vala实际上还会生成foo_construct
函数。所以,给出类似
[Compact]
public class Foo {
public Foo (int bar, string? baz) {
this.bar = bar;
this.baz = baz;
}
}
Vala实际会产生的东西有点像这样:
void foo_construct (Foo* foo, int bar, char* baz) {
foo->bar = bar;
foo->baz = g_strdup (baz);
}
Foo* foo_new (int bar, char* baz) {
Foo* foo = g_malloc (sizeof (Foo));
foo_construct (foo, bar, baz);
return foo;
}
现在,如果Vala中的Qux
类子类Foo
,它可以调用我们的Foo
命名构造函数:
[Compact]
public class Qux : Foo {
public Qux (int quux) {
base (0, "Hello, world");
this.quux = quux;
}
public int quux;
}
由于生成的代码实际上并未调用foo_new
,因此会调用foo_construct
:
Qux* qux_new (int quux) {
Qux* qux = g_malloc (sizeof (Qux));
foo_construct ((Foo*) qux, 0, "Hello, world");
qux->quux = quux;
}
可悲的是,用Vala编写的 not 代码很少遵循这个约定(grep用于随valac分发的VAPI中的&has #construct_function' CCode属性)。
此时你可能会想,"这很痛苦,但为什么不在qux_new"中重新创建foo_new函数的内容。好吧,因为您可能无法访问Foo
结构的内容。写Foo
的人可能不希望你弄乱他们的私人字段,因此他们可以在公共标题中使Foo
成为不完整的类型,并保留完整的定义。
现在,让我们开始谈论GObject properties。我将对细节有所了解,但基本上它允许您注册类型,并包含一些在运行时可用的信息。
使用GObject注册的类可以具有属性。这些在概念上有点类似于C结构中的字段,但该类型提供了用于加载和存储它们的回调,而不是直接让代码存储到地址。这也意味着当你设置一个值以及其他一些方便的东西时,它可以像想象一样发出信号。
GObject中的类初始化相当复杂。我们将在一分钟内讨论这个问题,但首先让我们从想要实例化GObject类的库的角度来看一下。这看起来像这样:
Qux* qux = g_object_new (QUX_TYPE,
"bar", 1729,
"baz", "Hello, world",
"quux", 1701,
NULL);
这可能非常明显:它会创建一个Qux
实例,并设置" bar"财产到1729年," baz" to" Hello,world"和" quux"现在,回到如何实例化类。同样,这是(稍微)一点点简化,但......
首先,分配足够的内存来保存Qux
实例(包括Foo
父类,现在也分配了所有GObject的祖先GObject
类。
接下来,回调设置" bar"," baz"和" qux"属性被调用。
接下来,调用*_constructor
函数。在Vala中,它映射到construct
块。它看起来像这样:
static GObject * foo_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam construct_properties[n_construct_properties]) {
GObjectClass * parent_class = G_OBJECT_CLASS (foo_parent_class);
GObject * obj = parent_class->constructor (type, n_construct_properties, construct_properties);
Foo * self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_FOO, Foo);
/* The code from your construct block goes here */
return obj;
}
请注意,您无法控制此功能的参数。如您所见,每个构造函数都调用其父类的构造函数。我添加了一个注释,其中包含构造块的代码;我们很快就会回到为什么它与命名构造函数分开。
现在,让我们看一下命名构造函数的代码。请记住,绝大多数图书馆都没有*_construct
功能,所以我们想象一下(对于我们的Qux
课程而言):
Qux* qux_new (int bar, int quux) {
Qux* qux = g_object_new (QUX_TYPE,
"bar", bar,
"quux", quux,
NULL);
/* Code from your named constructor goes here. */
}
最后,我们了解为什么在使用GtkBuilder
时不会调用您的命名构造函数:它不会调用qux_new
,而是调用g_object_new
。 Calling qux_new
is an enormous pain在不知道您的图书馆的情况下,显然GtkBuilder
无法了解您的图书馆。
最后,让我们谈谈类构造块。它们基本上完全不同。幸运的是,解释它们的时间差不多:当类型向GObject注册时,它们被调用,当实例化类型的实例时,不是。基本上,它会在第一次实例化时调用,而不会再次调用。一个简单的例子:
public class Foo : GLib.Object {
class construct {
GLib.message ("Hello, world!");
}
construct {
GLib.message ("%d", this.bar);
}
public int bar { get; set; default = 1729; }
}
private static int main (string[] args) {
var foo = new Foo ();
foo = new Foo ();
return 0;
}
将输出
Hello, world!
1729
1729