参考:
https://docs.gtk.org/glib/gvariant-format-strings.html
GVariant是glib提供的一种特殊的类型,它可以将许多不同类型的参数整合到一起,最终形成一个指针指向的对象,形式上就是一个指针参数。dbus接口通信中,往往会用到GVariant。
本文系统地学习glib手册中关于GVariant Format Strings的描述。
语法
有效的format strings具有以下形式中的一种:
- 任意的类型字符串
- 前缀为@的类型字符串
- &s, &o, &g, ^as, ^a&s, ^ao, ^a&o, ^ay, ^&ay, ^aay或者^a&ay
- 前缀为m的任意的类型字符串
- 一个由零个或多个format strings组成的序列,连接起来并用括号括起来
- 一个左大括号,后面跟着两个format strings,后面跟着一个右大括号(受这样的约束:第一个format string对应于一个可用作字典的key的类型)
符号
下表中列出的所有GVariant格式字符串将在后面一一详解。
b, y, n, q, i, u, x, t, h, d | 布尔,字节,以及数字类型 |
s, o, g | 字符串类型 |
v | variant类型 |
a | 数组类型 |
m | maybe类型 |
() | 元组类型 |
{} | 词典 |
@ | 用作GVariant类型字符串的前缀(不是格式字符串的前缀,因此@as是有效的format string,但@^as不是)。表示应该使用指向GVariant的指针来代替普通的C类型。对于g_variant_new(),这意味着您必须传递一个非NULL(GVariant*);如果它是一个浮动引用,则将获取所有权,就像使用g_variant_ref_sink()一样。对于g_variant_get(),这意味着必须将指针传递给要通过引用返回的值的(GVariant*)或忽略该值的NULL。请参阅下面的“GVariant*” |
*, ?, r | 完全等同于@*,@?和r。仅为了完整性而提供,使得所有GVariant类型字符串也可以用作格式字符串。请参阅下面的“GVariant*” |
& | 用作GVariant类型字符串的前缀(不是格式字符串的前缀,因此&s是有效的格式字符串,但&“s”不是)。表示应该使用指向串行数据的C指针来代替普通的C类型。请参阅下面的“指针” |
^ | 用于某些具体类型的format strings的前缀。见后面的“快捷转化” |
说明:
- 注意区分“GVariant类型字符串”和“GVariant格式字符串”,后者的范围更大,包含了前者。“GVariant类型字符串”具体指表中的哪些类型,这个问题暂不明确。但最起码表格的前4行是在这个概念范围之内的。
数字类型
b | gboolean |
y | guchar |
n | gint16 |
q | guint16 |
i | gint32 |
u | guint32 |
x | gint64 |
t | guint64 |
h | gint32 |
d | gdouble |
注意:c语言有数字类型的自动转换,比如用gint32的参数可以接收一个gint16的值,于是g_variant_new()可以将int类型转化为“b, y, n, q, i, u或者h”。但g_variant_get()就不会有自动转换了,“指针必须始终指向大小完全正确的内存区域”。
#include <glib.h>
int main(void)
{
GVariant *value1, *value2, *value3, *value4;
value1 = g_variant_new ("y", 200);
value2 = g_variant_new ("b", TRUE);
value3 = g_variant_new ("d", 37.5);
value4 = g_variant_new ("x", G_GINT64_CONSTANT (998877665544332211));
{
gdouble floating;
gboolean truth;
gint64 bignum;
g_variant_get (value1, "y", NULL); /* ignore the value. */
g_variant_get (value2, "b", &truth);
g_variant_get (value3, "d", &floating);
g_variant_get (value4, "x", &bignum);
g_print("truth=%u, floating=%f, bignum=%ld\n", truth, floating, bignum);
}
return 0;
}
输出如下:
truth=1, floating=37.500000, bignum=998877665544332211
字符串
s是普通字符串,o必须是一个D-Bus对象路径的字符串,g必须是一个D-Bus类型标签的字符串。
void test_string()
{
GVariant *value1, *value2, *value3;
value1 = g_variant_new ("s", "hello world!");
value2 = g_variant_new ("o", "/must/be/a/valid/path");
value3 = g_variant_new ("g", "iias");
#if 0
g_variant_new ("s", NULL); /* not valid: NULL is not a string. */
#endif
{
gchar *result;
g_variant_get (value1, "s", &result);
g_print ("value1 was '%s'\n", result);
g_free (result);
g_variant_get (value2, "o", &result);
g_print ("value2 was '%s'\n", result);
g_free (result);
g_variant_get (value3, "g", &result);
g_print ("value3 was '%s'\n", result);
g_free (result);
}
}
注意:
- 对于类型'o'和'g',虽然本质上是字符串,但在调用g_variant_get的时候,格式化字符串必须分别使用'o'和'g',不能使用s'。
在main函数中运行test_string(),输出如下:
value1 was 'hello world!'
Variants
字符:v
“遇到v时,g_variant_new()会获取一个(GVariant*)。GVariant的值用作variant值的内容。”
“遇到v时,g_variant_get()将指针指向(GVariant*)(即:(GVariant**))。它被设置为对包含变量值内容的GVariant实例的新引用。使用g_variant_unref()释放此引用是合适的。还可以传递NULL来指示应该忽略该值(在这种情况下,不会创建新的引用)。”
简单来说,g_variant_new会产生一个嵌套的GVariant类型;而g_variant_get用于解析这个嵌套的GVariant类型。在使用g_variant_get获取结果的时候,当结果不使用后,需要用g_variant_unref来释放引用。
GVariant *x, *y;
/* the following two lines are equivalent: */
x = g_variant_new ("v", y);
x = g_variant_new_variant (y);
/* as are these: */
g_variant_get (x, "v", &y);
y = g_variant_get_variant (x);
上面的例子中,x就是一个嵌套了y这个GVariant的GVariant。注意:这段程序仅仅是一个含义的示例,程序本身是不能执行的;因为y是NULL,在运行时会报Critical错误。
数组
字符:a
手册上的描述有点不好理解,我换一种方式。
当使用g_variant_new()构造一个数组类型的GVariant参数时,需要借助GVariantBuilder *的参数。每添加一个元素到数组中时,用g_variant_builder_add()向这个GVariantBuilder *参数添加。添加完之后,再用g_variant_new()将这个GVariantBuilder *转化为GVariant参数。
当使用g_variant_get()读取一个数组类型的GVariant参数时,需要先将GVariant参数读取到GVariantIter *的迭代器中,然后循环调用g_variant_iter_loop获取迭代器中的每一个元素。
代码示例如下:
void test_array()
{
GVariantBuilder *builder;
GVariant *value;
builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
g_variant_builder_add (builder, "s", "when");
g_variant_builder_add (builder, "s", "in");
g_variant_builder_add (builder, "s", "the");
g_variant_builder_add (builder, "s", "course");
// These two have the same result
value = g_variant_new ("as", builder);
//value = g_variant_builder_end(builder);
g_variant_builder_unref (builder);
{
GVariantIter *iter;
gchar *str;
g_variant_get (value, "as", &iter);
while (g_variant_iter_loop (iter, "s", &str))
g_print ("%s\n", str);
g_variant_iter_free (iter);
}
g_variant_unref (value);
}
注意:
- value = g_variant_new ("as", builder); 和value = g_variant_builder_end(builder);具有相同的效果。
输出如下:
when
in
the
course
Maybe类型
这个类型不知道用途是什么,跳过
元组
字符:()
使用元组时,每个元素直接对应处理,没有中间类型参与。即使有元组嵌套,也一样。
void test_tuple(void)
{
GVariant *value1, *value2;
value1 = g_variant_new ("(s(ii))", "Hello", 55, 77);
value2 = g_variant_new ("()");
{
gchar *string;
gint x, y;
g_variant_get (value1, "(s(ii))", &string, &x, &y);
g_print ("%s, %d, %d\n", string, x, y);
g_free (string);
g_variant_get (value2, "()"); /* do nothing... */
}
}
可以看到,即使有元组嵌套(s(ii)),依然是每个元素独自处理。程序输出如下:
Hello, 55, 77
GVariant * (GVariant指针)
字符:@, *, ?, r
先解释@。
- 对于g_variant_new,当遇到“@后跟类型字符串”时,会创建一个指向GVariant的非空指针,并且类型字符串匹配的值会立即给出(在g_variant_new的参数列表中)。
- 对于g_variant_get,当遇到“@后跟类型字符串”时,会获取一个指向GVariant*的指针(即GVariant**),并不像元组那样直接解析为和类型字符串匹配的一个个参数。另外,可以给定NULL来忽略该值。
对于其它几个符号:
- *与@*相同,即:取任何类型的GVariant
- ?和@?一样,即:取任何基本类型的GVariant
- r与@r相同,即:取任何元组类型的GVariant
void test_gvariant_star()
{
GVariant *value1, *value2;
value1 = g_variant_new ("(i@ii)", 44, g_variant_new_int32 (55), 66);
/* note: consumes floating reference count on 'value1' */
value2 = g_variant_new ("(@(iii)*)", value1, g_variant_new_string ("foo"));
{
const gchar *string;
GVariant *tmp;
gsize length;
gint x, y, z;
g_variant_get (value2, "((iii)*)", &x, &y, &z, &tmp);
string = g_variant_get_string (tmp, &length);
g_print ("it is %d %d %d %s (length=%d)\n", x, y, z, string, (int) length);
g_variant_unref (tmp);
/* quick way to skip all the values in a tuple */
g_variant_get (value2, "(rs)", NULL, &string); /* or "(@(iii)s)" */
g_print ("i only got the string: %s\n", string);
g_free ((gpointer)string);
}
}
注意:
- 这几个符号的使用比较灵活,创建和获取时的格式字符串并不需要完全一致。只要满足这几个符号后跟的“类型字符串匹配”即可。
- 例如:value1创建的时候,使用格式字符串"(i@ii)",但使用它作为value2的参数时,匹配的格式字符串实际上是“@(iii)”
- 再例如:value2创建的时候,使用格式字符串"(@(iii)*)",而get的时候,可以使用"((iii)*)",也可以使用"(rs)"
- 根据前面的描述,这几个符号可以理解为正则表达式中的通配符。
词典
字符:{}
字典条目的处理方式是先处理关键字,然后处理值。每个元素都以通常的方式处理。
词典经常和数组结合使用,使用的结果是有点像json表示的结构体。和数组一样,词典数组在构造的时候,也需要借助GVariantBuilder;在解析的时候,需要借助GVariantIter。程序如下:
void test_dictionary()
{
GVariantBuilder *b;
GVariant *dict;
b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (b, "{sv}", "name", g_variant_new_string ("foo"));
g_variant_builder_add (b, "{sv}", "timeout", g_variant_new_int32 (10));
dict = g_variant_builder_end (b);
g_variant_builder_unref (b);
{
GVariantIter iter;
gchar *key;
GVariant *value;
g_variant_iter_init (&iter, dict);
g_variant_iter_loop (&iter, "{sv}", &key, &value);
g_print("1: key=%s, value=%s\n", key, g_variant_get_string(value, NULL));
g_variant_iter_loop (&iter, "{sv}", &key, &value);
g_print("2: key=%s, value=%d\n", key, g_variant_get_int32(value));
}
{
GVariantIter *iter;
gchar *key;
GVariant *value;
g_variant_get (dict, "a{sv}", &iter);
g_variant_iter_next (iter, "{sv}", &key, &value);
g_print("1: key=%s, value=%s\n", key, g_variant_get_string(value, NULL));
g_free(key);
g_variant_unref(value);
g_variant_iter_next (iter, "{sv}", &key, &value);
g_print("2: key=%s, value=%d\n", key, g_variant_get_int32(value));
g_free(key);
g_variant_unref(value);
g_variant_iter_free (iter);
}
g_variant_unref(dict);
}
说明:
- 上述程序中,采用了两种等效的方式实现词典数组的解析。这么做是为了后面的两个“注意”。
- 注意g_variant_get和g_variant_iter_init的区别:使用g_variant_get的时候,是创建一个新的GVariantIter对象,因此传进去一个GVariantIter **,并且在使用完后,需要调用g_variant_iter_free释放GVariantIter对象;而使用g_variant_iter_init的时候,是对GVariantIter直接赋值,因此传进去GVariantIter *,由于GVariantIter作为局部变量、使用栈内存,故使用完后不需要释放
- 注意g_variant_iter_next和g_variant_iter_loop的区别:使用g_variant_iter_next时,每获取一个元素,都需要对获取的指针对象(在堆内存中)进行释放;而使用g_variant_iter_loop时,不需要释放。
上述程序运行结果如下:
1: key=name, value=foo
2: key=timeout, value=10
1: key=name, value=foo
2: key=timeout, value=10
结构体可以嵌套,词典数组也可以嵌套。看下面的程序:
void test_nested_dictionary()
{
GVariant *data;
gint value = 1;
gint max = 3;
/* type (oa{sa{sv}) */
data = g_variant_new_parsed ("(%o, {'brightness': {'value': <%i>, 'max': <%i>}})",
"/object/path", value, max);
{
GVariant *params;
GVariant *p_brightness;
gchar *obj;
gint p_max;
g_variant_get (data, "(o@a{?*})", &obj, ¶ms);
g_print ("object_path: %s\n", obj);
g_free(obj);
p_brightness = g_variant_lookup_value (params, "brightness", G_VARIANT_TYPE_VARDICT);
g_variant_lookup (p_brightness, "max", "i", &p_max);
g_print ("max: %d\n", p_max);
g_print("is GVariant floating: data(%u), p_brightness(%u), params(%u)\n",
g_variant_is_floating(data),
g_variant_is_floating(p_brightness),
g_variant_is_floating(params));
g_variant_unref(p_brightness);
g_variant_unref(params);
}
g_variant_unref(g_variant_ref_sink(data));
}
程序说明:
- g_variant_new_parsed提供了一个快速构造GVariant词典实例的方法。注意第一个参数有点像c语言的格式化字符串,且同样使用“%”用作其中的替代参数。根据该函数的实现,"%"后面可以跟GVariant格式字符串(不是GVariant类型字符串),也就是说,后面可以跟数组类型等非基本类型。但函数的注释中也单独提到:不能仅为“%*”、“%?”、“\%r”,或以“%@”开头的任何格式。
- 注意程序中内存的释放。按照glib描述:“GVariant使用了浮动引用的计数系统,所有形如`g_variant_new_`的函数都会返回浮动引用”。对于由函数g_variant_new_parsed产生的参数data,在释放前先要执行g_variant_ref_sink。关于浮动引用的解释,参见我的另一篇博文Glib中的Floating references
- 注意@后面仅包含一个独立的GVariant类型字符串。例如,对于"(o@a{?*})",a表示数组,这个不是独立的类型字符,因此a{?*}才是@后面跟着的一个独立的GVariant类型字符串,于是@a{?*}是一个“GVariant指针”。而对于"(i@ii)",i就是一个独立的GVariant类型字符,因此@i就是一个“GVariant指针”。
- g_variant_lookup_value和g_variant_lookup专用于解析词典结构,使用起来比较便利。
程序输出如下:
object_path: /object/path
max: 3
is GVariant floating: data(1), p_brightness(0), params(0)
指针
字符:&
该格式字符的唯一用途是将其应用于字符串(即:&s、-o或&g)
对于g_variant_new(),使用&没有额外的效果,和不使用&一样。
对于g_variant_get(),这意味着不创建新分配的字符串副本,而是返回指向串行数据的指针。因此不应释放此指针。
void test_pointer()
{
const gchar *str;
GVariant *value;
value = g_variant_new ("&s", "hello world");
g_variant_get (value, "&s", &str);
g_print ("string is: %s\n", str);
/* no need to free str */
//g_free((gpointer)str); // this will cause 'double free' issue
g_variant_unref(g_variant_ref_sink(value));
}
注意:当g_variant_get中使用了指针类型'&'之后,获取的字符串就不能g_free()了。
快捷转化
字符:^
其实就是针对某些特别类型的、'new'和'get'方法的快捷方式,了解一下即可。下面是手册中给出的详细说明:
转换 | 用于g_variant_new()时 | 用于g_variant_get()时 |
^as | 等效于g_variant_new_strv() | 等效于g_variant_dup_strv() |
^a&s | 同上 | 等效于 g_variant_get_strv() |
^ao | 等效于g_variant_new_objv() | 等效于g_variant_dup_objv() |
^a&o | 同上 | 等效于 g_variant_get_objv() |
^ay | 等效于g_variant_new_bytestring() | 等效于g_variant_dup_bytestring() |
^&ay | 同上 | 等效于g_variant_get_bytestring() |
^aay | 等效于g_variant_new_bytestring_array() | 等效于g_variant_dup_bytestring_array() |
^a&ay | 同上 | 等效于 g_variant_get_bytestring_array() |
说明:
- 以上表格中'g_variant_dup_'和'g_variant_get'的区别是:前者是深拷贝,后者是浅拷贝。深拷贝就是不仅复制指针本身,也复制指针里面的内容(意味着被赋值的指针需要为指向的内容单独创建内存);而浅拷贝仅复制指针本身(意味着被复制的指针不需要为指向的内容单独创建内存)。深拷贝和浅拷贝网上有很多解释,这里不展开描述。
附Makefile
CFLAGS的LDFLAGS的内容需要根据实际的linux服务器确定。详细的确定方法参考glib的开发环境
.PHONY: clean
CC = gcc
RM = rm
EXE = gvar
CFLAGS=-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
LDFLAGS=-lgobject-2.0 -lglib-2.0
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
$(EXE):$(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)