为啥即使回调参数与 XML 中的参数不匹配,GObject 方法仍会被调用?
Posted
技术标签:
【中文标题】为啥即使回调参数与 XML 中的参数不匹配,GObject 方法仍会被调用?【英文标题】:Why does GObject method still get called even if callback arguments don't match those in XML?为什么即使回调参数与 XML 中的参数不匹配,GObject 方法仍会被调用? 【发布时间】:2014-04-19 14:27:33 【问题描述】:假设我有这样的方法
<interface name="org.Test.ChildTest">
<!-- set_age(guint32 new_age): sets new age -->
<method name="set_age">
<arg type="u" name="new_age" direction="in"/>
</method>
在我的方法表中:
(GCallback) child_test_set_age, dbus_glib_marshal_child_test_BOOLEAN__UINT_POINTER, 0
正确的 GObject 方法签名是:
gboolean
child_test_set_age (ChildTest *childTest, guint ageIn, GError** error)
为什么我的方法child_test_set_age()
仍然在 DBus 上被调用,即使回调参数与我的 XML 中指定的参数不匹配?例如,如果我在guint ageIn
之后添加另一个参数,例如char*
或guint
或其他一些随机类型?
我注意到如果 DBus 函数包含方向为 OUT 的成员,这将不起作用。似乎任何不必要的 IN 类型参数都会被丢弃,并且调用照常完成。
虽然我相信这没有任何区别,但我使用的是 D-BUS Binding Tool 0.94、glib-2.30.0 和 dbus-glib 0.94。
【问题讨论】:
【参考方案1】:您发现了一个由于 C 语言而存在的有趣细节。 C 中的函数是强类型的,所以严格来说,你必须有函数来处理每一种可能的回调类型,就像下面的噩梦一样:
g_signal_connect_callback_void__void(GObject *object, gchar *signal,
void (*callback)(GObject *, gpointer), gpointer data);
g_signal_connect_callback_void__guint(GObject *object, gchar *signal,
void (*callback)(GObject *, guint, gpointer), gpointer data);
g_signal_connect_callback_gboolean__gdkevent(GObject *object, gchar *signal,
gboolean (*callback)(GObject *, GdkEvent *, gpointer), gpointer data);
幸运的是,C 语言的两个特性可以避免这种混乱。
无论函数的返回类型和参数如何,函数指针都保证大小相同。 C calling convention(技术上是依赖于编译器和架构的实现细节!)由于函数指针的大小都相同,因此可以将它们全部转换为void (*callback)(void)
,这就是GCallback
的类型定义。 GCallback
在所有 GLib 平台 API 中用于可以具有可变数量和类型的参数的回调。这就是为什么您必须在上面的代码示例中将child_test_set_age
转换为GCallback
。
但是即使你可以传递函数指针,就好像它们都是一样的,你如何确保函数真正得到它们的参数?这就是 C 调用约定的用途。编译器生成代码,使得调用者将函数的参数推入堆栈,函数从堆栈中读取参数但不弹出它们,当它返回时,调用者将参数从堆栈中弹出。所以调用者可以推送与函数预期不同数量的参数,只要函数可以找到它尝试访问的所有参数!
让我们用你的例子来说明这一点:调用方法child_test_set_age(ChildTest *childTest, guint ageIn, GError **error)
。假设您的平台上的指针和整数大小相同,我将跳过一些细节以了解总体思路。
调用者将参数放入堆栈:
+------------+
| &childTest | arg1
+------------+
| 25 | arg2
+------------+
| NULL | arg3
+------------+
...并调用该函数。该函数获取该堆栈并在那里查找它的参数:
+------------+
| &childTest | ChildTest *childTest
+------------+
| 25 | guint ageIn
+------------+
| NULL | GError **error
+------------+
一切都很好。然后函数返回,调用者从堆栈中弹出参数。
但是,现在,如果您为函数指定与 XML 中的 DBus 签名不同的类型,例如 child_test_set_age(ChildTest *childTest, guint ageIn, guint otherNumberIn, GError **error)
,假设相同的参数被压入堆栈,但您的函数对它们的解释不同:
+------------+
| &childTest | ChildTest *childTest ...OK so far
+------------+
| 25 | guint ageIn ...still OK
+------------+
| NULL | guint otherNumberIn ...will be 0 if you try to read it, but OK
+------------+
| undefined | GError **error ...will be garbage!
| behavior |
| land!! |
| ... |
前两个参数没问题。第三个,因为 DBus 不知道你期待另一个guint
,所以将GError **
转换为guint
。如果你足够幸运,那个指针是NULL
,那么otherNumberIn
将等于0,否则它将是一个转换为整数的内存位置:垃圾。
第四个参数特别危险,因为它会尝试从堆栈中读取一些东西,而你不知道那里有什么。因此,如果您尝试访问 error
指针,您可能会因段错误而导致程序崩溃。
但是,如果你设法在没有段错误的情况下通过函数,那么调用者会在函数返回后巧妙地从堆栈中弹出三个参数,一切都会恢复正常。所以这就是为什么你的程序似乎仍然有效。
“out”参数是使用指针参数实现的,这就是它们不起作用的原因;该函数几乎可以保证写入无效的内存地址,因为指针是垃圾。
总之,如果满足以下条件,您的函数可以具有不同的签名:
您的编译器使用 C 调用约定 您的函数的参数数量与调用者预期的相同(或更少) 每个参数的大小都与调用者期望推送的参数大小相同 在转换调用者期望推送的参数时,您的参数类型是有意义的【讨论】:
以上是关于为啥即使回调参数与 XML 中的参数不匹配,GObject 方法仍会被调用?的主要内容,如果未能解决你的问题,请参考以下文章
jQuery中的getJSON方法的url参数中,为啥加上callback=
Rails 6 - 创建一个回调以验证输入的特定文本的问题。
gob:类型不匹配:没有匹配的字段编译解码器 - Golang