Vala 语言入门教程 / 05 - GObject 类型系统
第 5 章:GObject 类型系统
GObject 是整个 GNOME 技术栈的基石。理解 GObject 类型系统,是精通 Vala 的必经之路。
5.1 GObject 是什么
GObject 是 GLib 库提供的面向对象类型系统,为纯 C 语言带来了:
- 类与继承
- 接口
- 属性(Properties)
- 信号(Signals)
- 引用计数(Reference Counting)
- 类型内省(Introspection)
┌────────────────────────────────────────────┐
│ GObject 类型层次 │
├────────────────────────────────────────────┤
│ GObject (基类) │
│ ↑ │
│ ┌────────┼────────┐ │
│ ↑ ↑ ↑ │
│ GFile GSocket GtkWidget │
│ ↑ ↑ │
│ GIO GtkButton │
│ ↑ │
│ GtkWindow │
└────────────────────────────────────────────┘
Vala 中的每个类最终都继承自 GLib.Object(即 GObject)。编译器会自动生成注册类、定义属性、连接信号等样板代码。
5.2 类型系统基础
5.2.1 GObject 类型层次
void main () {
// 创建对象
var obj = new Object ();
// 查询类型信息
Type type = obj.get_type ();
print ("类型名: %s\n", type.name ());
print ("类型是否抽象: %s\n", type.is_abstract ().to_string ());
print ("类型是否接口: %s\n", type.is_interface ().to_string ());
// 类型检查
var list = new GLib.List<string> ();
Type list_type = list.get_type ();
print ("List 类型名: %s\n", list_type.name ());
// 类型是否是另一个类型的子类
print ("List 继承自 Object: %s\n",
list_type.is_a (typeof (Object)).to_string ());
}
5.2.2 typeof 和类型检查
public class Animal : Object {
public string name { get; set; }
}
public class Dog : Animal {
public void bark () {
print ("汪汪!\n");
}
}
public class Cat : Animal {
public void meow () {
print ("喵喵!\n");
}
}
void main () {
Animal dog = new Dog () { name = "旺财" };
Animal cat = new Cat () { name = "咪咪" };
// 类型检查
print ("dog 是 Dog: %s\n", (dog is Dog).to_string ());
print ("dog 是 Cat: %s\n", (dog is Cat).to_string ());
print ("dog 是 Animal: %s\n", (dog is Animal).to_string ());
// 安全类型转换
if (dog is Dog) {
Dog d = (Dog) dog;
d.bark ();
}
// typeof 运算符
Type t = typeof (Dog);
print ("Dog 类型: %s\n", t.name ());
// 动态创建对象
Type type = Type.from_name ("Dog");
if (type != 0) {
var obj = Object.new (type);
print ("动态创建: %s\n", obj.get_type ().name ());
}
}
5.2.3 类型注册
Vala 编译器自动完成类型注册,但了解底层机制很重要:
// Vala 编译器生成的 C 代码(概念示例)
G_DEFINE_TYPE(MyApp, my_app, G_TYPE_OBJECT)
static void my_app_class_init(MyAppClass *klass) {
// 注册属性
g_object_class_install_property(object_class, PROP_NAME,
g_param_spec_string("name", "Name", "名字", "",
G_PARAM_READWRITE));
// 注册信号
signals[SIG_STARTED] = g_signal_new("started",
G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}
static void my_app_init(MyApp *self) {
// 初始化实例
}
// 等价的 Vala 代码
public class MyApp : Object {
public string name { get; set; }
public signal void started ();
}
💡 一个 Vala 属性声明,等价于约 20 行 C 代码。
5.3 属性系统详解
5.3.1 属性规范
public class Config : Object {
// 可读写属性
public string host { get; set; default = "localhost"; }
// 只读属性
public int64 start_time { get; default = 0; }
// 构造时设置,之后只读
public string app_name { get; construct; }
// 自定义实现
private string _password = "";
public string password {
get { return _password; }
set {
_password = value;
// 触发变化通知
notify_property ("password");
}
}
// 只在包内可写
public string debug_info { get; internal set; }
// owned getter(返回所有权,引用计数 +1)
public string data {
owned get { return "some data"; }
}
construct {
start_time = GLib.get_real_time ();
}
public Config (string app_name) {
Object (app_name: app_name);
}
}
5.3.2 属性变化通知
public class Settings : Object {
private int _volume = 50;
public int volume {
get { return _volume; }
set {
if (value < 0) value = 0;
if (value > 100) value = 100;
if (_volume != value) {
_volume = value;
// 通知属性已变化
notify_property ("volume");
}
}
}
}
void main () {
var s = new Settings ();
// 监听特定属性
s.notify["volume"].connect ((obj, pspec) => {
print ("音量已变化: %d\n", s.volume);
});
// 监听所有属性
s.notify.connect ((obj, pspec) => {
print ("属性 '%s' 已变化\n", pspec.name);
});
s.volume = 80;
s.volume = 30;
s.volume = 150; // 会被限制为 100
}
5.3.3 使用 GObject 构造器设置属性
public class Person : Object {
public string name { get; set; }
public int age { get; set; }
// 使用 Object() 构造器设置属性
public Person (string name, int age) {
Object (name: name, age: age);
}
construct {
// construct 块在属性设置后执行
print ("Person 已创建: %s, %d 岁\n", name, age);
}
}
void main () {
// 方式 1:通过构造器参数
var p1 = new Person ("张三", 30);
// 方式 2:使用 GObject 属性名
var p2 = Object.new (typeof (Person),
"name", "李四",
"age", 25
) as Person;
print ("p2: %s, %d\n", p2.name, p2.age);
}
5.4 信号系统详解
5.4.1 信号定义
public class Button : Object {
public string label { get; set; }
// 信号定义
public signal void clicked ();
public signal void pressed (int x, int y);
public signal bool validate (string input); // 带返回值
public Button (string label) {
Object (label: label);
}
public void simulate () {
clicked ();
pressed (10, 20);
bool valid = validate ("hello");
print ("验证结果: %s\n", valid.to_string ());
}
}
5.4.2 信号连接方式
void main () {
var btn = new Button ("确定");
// 1. Lambda 表达式
btn.clicked.connect (() => {
print ("按钮被点击\n");
});
// 2. 带参数的信号
btn.pressed.connect ((x, y) => {
print ("按下位置: (%d, %d)\n", x, y);
});
// 3. 带返回值的信号(最后一个处理器的返回值生效)
btn.validate.connect ((input) => {
return input.length > 0;
});
// 4. 方法引用
btn.clicked.connect (on_clicked);
// 5. 连接后自动断开(第一次触发后)
btn.clicked.connect_after (() => {
print ("后置处理器\n");
});
btn.simulate ();
}
void on_clicked () {
print ("独立函数处理器\n");
}
5.4.3 信号断开和检查
void main () {
var obj = new Object ();
// 连接并保存 handler ID
ulong handler = obj.notify.connect ((obj, pspec) => {
print ("属性变化: %s\n", pspec.name);
});
// 检查是否有处理器连接
print ("信号已连接: %s\n",
obj.handler_is_connected (handler).to_string ());
// 断开特定处理器
obj.disconnect (handler);
// 或使用信号名称断开所有
obj.disconnect_by_func ((Object obj, ParamSpec pspec) => {});
}
5.4.4 信号发射顺序
public class Demo : Object {
public signal int compute (int value);
}
void main () {
var demo = new Demo ();
// 多个处理器:按连接顺序执行
demo.compute.connect ((v) => {
print ("处理器1: %d -> %d\n", v, v + 1);
return v + 1;
});
demo.compute.connect ((v) => {
print ("处理器2: %d -> %d\n", v, v * 2);
return v * 2;
});
// 信号返回值:最后一个处理器的返回值
int result = demo.compute (5);
print ("最终结果: %d\n", result); // 最后一个处理器的返回值
}
5.5 引用计数与内存管理
5.5.1 引用计数基础
GObject 使用引用计数(Reference Counting)进行内存管理:
创建对象 → ref_count = 1
增加引用 → ref_count += 1
减少引用 → ref_count -= 1
引用为零 → 对象被销毁
void main () {
// 创建对象(ref_count = 1)
var obj = new Object ();
// Vala 自动管理引用计数
// obj 超出作用域时自动 unref
{
var obj2 = obj; // 引用传递,ref_count 不变(Vala 移动语义)
}
// obj2 超出作用域,但 obj 仍然有效
print ("程序结束\n");
}
5.5.2 所有权语义
public class DataHolder : Object {
private string _data;
// owned 参数:接收所有权
public void set_data (owned string data) {
_data = (owned) data; // 转移所有权
}
// owned 返回值:返回所有权
public owned string get_data () {
return _data; // 返回时 ref_count += 1
}
// 非 owned 返回:返回引用(不增加 ref_count)
public string peek_data () {
return _data;
}
}
5.5.3 避免循环引用
// ⚠️ 循环引用示例(会导致内存泄漏)
public class BadNode : Object {
public string name { get; set; }
public BadNode? parent { get; set; } // 引用父节点
public GLib.List<BadNode> children; // 引用子节点
// 这会造成循环引用!parent <-> children
}
// ✅ 正确做法:使用弱引用
public class GoodNode : Object {
public string name { get; set; }
// 使用 WeakRef 避免循环引用
private WeakRef _parent;
public GoodNode? parent {
owned get { return (GoodNode?) _parent.get (); }
set { _parent = WeakRef (value); }
}
public GLib.List<GoodNode> children = new GLib.List<GoodNode> ();
public void add_child (GoodNode child) {
child.parent = this;
children.append (child);
}
}
void main () {
var root = new GoodNode () { name = "root" };
var child1 = new GoodNode () { name = "child1" };
var child2 = new GoodNode () { name = "child2" };
root.add_child (child1);
root.add_child (child2);
print ("根节点: %s\n", root.name);
print ("子节点数: %u\n", children_length (root));
}
uint children_length (GoodNode node) {
uint len = 0;
foreach (var child in node.children) {
len++;
}
return len;
}
5.5.4 内存管理最佳实践
void main () {
// 1. 让 Vala 自动管理(推荐)
{
var obj = new Object ();
// 使用 obj
} // obj 在此处自动释放
// 2. 使用 GLib.Bytes 管理二进制数据
uint8[] data = { 0x01, 0x02, 0x03 };
var bytes = new GLib.Bytes (data);
// bytes 自动管理内存
// 3. 使用 GLib.Array 管理动态数组
var array = new GLib.Array<uint8> ();
array.append_val (0x01);
array.append_val (0x02);
// array 自动管理内存
// 4. 使用 GLib.HashTable 管理键值对
var map = new GLib.HashTable<string, int> (str_hash, str_equal);
map["key"] = 42;
// map 自动管理内存
}
5.6 对象生命周期
public class Lifecycle : Object {
public string name { get; set; }
public Lifecycle (string name) {
Object (name: name);
print ("[%s] 构造函数\n", name);
}
construct {
print ("[%s] construct 块\n", name);
}
~Lifecycle () {
print ("[%s] 析构函数\n", name);
}
public void do_something () {
print ("[%s] 执行操作\n", name);
}
}
void main () {
print ("=== 创建对象 ===\n");
var obj = new Lifecycle ("测试对象");
print ("\n=== 使用对象 ===\n");
obj.do_something ();
print ("\n=== 对象即将销毁 ===\n");
obj = null; // 引用计数归零,触发析构
print ("\n=== 程序结束 ===\n");
}
输出:
=== 创建对象 ===
[测试对象] construct 块
[测试对象] 构造函数
=== 使用对象 ===
[测试对象] 执行操作
=== 对象即将销毁 ===
[测试对象] 析构函数
=== 程序结束 ===
5.7 泛型对象创建
// 使用 Object.new() 动态创建对象
T create_instance<T> () requires T: Object {
Type type = typeof (T);
return (T) Object.new (type);
}
// 带属性的动态创建
Object create_with_props (Type type, string[] prop_names, Value[] prop_values) {
return Object.new_with_properties (type, prop_names, prop_values);
}
void main () {
// 动态创建
var obj = create_instance<Object> ();
print ("类型: %s\n", obj.get_type ().name ());
// 带属性创建
var btn = Object.new (typeof (GLib.Object)) as Object;
print ("按钮类型: %s\n", btn.get_type ().name ());
}
5.8 业务场景:数据模型绑定
// 数据模型
public class User : Object {
public string id { get; set; }
public string name { get; set; }
public string email { get; set; }
public bool active { get; set; default = true; }
// 当任何属性变化时发出信号
public signal void changed ();
construct {
// 监听所有属性变化
this.notify.connect ((obj, pspec) => {
changed ();
});
}
public User (string id, string name, string email) {
Object (id: id, name: name, email: email);
}
}
// 视图层(简单打印)
public class UserView : Object {
private User user;
public UserView (User user) {
this.user = user;
user.changed.connect (refresh);
}
private void refresh () {
print ("┌─────────────────────────────┐\n");
print ("│ 用户: %-20s │\n", user.name);
print ("│ 邮箱: %-20s │\n", user.email);
print ("│ 状态: %-20s │\n", user.active ? "活跃" : "禁用");
print ("└─────────────────────────────┘\n");
}
}
void main () {
var user = new User ("1", "张三", "zhang@example.com");
var view = new UserView (user);
print ("--- 初始状态 ---\n");
user.name = "张三丰";
user.email = "zhangsf@example.com";
user.active = false;
}
5.9 注意事项
⚠️ GObject 常见陷阱
- 不要使用裸指针:Vala 的对象引用会自动管理 ref_count
- 循环引用必须处理:使用
WeakRef或手动断开 - 信号处理器内存:连接的 lambda 会持有外部变量的引用
construct块顺序:在Object()调用之后执行,此时属性已设置- 线程安全:GObject 不是线程安全的,跨线程使用需要额外处理
unowned引用:使用unowned获取不增加引用计数的引用,但要确保对象在引用期间有效
5.10 扩展阅读
| 资源 | 链接 |
|---|---|
| GObject 手册 | https://docs.gtk.org/gobject/ |
| GObject 类型系统 | https://docs.gtk.org/gobject/type-system.html |
| GObject 属性 | https://docs.gtk.org/gobject/properties.html |
| GObject 信号 | https://docs.gtk.org/gobject/signals.html |
| 引用计数 | https://docs.gtk.org/gobject/memory.html |
| GObject 内省 | https://gi.readthedocs.io/ |
| Vala 和 GObject | https://wiki.gnome.org/Projects/Vala/GObjectInterop |
5.11 总结
| 要点 | 说明 |
|---|---|
| GObject | GNOME 的 C 面向对象类型系统 |
| 类型注册 | Vala 自动完成 G_DEFINE_TYPE |
| 属性 | { get; set; } → g_object_class_install_property |
| 信号 | signal → g_signal_new |
| 内存管理 | 引用计数,非 GC |
| 循环引用 | 使用 WeakRef 避免 |
| 类型检查 | 使用 is 运算符,typeof() 获取类型 |
下一章我们将学习 Vala 的泛型编程。→ 第 6 章:泛型