为什么即使callback参数与XML中的callback参数不匹配,GObject方法仍然会被调用?

假设我有这样的方法

<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()仍然在child_test_set_age()调用,即使callback参数与我的XML中指定的参数不匹配? 例如,如果我添加guint ageIn之后的另一个参数,就像一个char*guint或其他一些随机types?

我注意到,如果DBus函数包含方向为OUT的成员,这将不起作用。 似乎任何不必要的INtypes的参数都会被丢弃,并且通常是这样完成的。

虽然我相信这没有什么区别,但我使用D-BUS绑定工具0.94,glib-2.30.0和dbus-glib 0.94。

由于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调用约定 (技术上依赖于编译器和架构的实现细节!)

因为函数指针的大小都是一样的,所以把它们全部转换为void (*callback)(void) ,这就是GCallback是一个typedef。 GCallback用于所有的GLib平台API,用于回调,可以有可变的数量和类型的参数。 这就是为什么你必须在上面的代码示例child_test_set_ageGCallback

但即使你可以传递函数指针,就好像它们都是一样的,你怎么确定这些函数实际上得到了他们的参数呢? 这就是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中的child_test_set_age(ChildTest *childTest, guint ageIn, guint otherNumberIn, GError **error)签名不同,比如说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 ** 。 如果足够幸运的话,指针是NULL ,那么otherNumberIn将等于0,否则它将成为一个内存位置投射到一个整数:垃圾。

第四个参数是特别危险的,因为它会尝试从堆栈中读取某些东西,而您不知道那里有什么。 所以,如果你尝试访问error指针,你可能会崩溃你的程序与段error

然而,如果你设法通过没有segfault的函数,那么在函数返回后,调用者会整齐地从堆栈中弹出三个参数,一切都将恢复正常。 所以这就是为什么你的程序似乎仍然工作。

“out”参数是使用指针参数实现的,所以这是他们不工作的原因。 该函数几乎保证写入无效的内存地址,因为指针是垃圾。

总之,如果下列条件成立,你的函数可以有不同的签名:

  • 你的编译器使用C调用约定
  • 你的函数具有与调用者所期望的相同数量的参数(或更少)
  • 你的参数和调用者期望的参数大小相同
  • 在调用调用者期望推送的参数时,您的参数类型是有意义的