GVariant格式字符串(GVariant Format Strings)

参考:

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, &params);
    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) 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值