基于GDBus框架的进程间通信详解
- 1. GDBus简介
- 2. 生成D-Bus interface
- 3.GDBus interface源文件详解
- 4. Client端编程
- 5. Server端编程
1. GDBus简介
首先简单介绍一下什么是GDBus。GDBus是一种基于d-bus技术的进程间通信框架,其中最核心的部分就是“Bus”,个人理解,它就是一个进程间通信的“桥梁”,不同的进程之间进行接收或者传递消息,都需要通过这个"Bus"总线。进程间通信的消息都会先发送到"Bus"总线上,然后再分发到目标进程上,"Bus"总线会根据收到消息的类型不同,采取不同的处理,主要处理可以分为两类:函数调用、信号广播。为了方便起见,我们假设有两个进程需要进行通信,一个Client进程,一个Server进程。
- 函数调用
Client进程需要调用Server进程的某个方法(Method Call),请求信息会先发送给"Bus"总线,经过"Bus"总线处理后,再将请求信息发送到Server进程,调用相应的Method方法去执行某些操作,然后将操作结果返回给Client进程。 - 信号广播
Server进程可以主动发送一些信号(Signal),Client进程可以选择注册感兴趣的信号。当Server发送信号时,消息同样会先发送到"Bus"总线上,如果Client进程正好注册接受该类型的信号,信号就会发送到Client进程上。
2. 生成D-Bus interface
实现Client和Server 进程通信的第一步,就是要定义好通信的接口。GDBus通信接口是在xml文件下描述的,然后通过gdbus-codegen工具就可以自动生成对应D-Bus interface源文件和头文件了。
Interface.xml:
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="UTF-8"?> <node> <interface name="com.gdbus.demo"> <method name="SetData"> <arg name="data" type="s" direction="in"/> <arg name="response" type="s" direction="out"/> </method> <signal name="SendSignal"> <arg name="sig" type="i"/> </signal> </interface> </node> |
这里我们将interface name定义为"com.gdbus.demo",然后分别定义了一个method方法和一个signal方法。
arg name表示参数名字,type表示参数类型,direction in和out分别表示入参和出参。
这里xml中定义的参数类型都是GVariant类型的,对于每个字符代表的含义,可以参照下表。
| GVariant数据类型 | 代表含义 |
|---|---|
| b:G_VARIANT_TYPE_BOOLEAN的类型字符串 | 布尔值。 |
| y:G_VARIANT_TYPE_BYTE的类型字符串 | 一个字节。 |
| n:G_VARIANT_TYPE_INT16的类型字符串; | 有符号的16位整数。 |
| q:G_VARIANT_TYPE_UINT16的类型字符串 | 一个无符号的16位整数。 |
| i:G_VARIANT_TYPE_INT32的类型字符串 | 有符号的32位整数。 |
| u:G_VARIANT_TYPE_UINT32的类型字符串 | 一个无符号的32位整数。 |
| x:G_VARIANT_TYPE_INT64的类型字符串 | 有符号的64位整数。 |
| t:G_VARIANT_TYPE_UINT64的类型字符串 | 一个无符号的64位整数。 |
| h:G_VARIANT_TYPE_HANDLE的类型字符串 | 一个有符号的32位值,按照惯例,该值用作与D-Bus消息一起发送的文件描述符数组的索引。 |
| d:G_VARIANT_TYPE_DOUBLE的类型字符串 | 双精度浮点值。 |
| s:G_VARIANT_NEW_STRING的类型字符串 | 一个字符串。 |
| o:G_VARIANT_TYPE_OBJECT_PATH的类型字符串 | D-Bus对象路径形式的字符串。 |
| g:G_VARIANT_TYPE_SIGNATURE的类型字符串 | D-Bus类型签名形式的字符串。 |
| ?:G_VARIANT_TYPE_BASIC的类型字符串 | 一个不确定类型,它是任何基本类型的超类型。 |
| v:G_VARIANT_TYPE_VARIANT的类型字符串 | 包含任何其他类型的值的容器类型。 |
| a:用作另一个类型字符串的前缀,表示该类型的数组 | 例如,类型字符串“ ai”是带符号的32位整数数组的类型。 |
| m:用作另一个类型字符串的前缀,表示该类型的“也许”或“可空”版本 | 例如,类型字符串“ ms”是可能包含字符串或不包含任何值的值的类型。 |
| ():用于封装零个或多个其他串联类型的字符串以创建元组类型 | 例如,类型字符串“(is)”,是整数和字符串对的类型。 |
| r:G_VARIANT_TYPE_TUPLE的类型字符串 | 一个不定类型,它是任何元组类型的超类型,而与元素数无关。 |
定义完这个xml文件,我们只需要在终端输入以下命令,就可以生成其对应的源文件了:
gdbus-codegen --generate-c-code=gdbusdemo_gen Interface.xml
3.GDBus interface源文件详解
自己在学习GDBus的时候,看过很多篇文章,对gdbus-codegen生成的D-Bus interface源文件,很多文章都是一带而过,由于后面编写Server和Client部分的代码都需要用到这些接口,如果不理解的话,代码写起来也是一头雾水,只能去网上复制别人的代码,到最后也不知道实现过程和原理。。 为了加深对GDBus的理解,笔者自己去研究了一下,下面带大家看一看生成的这些文件中到底有什么神秘的东西。
3.1
首先,你会看到源文件中定义了如下结构体(Introspection data):

