DBus glib 各數據類型接收與發送詳解—C語言(2)
動機
說到 DBus 用過的人大概都能明白其工作的流程。典型的使用流程是,向 DBus 服務進程發送數據,然後接收其返回的數據。簡單的說,就像調用函數一樣,向服務進程發送數據就相當於函數的參數,其返回的數據就相當於函數返回的結果。雖然明白了流程,但想要使用 C語言 通過已有的 DBus 服務進行操作,仍然是一項不太容易的工作(對像我這樣的菜鳥^_^),因爲數據的類型真是太多了, 使用 Python 會簡單一點。簡單點的有 Boolean, Byte, Int32, Int64, String, ObjectPath, Signature 等; 複雜一點的有 Array, Struct, Dict 等。如果不能弄清楚它們之間的聯繫,那麼將是一件非常頭痛的事。爲了使我研究的結果不被淡忘,於是有了這篇文章。
前置知識
- 能夠熟練使用 C語言;
- 瞭解 DBus 各數據類型的表示, 參考 D-Bus Specification
- 對 DBus-glib 有基本的瞭解,能夠與 DBus 服務進程進行簡單的交互。
- 簡單使用 d-feet, 參考 D-Bus 實例講解
- 大概對 Python 有些瞭解(只是爲了說明我的分析思路,如果你只想找 C 的解決方法,那完全可以不瞭解);
- 簡單瞭解 python dbus
正文
上一篇討論了基本的數據類型的傳遞,這次我們就討論難一點的, 高級數據類型 的傳遞。這裏我們會討論四種高級的(也就是難一點的)數據類型的傳遞: ARRAY, STRUCT, DICT_ENTRY, VARIANT, 具體請參照 D-Bus Specification。
同樣先給出 Python 編寫的服務與測試(這次沒有 shell 腳本了)。
Python DBus 服務進程
advanced_data_deliver_service.py
#!/usr/bin/env python import gobject import dbus import dbus.service import dbus.mainloop.glib class AdvancedData(dbus.service.Object): def __init__(self, bus, object_path): dbus.service.Object.__init__(self, bus, object_path) self._last_input = None @dbus.service.method('airead.fan.AdvancedDataType', in_signature='ai', out_signature='ai') def IntArrayPrint(self, ai): print "receive int array:", ai for i in range(len(ai)): print ai[i], ai[i] = ai[i] + 1 print '\n' + '=' * 33 return ai @dbus.service.method('airead.fan.AdvancedDataType', out_signature='(sidb)') def StructPrint(self, struct): print "receive struct:", struct for value in struct: print value print '=' * 33 return ('Li', 24, 55.1, False) @dbus.service.method('airead.fan.AdvancedDataType', in_signature='a{ss}', out_signature='a{ss}') def DictPrint(self, dict): print "receive dict:", dict for k in dict: print "%s: %s" % (k, dict[k]) print '=' * 33 return {'fan': "male", 'li': "female"} @dbus.service.method('airead.fan.AdvancedDataType', in_signature='v', out_signature='v') def VariantPrint(self, variant): print "receive variant:", variant print '=' * 33 return {'fan': "male", 'li': "female"} if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) session_bus = dbus.SessionBus() name = dbus.service.BusName("airead.fan.AdvancedDataType", session_bus) object = AdvancedData(session_bus, '/airead/fan/AdvancedDataType') mainloop = gobject.MainLoop() print "Running example service." mainloop.run()
Python 測試服務
advanced_data_deliver_test_py.py
#!/usr/bin/python import sys import dbus from traceback import print_exc def main(): bus = dbus.SessionBus() try: remote_object = bus.get_object("airead.fan.AdvancedDataType", "/airead/fan/AdvancedDataType") dbus_interface = dbus.Interface(remote_object, "airead.fan.AdvancedDataType") #test dictionary dic = {'a':'apple', 'b':'banana', 'c':'cherry'} ret = dbus_interface.DictPrint(dic) print ret print '=' * 33 + "\n" #test int array intarray = [1, 2, 3, 4, 5, 6] ret = dbus_interface.IntArrayPrint(intarray) print ret print '=' * 33 + "\n" #test struct struct = ("fan", 24, 70.5, False) ret = dbus_interface.StructPrint(struct) print ret print '=' * 33 + "\n" #test variant variant = dic ret = dbus_interface.VariantPrint(variant) print ret print '-' * 33 + "\n" variant = intarray ret = dbus_interface.VariantPrint(variant) print ret print '-' * 33 + "\n" variant = struct ret = dbus_interface.VariantPrint(variant) print ret print '-' * 33 + "\n" except dbus.DBusException: print_exc() sys.exit(1) main()
使用 C 實現高級數據類型的傳遞
以下代碼僅僅爲了演示數據類型的傳遞,不保證沒有內存泄漏,請仔細檢查後再使用。
ARRAY
ARRAY: glib->garray *, G_TYPE_ARRAY, dbus->'a'
傳遞字節數組的話使用 "ay", 傳遞 ObjectPath 數組的話使用 "ao", 傳遞整數數組的話使用 "ai", 具體可以使用 Python 來寫 service 進行測試。下面是一個傳遞整數數組的例子。在這個例子中有幾個要點:
- garray 的相關操作;
- 傳遞 int array 時,要使用 DBUS_TYPE_G_INT_ARRAY. 關於 DBUS_TYPE_G_INT_ARRAY 是怎麼來的將在後面討論;
- 注意傳遞的是指針。
int send_recv_int_array(DBusGProxy *proxy) { char *method; GError *error; GArray *garray, *ret; gint i, j; garray = g_array_new (FALSE, FALSE, sizeof (gint)); for (i = 0; i < 6; i++) { j = i + 1; g_array_append_val(garray, j); } method = "IntArrayPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_TYPE_G_INT_ARRAY, garray, G_TYPE_INVALID, DBUS_TYPE_G_INT_ARRAY, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive int array:\n"); for (i = 0; i < ret->len; i++) { g_print("%d ", g_array_index(ret, gint, i)); } g_print("\n=================================\n\n"); return 0; }
STRUCT
STRUCT: glib->GValueArray *, ????, dbus->'()'
結構體在 D-Bus 服務進程中的聲名爲 (??), 其中 `?' 可以任意數據類型。比如
// (si)
struct str_int {
gchar *s;
gint i;
};
// (sidb)
struct str_int_double_boolean {
gchar *s;
gint i;
gdouble d;
gboolean b;
};
下面演示了 (sidb) 在 D-Bus 中的傳遞。代碼分爲三大塊:
- define 需要的結構;
- 創建輸入數據;
- 調用命令;
- 打印接收到的數據;
#define DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN ( \ dbus_g_type_get_struct ( "GValueArray", G_TYPE_STRING, G_TYPE_INT, \ G_TYPE_DOUBLE, G_TYPE_BOOLEAN, G_TYPE_INVALID)) int send_recv_struct(DBusGProxy *proxy) { char *method; GError *error = NULL; GValueArray *ret, *send_array; GValue *gval; GValue send_gval[4] = `0`; int i; g_value_init (&send_gval[0], G_TYPE_STRING); g_value_set_string(&send_gval[0], "fan"); g_value_init (&send_gval[1], G_TYPE_INT); g_value_set_int(&send_gval[1], 24); g_value_init (&send_gval[2], G_TYPE_DOUBLE); g_value_set_double(&send_gval[2], 70.2); g_value_init (&send_gval[3], G_TYPE_BOOLEAN); g_value_set_boolean(&send_gval[3], FALSE); send_array = g_value_array_new(0); for (i = 0; i < 4; i++) { send_array = g_value_array_append(send_array, &send_gval[i]); } method = "StructPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN, send_array, G_TYPE_INVALID, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive struct:\n"); for (i = 0; i < ret->n_values; i++) { gval = g_value_array_get_nth(ret, i); if (G_VALUE_TYPE(gval) == G_TYPE_STRING) { g_print("%s\n", g_value_get_string(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_DOUBLE) { g_print("%f\n", g_value_get_double(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_INT) { g_print("%d\n", g_value_get_int(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_BOOLEAN) { g_print("%d\n", g_value_get_boolean(gval)); } } g_print("\n=================================\n\n"); return 0; }
回憶一下,在傳遞 ARRAY 中我們遇到了 DBUS_TYPE_G_INT_ARRAY , 那麼它是如何定義的呢?
DBUS_TYPE_G_INT_ARRAY (dbus_g_type_get_collection ("GArray", G_TYPE_INT))
它是 dbus-glib 庫幫我定義的,使用到了 dbus_g_type_get_collection() 函數。當我們傳遞某些特殊數據的時候,如果 dbus-glib 庫沒有它的定義,那我們需要使用
- dbus_g_type_get_collection()
- dbus_g_type_get_map()
- dbus_g_type_get_struct()
等相關函數自行定義。上面的例子中我們就定義一個 struct 類型:
DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN ( \
dbus_g_type_get_struct ( "GValueArray", G_TYPE_STRING, G_TYPE_INT, \
G_TYPE_DOUBLE, G_TYPE_BOOLEAN, G_TYPE_INVALID))
具體細節請參考 Specializable GType System,以及詳解中其它的類型定義。 map 對應 GHashTable, collection 對應 GArray, struct 對 GValueArray 等。
另外,STRUCT 數據還有另一種構造方法,在 `使用 VARINAT 傳遞 STRUCT' 中,我也不知道哪種方法更好。
DICT_ENTRY
DICT_ENTRY: glib->GHashTable, ????, dbus->a{??}
參考 STRUCT 中 `?' 的示例,下面我們演示了 a{ss} 的傳遞代碼。其中 DBUS_TYPE_G_STRING_STRING_HASHTABLE,是庫幫我們定義的:
DBUS_TYPE_G_STRING_STRING_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING))
代碼同樣分爲三大塊,與 STUCT 流程相似。
int send_recv_dict(DBusGProxy *proxy) { char *method; GHashTable *table, *ret; GHashTableIter iter; gpointer key, value; GError *error = NULL; char *str[3]; table = g_hash_table_new(NULL, NULL); str[0] = "apple"; str[1] = "banana"; str[2] = "cherry"; g_hash_table_insert(table, "a", str[0]); g_hash_table_insert(table, "b", str[1]); g_hash_table_insert(table, "c", str[2]); method = "DictPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_TYPE_G_STRING_STRING_HASHTABLE, table, G_TYPE_INVALID, DBUS_TYPE_G_STRING_STRING_HASHTABLE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive: dictionary\n"); g_hash_table_iter_init(&iter, ret); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("=================================\n\n"); g_hash_table_unref(table); g_hash_table_unref(ret); return 0; }
VARIANT
VARIANT: glib->GValue, G_TYPE_VALUE, dbus->'v'
VARIANT 是一個通用的容器,它可以裝任意數據類型。如:所有基本類型,ARRAY, STRUCT, DICT_ENTRY 甚至容納自身。以下代碼演示了裝 INT_ARRAY 和 STRUCT 的 VARIANT 的傳遞,它們分爲兩個函數。
int send_recv_variant(DBusGProxy *proxy) { char *method; method = "VariantPrint"; send_recv_variant_int_array(proxy, method); send_recv_variant_struct(proxy, method); return 0; }
使用 VARINAT 傳遞 INT_ARRAY
重點就是如何將 INT_ARRAY 裝入 GValue 中。這裏的辦法是,先產生一個容器,然後從容器中獲取指針進行賦值。試這個的時候費了我老大勁了-_-!。注意,服務進程返回的是 DICT_ENTRY 類型的數據。
int send_recv_variant_int_array(DBusGProxy *proxy, char *method) { GError *error = NULL; GValue gval = G_VALUE_INIT; GValue ret = G_VALUE_INIT; GHashTable *table; GHashTableIter iter; gpointer key, value; GArray *garray; gint i, j; g_value_init(&gval, DBUS_TYPE_G_INT_ARRAY); g_value_take_boxed(&gval, dbus_g_type_specialized_construct(DBUS_TYPE_G_INT_ARRAY)); garray = g_value_get_boxed(&gval); for (i = 0; i < 6; i++) { j = i + 1; g_array_append_val(garray, j); } if (!dbus_g_proxy_call(proxy, method, &error, G_TYPE_VALUE, &gval, G_TYPE_INVALID, G_TYPE_VALUE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive variant:\n"); table = g_value_get_boxed(&ret); g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("\n=================================\n\n"); return 0; }
使用 VARINAT 傳遞 INT_STRUCT
重點就是如何將 STRUCT 裝入 GValue 中。這裏出現了構造 STRUCT 的第二種方法。注意,服務進程返回的是 DICT_ENTRY 類型的數據。
int send_recv_variant_struct(DBusGProxy *proxy, char *method) { GError *error = NULL; GValue gval = G_VALUE_INIT; GValue ret = G_VALUE_INIT; GHashTable *table; GHashTableIter iter; gpointer key, value; g_value_init(&gval, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN); g_value_take_boxed(&gval, dbus_g_type_specialized_construct(DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN)); dbus_g_type_struct_set(&gval, 0, "fan", 1, 24, 2, 70.1, 3, FALSE, G_MAXUINT); if (!dbus_g_proxy_call(proxy, method, &error, G_TYPE_VALUE, &gval, G_TYPE_INVALID, G_TYPE_VALUE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive variant:\n"); table = g_value_get_boxed(&ret); g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("\n=================================\n\n"); return 0; }
C D-Bus 測試完整代碼
advanced_data_deliver_test_c.c
/** * @file advanced_data_deliver_test_c.c * @brief * @author Airead Fan <[email protected]> * @date 2012/03/23 11:28:29 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dbus/dbus-glib.h> #define METHOD_STRLEN 128 int send_recv_dict(DBusGProxy *proxy) { char *method; GHashTable *table, *ret; GHashTableIter iter; gpointer key, value; GError *error = NULL; char *str[3]; table = g_hash_table_new(NULL, NULL); str[0] = "apple"; str[1] = "banana"; str[2] = "cherry"; g_hash_table_insert(table, "a", str[0]); g_hash_table_insert(table, "b", str[1]); g_hash_table_insert(table, "c", str[2]); method = "DictPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_TYPE_G_STRING_STRING_HASHTABLE, table, G_TYPE_INVALID, DBUS_TYPE_G_STRING_STRING_HASHTABLE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive: dictionary\n"); g_hash_table_iter_init(&iter, ret); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("=================================\n\n"); g_hash_table_unref(table); g_hash_table_unref(ret); return 0; } int send_recv_int_array(DBusGProxy *proxy) { char *method; GError *error; GArray *garray, *ret; gint i, j; garray = g_array_new (FALSE, FALSE, sizeof (gint)); for (i = 0; i < 6; i++) { j = i + 1; g_array_append_val(garray, j); } method = "IntArrayPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_TYPE_G_INT_ARRAY, garray, G_TYPE_INVALID, DBUS_TYPE_G_INT_ARRAY, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive int array:\n"); for (i = 0; i < ret->len; i++) { g_print("%d ", g_array_index(ret, gint, i)); } g_print("\n=================================\n\n"); return 0; } #define DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN ( \ dbus_g_type_get_struct ( "GValueArray", G_TYPE_STRING, G_TYPE_INT, \ G_TYPE_DOUBLE, G_TYPE_BOOLEAN, G_TYPE_INVALID)) int send_recv_struct(DBusGProxy *proxy) { char *method; GError *error = NULL; GValueArray *ret, *send_array; GValue *gval; GValue send_gval[4] = `0`; int i; g_value_init (&send_gval[0], G_TYPE_STRING); g_value_set_string(&send_gval[0], "fan"); g_value_init (&send_gval[1], G_TYPE_INT); g_value_set_int(&send_gval[1], 24); g_value_init (&send_gval[2], G_TYPE_DOUBLE); g_value_set_double(&send_gval[2], 70.2); g_value_init (&send_gval[3], G_TYPE_BOOLEAN); g_value_set_boolean(&send_gval[3], FALSE); send_array = g_value_array_new(0); for (i = 0; i < 4; i++) { send_array = g_value_array_append(send_array, &send_gval[i]); } method = "StructPrint"; if (!dbus_g_proxy_call(proxy, method, &error, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN, send_array, G_TYPE_INVALID, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive struct:\n"); for (i = 0; i < ret->n_values; i++) { gval = g_value_array_get_nth(ret, i); if (G_VALUE_TYPE(gval) == G_TYPE_STRING) { g_print("%s\n", g_value_get_string(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_DOUBLE) { g_print("%f\n", g_value_get_double(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_INT) { g_print("%d\n", g_value_get_int(gval)); } else if (G_VALUE_TYPE(gval) == G_TYPE_BOOLEAN) { g_print("%d\n", g_value_get_boolean(gval)); } } g_print("\n=================================\n\n"); return 0; } int send_recv_variant_int_array(DBusGProxy *proxy, char *method) { GError *error = NULL; GValue gval = G_VALUE_INIT; GValue ret = G_VALUE_INIT; GHashTable *table; GHashTableIter iter; gpointer key, value; GArray *garray; gint i, j; g_value_init(&gval, DBUS_TYPE_G_INT_ARRAY); g_value_take_boxed(&gval, dbus_g_type_specialized_construct(DBUS_TYPE_G_INT_ARRAY)); garray = g_value_get_boxed(&gval); for (i = 0; i < 6; i++) { j = i + 1; g_array_append_val(garray, j); } if (!dbus_g_proxy_call(proxy, method, &error, G_TYPE_VALUE, &gval, G_TYPE_INVALID, G_TYPE_VALUE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive variant:\n"); table = g_value_get_boxed(&ret); g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("\n=================================\n\n"); return 0; } int send_recv_variant_struct(DBusGProxy *proxy, char *method) { GError *error = NULL; GValue gval = G_VALUE_INIT; GValue ret = G_VALUE_INIT; GHashTable *table; GHashTableIter iter; gpointer key, value; g_value_init(&gval, DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN); g_value_take_boxed(&gval, dbus_g_type_specialized_construct(DBUS_STRUCT_STRING_INT_DOUBLE_BOOLEAN)); dbus_g_type_struct_set(&gval, 0, "fan", 1, 24, 2, 70.1, 3, FALSE, G_MAXUINT); if (!dbus_g_proxy_call(proxy, method, &error, G_TYPE_VALUE, &gval, G_TYPE_INVALID, G_TYPE_VALUE, &ret, G_TYPE_INVALID)) { g_printerr("call %s failed: %s\n", method, error->message); g_error_free(error); error = NULL; return -1; } g_print("receive variant:\n"); table = g_value_get_boxed(&ret); g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { g_print("key: %s, %s\n", (char *)key, (char *)value); } g_print("\n=================================\n\n"); return 0; } int send_recv_variant(DBusGProxy *proxy) { char *method; method = "VariantPrint"; send_recv_variant_int_array(proxy, method); send_recv_variant_struct(proxy, method); return 0; } int main(int argc, char *argv[]) { DBusGConnection *connection; GError *error = NULL; DBusGProxy *proxy; g_type_init(); /* conect system connection and get proxy */ connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); if (connection == NULL) { g_printerr("get system bus failed: %s\n", error->message); g_error_free(error); return -1; } /* get proxy */ proxy = dbus_g_proxy_new_for_name(connection, "airead.fan.AdvancedDataType", "/airead/fan/AdvancedDataType", "airead.fan.AdvancedDataType"); send_recv_dict(proxy); send_recv_int_array(proxy); send_recv_struct(proxy); send_recv_variant(proxy); return 0; }
Makefile
有些東西實際上沒用,我也懶得去了。
CC = gcc CFLAGS = -Wall -g CFLAGS += $(shell pkg-config --cflags glib-2.0 ) CFLAGS += $(shell pkg-config --cflags dbus-glib-1) #CFLAGS += $(shell pkg-config --cflags gtk+-2.0) LDFLAGS = LDFLAGS += $(shell pkg-config --libs glib-2.0) LDFLAGS += $(shell pkg-config --libs dbus-glib-1) #LDFLAGS += $(shell pkg-config --libs gtk+-2.0) SOURCE = $(wildcard *.c) TARGETS := $(patsubst %.c, %, $(SOURCE)) TARGETS_OUT = common_marshaler basic_data TARGETS := $(filter-out $(TARGETS_OUT), $(TARGETS)) TARGETS := $(addsuffix .out, $(TARGETS)) %.out: %.c @echo CC $< -o $@ @$(CC) $< common_marshaler.c basic_data.c $(CFLAGS) -o $@ $(LDFLAGS) .PHONY: all clean test marshaler all: $(TARGETS) marshaler: glib-genmarshal --prefix _common_marshal --header common_marshaler.list > common_marshaler.h glib-genmarshal --prefix _common_marshal --body common_marshaler.list > common_marshaler.c dbus-binding-tool --prefix=airead_fan --mode=glib-server all_basic_data_deliver_server.xml > all_basic_data_deliver_server.h clean: rm -f *~ a.out *.o $(TARGETS) core.* test: @echo TARGETS: $(TARGETS)