为啥即使回调参数与 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

为啥 PhpStorm 会报“参数类型不匹配”错误?

为啥我在 Luminus (Clojure) 中看到参数不匹配错误?

Go:将 gob 与 zmq4 一起使用