让我们来看一下这些结构体的具体组成:
_ExtendedGDBusArgInfo:
1 2 3 4 5 6 7 8 9 10 | static const _ExtendedGDBusArgInfo _com_gdbus_demo_method_info_set_name_IN_ARG_name = { { -1, (gchar *) "name", (gchar *) "s", NULL }, FALSE }; |
可以看到,_ExtendedGDBusArgInfo这个结构体中又嵌套了一个GDBusArgInfo结构体:
1 2 3 4 5 6 | typedef struct { volatile gint ref_count; gchar *name; gchar *signature; GDBusAnnotationInfo **annotations; } GDBusArgInfo; |
其主要用来说明我们定义的Method或者Signal的参数。
_ExtendedGDBusMethodInfo:
1 2 3 4 5 6 7 8 9 10 11 12 | static const _ExtendedGDBusMethodInfo _com_gdbus_demo_method_info_set_name = { { -1, (gchar *) "SetName", (GDBusArgInfo **) &_com_gdbus_demo_method_info_set_name_IN_ARG_pointers, (GDBusArgInfo **) &_com_gdbus_demo_method_info_set_name_OUT_ARG_pointers, NULL }, "handle-set-name", FALSE }; |
在_ExtendedGDBusMethodInfo中嵌套了一个GDBusMethodInfo结构体:
1 2 3 4 5 6 7 | typedef struct { volatile gint ref_count; gchar *name; GDBusArgInfo **in_args; GDBusArgInfo **out_args; GDBusAnnotationInfo **annotations; } GDBusMethodInfo; |
其主要用来说明D-Bus interface的Method方法,name表示方法的名字,**in_args 、**out_args分别表示指向入参和出参结构体的指针。
_ExtendedGDBusSignalInfo:
1 2 3 4 5 6 7 8 9 10 | static const _ExtendedGDBusSignalInfo _com_gdbus_demo_signal_info_send_signal = { { -1, (gchar *) "SendSignal", (GDBusArgInfo **) &_com_gdbus_demo_signal_info_send_signal_ARG_pointers, NULL }, "send-signal" }; |
在_ExtendedGDBusSignalInfo中嵌套了一个GDBusSignalInfo结构体:
1 2 3 4 5 6 | typedef struct { volatile gint ref_count; gchar *name; GDBusArgInfo **args; GDBusAnnotationInfo **annotations; } GDBusSignalInfo; |
其主要用来说明D-Bus interface的Signal方法,name表示信号的名字,**args表示指向参数结构体的指针。
所有的_ExtendedGDBusMethodInfo和_ExtendedGDBusSignalInfo会分别存放到method_info_pointers[]和signal_info_pointers[]
这两个指针数组中。
1 2 3 4 5 | static const _ExtendedGDBusSignalInfo * const _com_gdbus_demo_signal_info_pointers[] = { &_com_gdbus_demo_signal_info_send_signal, NULL }; |
随后又定义了一个_ExtendedGDBusInterfaceInfo结构体,在这个结构体中,存放了我们上面提到的那两个指针数组。
这个结构体就是针对于我们xml文件生成的D-Bus interface的一个整体说明,把所有Methond、Signal及其参数都关联到了这一个结构体中。
1 2 3 4 5 6 7 8 9 10 11 12 | static const _ExtendedGDBusInterfaceInfo _com_gdbus_demo_interface_info = { { -1, (gchar *) "com.gdbus.demo.", (GDBusMethodInfo **) &_com_gdbus_demo_method_info_pointers, (GDBusSignalInfo **) &_com_gdbus_demo_signal_info_pointers, NULL, NULL }, "com-gdbus-demo", }; |
3.2
接下来,你会看到一堆这样的函数接口:

