结构
GLibVariant
自 2.24 起
描述 [src]
struct GVariant {
/* No available fields */
}
GVariant
是一个变异数据类型;它可以包含一个或多个值以及有关值类型的 信息。
GVariant
可能包含简单类型(如整数或布尔值);或复杂类型(如两个字符串的数组或键值对的字典)。GVariant
也是不可变的:一旦创建,则其类型和内容都不能再进一步 修改。
GVariant
在需要序列化数据时很有用,例如在 D-Bus 中发送方法参数,或在使用 GSettings
保存设置时。
创建新的 GVariant
时,您可以传递要存储在其中的数据以及表示要传递给它的数据类型的字符串。
例如,如果要创建一个保存整数值的 GVariant
,可以使用
GVariant *v = g_variant_new ("u", 40);
第一个参数中的字符串 u
告诉 GVariant
传递给构造函数的数据 (40
) 将是无符号 整数。
可在 GVariant
格式字符串 的文档中找到更多有关 GVariant
用法的示例。
可能范围由类型确定。
GVariant
使用的类型系统是 GVariantType
。
GVariant
实例始终有类型和值(它们在构造时间给出)。GVariant
实例的类型和值永远不能改变,除非 GVariant
自身被销毁。GVariant
不能包含指针。
使用 g_variant_ref()
和 g_variant_unref()
计算 GVariant
的引用。GVariant
还有浮动引用计数——见 g_variant_ref_sink()
。
GVariant
完全是线程安全的。任何数量的线程都可以从任何方式同时访问 GVariant
实例,而没有问题。
GVariant
进行了大量的优化,以处理序列化形式的数据。它特别适合位于内存映射文件中的数据。它可以在一个小常数时间内执行几乎所有反序列化操作,通常只接触单个内存页。序列化的 GVariant
数据还可以通过网络发送。
GVariant
在很大程度上与 D-Bus 兼容。几乎所有类型的 GVariant
实例都可以通过 D-Bus 发送。有关异常,请参见 GVariantType
。(但是,GVariant
的序列化格式与 D-Bus 消息正文的序列化格式不同:对于那些,请使用 GIO 库中的 GDBusMessage。)
出于空间效率,GVariant
序列化格式不会自动包含变体长度、类型或字节序,这些必须从上下文推断出来(例如,知道特定文件格式总是包含一个占用整个文件长度的 little-endian G_VARIANT_TYPE_VARIANT
)或从外部提供(例如,可以将一个长度、类型和/或字节序指示器放在文件开头、网络消息或网络流中)。
GVariant
的大小主要受到任何较低级别的操作系统约束的限制,例如 gsize
中的位数。例如,使用 GMappedFile
将 2GB 文件映射到内存中并调用其 g_variant_new_from_data()
是合理的。
为方便 C 编程人员,GVariant
具有功能强大的基于变长参数的值构造和析构。此功能设计为可嵌入其他库中。
有一种受 Python 启发的文本语言用于描述 GVariant
值。GVariant
包括此语言的打印机和带有类型推断的解析器。
内存使用情况
GVariant
尝试在内存使用方面非常高效。本部分大致介绍了当前实现使用多少内存。此处的信息将来可能会更改。
GVariant
分配的内存可以分为 4 个广泛的目的:序列化数据内存、类型信息缓存内存、缓冲区管理内存以及 GVariant
结构自身内存。
序列化数据内存
这是用于以序列化形式存储 GVariant
数据的内存。这就是在网络上传输的内容,或最终进入磁盘的内容,而不计算任何字节序指示器,或顶层变体的长度或类型。
存储布尔值所需的内存量为 1 字节。16、32 和 64 位整数和双精度浮点数使用它们的“自然”大小。字符串(包括对象路径和签名字符串)存储有空终止符,因此使用字符串的长度加 1 字节。
“Maybe” 类型根本不使用任何空间就能表示空值,并且使用与等效的非 maybe 类型的值相同数量的空间(有时再加一个字节)来表示非空情况。
数组使用存储各个成员所需的空间,这些成员被串联起来。此外,如果存储在数组中的项不是固定大小(例如:字符串、其他数组等),那么将为每一项存储一个额外的框架偏移量。此偏移量大小为 1、2 或 4 个字节,具体取决于容器的整体大小。此外,为对齐子值所需,还将添加额外的填充字节。
元组(包括字典项)使用存储各个成员所需的空间大小,这些成员被串联起来,加上每个非固定大小项一个框架偏移量(与数组相同),元组中的最后一个项除外。此外,为对齐子值所需,还将添加额外的填充字节。
变体使用的空间大小与变体内的项相同,再加上 1 个字节,以及变体内的项的类型字符串长度。
作为一个示例,考虑一个将字符串映射到变体的字典。如果字典为空,则序列化不需要 0 字节。
如果我们添加项“宽度”,并且将其映射到 500 的 int32 值,那么我们将使用 4 个字节来存储 int32(因此对于包含它的变体来说是 6 个字节),并将 6 个字节用于字符串。在字符串的 6 字节之后,变体必须对齐至 8,因此这需要再添加 2 个字节。6(字符串)+ 2(填充)+ 6(变体)= 14 字节用于字典项。以框架偏移量形式向数组中再添加 1 个字节,总共为 15 个字节。
如果我们添加另一个项“标题”,把它映射到恰好具有空值的一个可空字符串,那么对于空值,我们将使用 0 字节(再使用 3 个字节用于变体以便用它来包含类型字符串),并使用 6 字节用于字符串。同样,我们需要 2 个填充字节。这使得总计为 6 + 2 + 3 = 11 个字节。
我们现在需要在数组中的两个项之间添加额外的填充。在第一个项的 14 字节之后,需要 2 个字节。我们现在需要 2 个框架偏移量再加额外的 2 个字节。14 + 2 + 11 + 2 = 29 字节,用于编码整个两项字典。
类型信息缓存
对于程序中当前存在的每个 GVariant
类型,类型信息结构保存在类型信息缓存中。类型信息结构对于快速反序列化是必需的。
继续上述示例,如果 GVariant
存在于类型 a{sv}
时,那么 a{sv}
、{sv}
、s
和 v
的类型信息结构将会存在。相同类型的多次使用将共享相同的类型信息。此外,所有单数字类型都存储在只读静态内存中,并且不计入使用 GVariant
的程序的可写内存占用。
除了存储在只读内存中的类型信息结构之外,还有两种类型的类型信息。一种用于存在单一元素类型的容器类型:数组和 maybe 类型。另一种用于存在多个元素类型的容器类型:元组和字典项。
数组类型信息结构为 6 * sizeof (void *)
,加上存储类型字符串本身所需内存的大小。这意味着,在 32 位系统中,a{sv}
的缓存项需要 30 个字节的内存(加上分配开销)。
元组类型信息结构是6 * sizeof (void *)
,外加元组中每一项的4 * sizeof (void *)
,以及存储类型字符串本身所需的内存。例如,2 项元组的类型信息结构将消耗尺寸为14 * sizeof (void *)
(外加类型字符串)的可写内存。这意味着在 32 位系统上,{sv}
的缓存项将需要 61 字节的内存(外加分配开销)。
这意味着,对于我们的a{sv}
示例,总共将分配 91 字节的类型信息。
此外,类型信息缓存使用GHashTable
来存储和查找缓存项,并将指向该哈希表的一个指针存储在静态存储器中。类型缓存中没有项时释放哈希表。
虽然这些尺寸看起来可能很大,但是记住一个程序可能只包含非常少数量的不同类型值,并且对于相同类型的许多不同值,只需要一个类型信息结构这一点非常重要。
缓冲区管理内存
GVariant
使用一个内部缓冲区管理结构处理各种可能的序列化数据源。缓冲区负责确保在GVariant
不再使用数据时调用正确的函数。这可能涉及一个g_free()
或甚至g_mapped_file_unref()
。
对每一块序列化数据使用一个缓冲区管理结构。缓冲区管理结构的尺寸是4 * (void *)
。在 32 位系统上,那就是 16 字节。
GVariant 结构
GVariant
结构的尺寸是6 * (void *)
。在 32 位系统上,那就是 24 字节。
仅当使用API
调用显式创建时,GVariant
结构才存在。例如,如果根据上面给出的示例(使用词典),从序列化数据构建一个GVariant
,那么,尽管构成整个词典的 9 个单独值(两个键、两个值、两个包含值的变体、两个词典项以及词典本身),仅存在一个GVariant
实例 — 即引用词典的实例。
如果发出调用以开始访问其他值,那么仅在这些值正在使用时才存在GVariant
实例(即:直到您调用g_variant_unref()
)。类型信息是共享的。序列化数据和该序列化数据的缓冲区管理结构由子级共享。
总结
将整个示例放在一起,对于将字符串映射到变体的词典(具有如上给出的两个项),我们将使用 91 个字节的内存作为类型信息,29 个字节的内存作为序列化数据,16 个字节用于缓冲区管理,24 个字节用于GVariant
实例,总计 160 个字节,外加分配开销。如果我们使用g_variant_get_child_value()
来访问这两个词典项,我们将额外使用 48 个字节。如果我们有相同类型的其他词典,我们将为那些词典的序列化数据和缓冲区管理使用更多内存,但类型信息将被共享。
从 2.24 起可用
构造函数
g_variant_new_bytestring
使用string
中的内容创建字节数组GVariant
。此函数与g_variant_new_string()
类似,只不过该字符串不必是有效的UTF-8。
自:2.26
g_variant_new_object_path
使用object_path
中的内容创建 D-Bus 对象路径GVariant
。object_path
必须是一个有效的 D-Bus 对象路径。不确定时,请使用g_variant_is_object_path()
。
自 2.24 起
g_variant_new_signature
使用string
中的内容创建 D-Bus 类型签名GVariant
。string
必须是一个有效的 D-Bus 类型签名。不确定时,请使用g_variant_is_signature()
。
自 2.24 起
Functions
g_variant_is_object_path
确定给定的字符串是否是有效的 D-Bus 对象路径。应在将字符串传递给 g_variant_new_object_path() 之前确保字符串是有效的 D-Bus 对象路径。
自 2.24 起
g_variant_is_signature
确定给定的字符串是否是有效的 D-Bus 类型签名。应在将字符串传递给 g_variant_new_signature() 之前确保字符串是有效的 D-Bus 类型签名。
自 2.24 起
实例方法
g_variant_byteswap
对 `value` 的内容执行字节交换操作。结果是 `value` 中包含的所有多字节数字数据都被字节交换。其中包括 16、32 和 64 位有符号和无符号整数,以及文件句柄和双精度浮点值。
自 2.24 起
g_variant_check_format_string
检查使用 `format_string` 在 `value` 上调用 `g_variant_get()` 从类型兼容性的角度来看是否有效。`format_string` 假设是一个有效的格式字符串(从语法角度来看)。
自:2.34
g_variant_get_bytestring_array
获取 `GVariant` 中字节数组数组的内容。此调用进行浅层复制;应使用 g_free() 释放返回结果,但不能修改各个字符串。
自:2.26
g_variant_get_child
从容器 `GVariant` 实例中读取子项,并根据 `format_string`对其进行解构。此调用本质上是 `g_variant_get_child_value()` 和 g_variant_get() 的组合。
自 2.24 起
g_variant_get_child_value
从容器 `GVariant` 实例中读取子项。其中包括变量、Maybe、数组、元组和字典条目。在任何其他类型的 `GVariant` 上调用此函数都是错误的。
自 2.24 起
g_variant_get_data
返回一个序列化的 GVariant
实例的指针。如果从不可信来源读取,则返回的数据可能不是完全规范化形式。返回的数据无需释放;它在 value
存在的过程中一直有效。
自 2.24 起
g_variant_get_data_as_bytes
返回一个序列化的 GVariant
实例的指针。此函数的语义与 g_variant_get_data() 完全相同,不同之处在于返回的 GBytes
持有一个对变种数据的引用。
自:2.36
g_variant_get_string
返回带有字符串类型的 GVariant
实例的字符串值。这包括类型 G_VARIANT_TYPE_STRING
、G_VARIANT_TYPE_OBJECT_PATH
和 G_VARIANT_TYPE_SIGNATURE
。
自 2.24 起
g_variant_get_type_string
返回 value
的类型字符串。这与调用 g_variant_type_peek_string() 的结果不同,此字符串以空结尾。此字符串属于 GVariant
,且不得释放。
自 2.24 起
g_variant_n_children
确定容器 GVariant
实例中的子项数。这包括变体、maybe、数组、元组和字典项。在任何其他类型的 GVariant
上调用此函数都会出错。
自 2.24 起