如何让 g_dbus_connection_signal_subscribe 函数告诉我有关预先存在的对象/接口的信息?
Posted
技术标签:
【中文标题】如何让 g_dbus_connection_signal_subscribe 函数告诉我有关预先存在的对象/接口的信息?【英文标题】:How can I get the g_dbus_connection_signal_subscribe function to tell me about pre-existing objects/interfaces? 【发布时间】:2019-06-05 11:47:57 【问题描述】:函数 g_dbus_connection_signal_subscribe 非常适合告诉我新的 DBus 对象何时出现(或消失)并带有 InterfacesAdded 信号(或 InterfacesRemoved 信号)。但我需要了解预先存在的对象/接口。
我编写了以下一段 C 代码,用于在从总线添加/删除 DBus 对象时提供回调。为简单起见省略了错误检查。
#include <stdio.h>
#include <stdlib.h>
#include <gio/gio.h>
static void signal_cb(GDBusConnection *connection,
const gchar *sender_name,const gchar *object_path,
const gchar *interface_name,const gchar *signal_name,
GVariant *parameters,gpointer user_data)
printf("%s: %s.%s %s\n",object_path,interface_name,signal_name,
g_variant_print(parameters,TRUE));
int main(int argc,char *argv[])
GDBusConnection *c;
GMainLoop *loop;
int filter_id;
c = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err);
loop = g_main_loop_new(NULL,0);
filter_id = g_dbus_connection_signal_subscribe(c,
"org.bluez",NULL,NULL,NULL,NULL,
G_DBUS_SIGNAL_FLAGS_NONE,signal_cb,NULL,NULL);
g_main_loop_run (loop);
g_main_loop_unref (loop);
exit(0);
所以我要做的是跟踪树的 org.bluez 分支下存在的所有 DBus 对象。 (这些代表可插拔蓝牙控制器和每个控制器发现的设备)。我需要了解在我的程序启动之前已经存在的 DBus 对象,并且我需要了解在我的程序启动后出现的新对象。
我上面的代码告诉我新对象,但没有告诉我已经存在的对象。 gdbus API 中有没有办法为已经存在的对象获取“InterfacesCreated”信号?我想可以读取整个 DBus 对象层次结构,然后订阅更改,但这会导致竞争条件,如果在我读取对象层次结构和订阅时间之间出现对象,那么我会错过这些对象......
使用 gdbus API 完成此任务的最佳实践方法是什么?
【问题讨论】:
你看过自省函数吗? developer.gnome.org/gio//2.50/gio-D-Bus-Introspection-Data.html 这些函数用于询问您已经知道名称的节点的详细信息。我需要真正发现存在哪些节点名称。此外,即使它们可以提供对存在哪些节点的发现,它也会导致两条完全独立的代码路径来执行相同的操作:一条代码路径用于发现预先存在的节点并查询它们,另一条代码路径用于发现新节点被添加。这似乎是一个糟糕的 API 设计,肯定有更好的方法。 @deltamind106 信号用于在程序启动后出现时为您提供提示。通过获取现有对象/接口,您需要在org.freedesktop.DBus.ObjectManager
中使用GetManagedObjects
进行扫描。在扫描现有接口之前注册信号处理程序,因此当出现新接口时,您将收到信号回调通知。但是您需要保持同步或管理代码中的对象/接口重复。
@Parthiban 这正是我最终要做的:从 org.freedesktop.DBus.ObjectManager 接口调用 GetManagedObjects。通过简单地检查文档,这远非直观。我通过查看 bluetoothctl 实用程序的源代码弄清楚了,它基本上必须做我想做的同样的事情。 GetManagedObjects 的返回值是一个三重嵌套的字典,使用 gdbus GVariant 调用遍历它很痛苦,这主要是由于文档极差。
@jku 好吧,只要您进行异步 gdbus 调用...猜猜怎么着?库最终调用以向您提供您请求的结果的回调是....等待它....从不同的线程调用。所以不,gdbus 库中存在异步调用这一事实并不能神奇地消除处理由多线程引起的竞争条件的需要。当然您的代码不需要创建其他线程,但库会在幕后为您完成。
【参考方案1】:
如果有人遇到这种情况,正如 Parthiban 所说,解决方案(对于实现 org.freedesktop.DBus.ObjectManager
接口的 D-Bus 服务,例如 BlueZ)是调用 GetManagedObjects
方法。使用 GDBus 用 C 编写代码是非常痛苦的,因为它需要详细了解 GVariant
类型;见documentation。另请查看GVariant
数据类型字符串上的docs。但它是这样完成的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <gio/gio.h>
/* The GVariant must be of type "asasv" (array of interfaces, where each */
/* interface has an array of properties). */
/* This type what DBus provides with InterfacesAdded signal and is also */
/* part of the return value from the GetManagedObjects method. */
static void proc_interface_var(const gchar *objpath,GVariant *iflist)
GVariantIter *iter,*iter2;
gchar *ifname,*propname,*proptext;
GVariant *propvalue;
g_variant_get(iflist,"asasv",&iter);
while (g_variant_iter_loop(iter,"sasv",&ifname,&iter2))
if (strcmp(ifname,"org.bluez.Adatper1") != 0 &&
strcmp(ifname,"org.bluez.Device1") != 0)
/* we only care about the Adatper1 and Device1 interfaces */
continue;
/* if */
printf("Interface %s added to object %s\n",ifname,objpath);
while (g_variant_iter_loop(iter2,"sv",&propname,&propvalue))
proptext = g_variant_print(propvalue,0);
printf("\t%s=%s\n",propname,proptext);
g_free(proptext);
/* while */
/* while */
return;
/* proc_interface_var */
/* The GVariant must be of type "asv" (an array of properties). */
static void proc_property_var(const gchar *objpath,
gchar *ifname,GVariant *proplist)
GVariantIter *iter;
gchar *propname,*proptext;
GVariant *propvalue;
g_variant_get(proplist,"asv",&iter);
while (g_variant_iter_loop(iter,"sv",&propname,&propvalue))
proptext = g_variant_print(propvalue,0);
printf("\tProperty changed on object %s interface %s: %s=%s\n",
objpath,ifname,propname,proptext);
g_free(proptext);
/* while */
return;
/* proc_property_var */
static void signal_cb(GDBusConnection *c,
const gchar *sender_name,const gchar *object_path,
const gchar *interface_name,const gchar *signal_name,
GVariant *parameters,gpointer user_data)
char fullsignal[200];
gchar *s,*objpath,*ifname,*propname,*proptext;
GVariant *ifvar,*propvalue;
GVariantIter *iter,*iter2;
snprintf(fullsignal,200,"%s.%s",interface_name,signal_name);
if (strcmp(fullsignal,
"org.freedesktop.DBus.ObjectManager.InterfacesAdded") == 0)
g_variant_get(parameters,"(o*)",&objpath,&ifvar);
proc_interface_var(objpath,ifvar);
else if (strcmp(fullsignal,
"org.freedesktop.DBus.Properties.PropertiesChanged") == 0)
g_variant_get(parameters,"(s*as)",&ifname,&propvalue,&iter2);
proc_property_var(object_path,ifname,propvalue);
while (g_variant_iter_loop(iter2,"s",&propname))
printf("\tProperty changed on object %s interface %s: "
"%s is nil\n",object_path,ifname,propname);
/* while */
else
printf("Ignoring unsupported signal for object %s, "
"signal=%s.%s, param type=%s\n",
object_path,interface_name,signal_name,
g_variant_get_type_string(parameters));
s = g_variant_print(parameters,TRUE);
printf("Unsupported signal: parameters %s\n",s);
g_free(s);
/* else */
return;
/* signal_cb */
static void bt_discover(GDBusConnection *c,const char *ctlname,int on_off)
GError *err=NULL;
GVariant *result;
const char *method;
char ctlpath[80];
snprintf(ctlpath,80,"/org/bluez/%s",ctlname);
method = on_off ? "StartDiscovery" : "StopDiscovery";
result = g_dbus_connection_call_sync(c,"org.bluez",ctlpath,
"org.bluez.Adapter1",method,NULL,
G_VARIANT_TYPE("()"), /* return-type */
G_DBUS_CALL_FLAGS_NONE,5000,NULL,&err);
if (result==NULL)
if (err) fprintf(stderr,"g_dbus_connection_call error: %s\n",
err->message);
exit(1);
/* if */
g_variant_unref(result);
return;
/* bt_discover */
static void *receive_dbus_signals(void *arg)
GMainLoop *loop;
printf("Receiving DBus signals...\n");
loop = g_main_loop_new(NULL,0);
g_main_loop_run(loop);
g_main_loop_unref(loop);
return NULL;
/* receive_dbus_signals */
int main(int argc,char *argv[])
GError *err=NULL;
GVariant *result,*ifvar;
GVariantIter *iter;
GDBusConnection *c;
GDBusNodeInfo *node;
gchar *objpath;
pthread_t handle;
int filter_id;
if ((c = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err)) == NULL)
if (err) fprintf(stderr,"g_bus_get error: %s\n",err->message);
exit(1);
/* if */
filter_id = g_dbus_connection_signal_subscribe(c,
"org.bluez",NULL,NULL,NULL,NULL,
G_DBUS_SIGNAL_FLAGS_NONE,signal_cb,NULL,NULL);
if (pthread_create(&handle,NULL,receive_dbus_signals,NULL) != 0)
fprintf(stderr,"Failed to create DBus listen thread\n");
exit(1);
/* if */
result = g_dbus_connection_call_sync(c,"org.bluez","/",
"org.freedesktop.DBus.ObjectManager","GetManagedObjects",NULL,
G_VARIANT_TYPE("(aoasasv)"), /* return-type */
G_DBUS_CALL_FLAGS_NONE,5000,NULL,&err);
if (result==NULL)
if (err) fprintf(stderr,"g_dbus_connection_call error: %s\n",
err->message);
exit(1);
/* if */
g_variant_get(result,"(aoasasv)",&iter);
/* below we replace 'asasv' with '*' to get it as a GVariant */
while (g_variant_iter_loop(iter,"o*",&objpath,&ifvar))
proc_interface_var(objpath,ifvar);
/* while */
g_variant_unref(result);
bt_discover(c,"hci0",1);
sleep(5);
bt_discover(c,"hci0",0);
sleep(5);
exit(0);
对于不实现org.freedesktop.DBus.ObjectManager
接口的D-Bus服务,您需要使用D-Bus introspection并解析自省XML以查找现有对象节点的路径。 p>
【讨论】:
注意:除非您提交 upstream bug report 关于它的“极差”文档,并提供要解决的具体问题或如何解决这些问题的建议,否则不会得到改进。 我改进了您的答案,以提及当服务未实现org.freedesktop.DBus.ObjectManager
接口时会发生什么。
是的,我之前尝试过提交报告以改进文档,但总是无济于事。问题不在于人们需要被告知文档需要改进的地方,每个人都已经知道了。问题是没有资源去做。这并不是说这里和那里都有一些任何人都可以修复的错别字——文档需要加倍,并提供更多解释和更多示例代码。开发人员确实是唯一能够充分编写此类文档的人,他们正忙于修复代码中的错误并满足改进要求。
开发人员(像我一样)也最不知道哪些文档是不合适的,因为他们非常熟悉 API,以至于他们从不阅读文档;即使他们这样做了,他们也会在精神上填补其中的任何空白。我们可以根据用户对文档问题的具体反馈采取行动。 :-)以上是关于如何让 g_dbus_connection_signal_subscribe 函数告诉我有关预先存在的对象/接口的信息?的主要内容,如果未能解决你的问题,请参考以下文章