看完下面这些内容,相信大家应该就会对D-Bus的通信的实现过程有一定了解了。首先,com_gdbus_demo_default_init () 通过 g_signal_new() 定义了用于传入D-Bus方法调用的GObject信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | static void com_gdbus_demo_default_init (ComGdbusDemoIface *iface) { /* GObject signals for incoming D-Bus method calls: */ /** * ComGdbusDemo::handle-set-name: * @object: A #ComGdbusDemo. * @invocation: A #GDBusMethodInvocation. * @arg_name: Argument passed by remote caller. * * Signal emitted when a remote caller is invoking the <link linkend="gdbus-method-com-gdbus-demo-.SetName">SetName()</link> D-Bus method. * * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call com_gdbus_demo_complete_set_name() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. * * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. */ g_signal_new ("handle-set-name", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ComGdbusDemoIface, handle_set_name), g_signal_accumulator_true_handled, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); /* GObject signals for received D-Bus signals: */ /** * ComGdbusDemo::send-signal: * @object: A #ComGdbusDemo. * @arg_sig: Argument. * * On the client-side, this signal is emitted whenever the D-Bus signal <link linkend="gdbus-signal-com-gdbus-demo-.SendSignal">"SendSignal"</link> is received. * * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. */ g_signal_new ("send-signal", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ComGdbusDemoIface, send_signal), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT); } |
由于我们xml中只定义了一个method和一个signal,所以这里只通过g_signal_new() 生成两个信号:"handle-set-name"和 “send-signal”。
- 当Client端远程调用D-Bus上的SetName方法的时候,就会触发
"handle-set-name"信号,D-Bus收到这个信号后,就会通知Server去采取相应处理。 - 每当D-Bus接收到Sever发来的SendSignal信号时,就会给Client发送"send-signal",这样Client就接收到了Server广播的信号。
还剩下一些接口,我把它们分为了以下两类:
- 对于xml中定义的signal,其接口会抽象为 xxx_emit_xxx() 的形式(com_gdbus_demo_emit_send_signal),内部实现是通过调用 g_signal_emit_by_name() 来发送一个信号。
1 2 3 4 5 6 7 | void com_gdbus_demo_emit_send_signal ( ComGdbusDemo *object, gint arg_sig) { g_signal_emit_by_name (object, "send-signal", arg_sig); } |
- 对于xml中定义的method方法,其接口会抽象为以下三种形式:xxx_call_xxx()、 xxx_call_xxx_finish() 、xxx_call_xxx_sync(),这三者之间有什么联系和区别呢?对于 xxx_call_xxx() 和xxx_call_xxx_sync(),前者是通过 g_dbus_proxy_call() 接口来异步调用proxy上的method方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void com_gdbus_demo_call_set_name ( ComGdbusDemo *proxy, const gchar *arg_name, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_dbus_proxy_call (G_DBUS_PROXY (proxy), "SetName", g_variant_new ("(s)", arg_name), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } |
g_dbus_proxy_call 的倒数第二个参数(callback)是请求成功时调用的callback函数,如果你关心调用结果如何,可以给他注册一个GAsyncReadyCallback (),在这个函数里调用xxx_call_xxx_finish() (函数内部调用g_dbus_proxy_call_finish()接口来完成由g_dbus_proxy_call()开始的操作,并获取返回值。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | gboolean com_gdbus_demo_call_set_name_finish ( ComGdbusDemo *proxy, gchar **out_response, GAsyncResult *res, GError **error) { GVariant *_ret; _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); if (_ret == NULL) goto _out; g_variant_get (_ret, "(s)", out_response); g_variant_unref (_ret); _out: return _ret != NULL; } |
如果你并不关心调用结果,那么用NULL就可以。注意,GAsyncReadyCallback接口不可以随便定义,必须按照以下形式进行定义:
1 2 3 4 | void (*GAsyncReadyCallback) (GObject *source_object, GAsyncResult *res, gpointer user_data); |
xxx_call_xxx_sync() 是通过g_dbus_proxy_call_sync() 来同步调用proxy上的method方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | gboolean com_gdbus_demo_call_set_name_sync ( ComGdbusDemo *proxy, const gchar *arg_name, gchar **out_response, GCancellable *cancellable, GError **error) { GVariant *_ret; _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "SetName", g_variant_new ("(s)", arg_name), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (_ret == NULL) goto _out; g_variant_get (_ret, "(s)", out_response); g_variant_unref (_ret); _out: return _ret != NULL; } |
xxx_complete_xxx() 与xxx_call_xxx_sync() 对应,在Client调用调用xxx_call_xxx_sync() 后,还需要在Server调用xxx_complete_xxx() 来获得返回值并结束同步方法的调用。
对于g_dbus_proxy_call()这些接口的具体讲解,这里就不展开说明了,内容实在太多了,感兴趣的同学可以自己从这个链接学习一下。
3.3
最后还剩下一些关于proxy 和skeleton的接口,常用的应该就是xxx_proxy_new_sync() 和xxx_skeleton_new()。
xxx_proxy_new_sync() 是为D-Bus interface同步创建一个代理使用的。
xxx_skeleton_new() 是为D-Bus创建框架对象使用的。
对于其他接口,感兴趣的朋友可以自己再研究一下,通过看注释以及参考官方文档应该都能明白,后续有时间的话,我可能也会研究整理一下。
有了前面这些内容的了解,接下来就是该考虑如何利用这些接口去编写Client和Server的代码了。
4. Client端编程
首先来明确一下,我们需要在Client实现的两个最重要的内容:
- 同步创建一个proxy,将Server进程发来的signal信号与proxy连接,并在回调函数中根据signal采取相应的处理。
- 主动发送请求,向Server进程调用method方法。
下面来看一下Client进程具体代码思路:
- 调用g_main_loop_new ()接口创建一个新的GMainLoop(表示GLib或GTK +应用程序的主事件循环)。
1 | pLoop = g_main_loop_new(NULL,FALSE); |
这里我们先创建,并不开启(将 g_main_loop_new () 第二个入参设为false),后面通过调用g_main_loop_run() 再来开启循环。
- 同步创建一个proxy
1 2 3 4 5 6 | pProxy = com_gdbus_demo_proxy_new_sync(pConnection, G_DBUS_PROXY_FLAGS_NONE, COM_GDBUS_DEMO_NAME, COM_GDBUS_DEMO_OBJECT_PATH, NULL, &pProxyError); |
- 将signal信号与proxy连接,并在回调函数中根据收到的信号采取相应的处理。
1 | g_signal_connect(pProxy, "send-signal", G_CALLBACK(sendSignalHandler), NULL); |
- 向D-Bus发送信号,主动调用Server的method方法
1 | com_gdbus_demo_call_set_name_sync (pProxy, in_arg, out_arg, NULL, pError); |
- 最后调用g_main_loop_run()接口开启GMainLoop。
1 | g_main_loop_run( pLoop ); |
5. Server端编程
同样,首先来明确一下,我们需要在Server实现的两个最重要的内容:
- 创建一个skeleton(skeleton可以理解为Server端的D-Bus 接口),将skeleton与method信号连接,并在回调函数中具体完成Method的实现。
- 主动向Client端发送Signal信号。
Server进程具体代码思路如下:
- 调用g_main_loop_new ()接口创建一个新的GMainLoop。
1 | pLoop = g_main_loop_new(NULL,FALSE); |
- 调用g_bus_own_name ()接口连接上D-Bus,获取总线上的名字。
1 2 3 4 5 6 7 8 9 | guint own_id = g_bus_own_name (COM_GDBUS_DEMO_BUS, COM_GDBUS_DEMO_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, &GBusAcquired_Callback, &GBusNameAcquired_Callback, &GBusNameLost_Callback, NULL, NULL); |
这上面提到了三个非常重要的回调函数,GBusAcquiredCallback、GBusNameAcquiredCallback、GBusNameLostCallback。下面我们来讲一下,这三个函数分别在什么时候被调用,以及在这三个回调函数中应该分别实现什么功能。
- 如果无法建立与总线的连接会调用name_lost_handler()
- 如果无法获取 bus name,会先调用bus_acquired_handler(),再调用name_lost_handler()
- 如果已获得 bus name ,会先调用bus_acquired_handler(),再调用name_acquired_handler()
2.1 GBusAcquiredCallback()
当获得到与消息总线的连接时会被调用,在这个函数里,我们要实现以下内容:首先要为D-Bus interface创建一个skeleton对象:
1 | pSkeleton = com_gdbus_demo_skeleton_new(); |
然后将调用g_signal_connect() 接口将信号与对应的callback函数连接:
1 | (void) g_signal_connect(pSkeleton, "handle-set-name", G_CALLBACK(setName), NULL); |
将pSkeleton连接到D-Bus路径上。
1 2 3 4 | (void) g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(pSkeleton), connection, COM_GDBUS_DEMO_OBJECT_PATH, &pError); |
还应该有一个函数主动向Client发送信号
1 | g_timeout_add(2000, (GSourceFunc)sendSignal, NULL); |
2.2 GBusNameAcquiredCallback
这个函数会在成功获取到Bus name的时候被调用。
2.3 GBusNameLostCallback ()
当获取到Bus name失败的时候会被调用,在这里我们应该打印相应的Error log,出问题的时候方便我们排查错误原因。此时还应该调用g_main_loop_quit() 来退出循环。
- 最后调用g_main_loop_run() 开启GMainLoop。
1 | g_main_loop_run( pLoop ); |
到这里,关于GDBus的讲解就全部结束了,有理解错误的地方欢迎大家指出。笔者最后自己写了一个Demo,带大家来看一下运行效果:

部分源码已在文章中提到,完整工程代码已经上传到了Github上,想学习交流的朋友们欢迎下载。
完整代码:https://github.com/Pedal2Metal/GDBus