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

Ale*_*x C 3 c linux dbus glib calling-convention

假设我有这样的方法

<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>
Run Code Online (Sandbox Code Playgroud)

在我的方法表中,我有:

{ (GCallback) child_test_set_age, dbus_glib_marshal_child_test_BOOLEAN__UINT_POINTER, 0 }
Run Code Online (Sandbox Code Playgroud)

和正确的GObject方法签名是:

gboolean
child_test_set_age (ChildTest *childTest, guint ageIn, GError** error)
Run Code Online (Sandbox Code Playgroud)

为什么child_test_set_age()即使回调参数与我的XML中指定的回调参数不匹配,我的方法仍会在DBus上调用?例如,如果我之后添加另一种说法guint ageIn,像一个char*guint或其他一些随机类型?

我注意到如果DBus函数包含方向为OUT的成员,这将不起作用.似乎任何类型IN的不必要的参数都会被丢弃,并且调用会像往常一样完成.

虽然我认为这没有任何区别,但我使用的是D-BUS绑定工具0.94,glib-2.30.0和dbus-glib 0.94.

pto*_*ato 6

你已经找到了因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);
Run Code Online (Sandbox Code Playgroud)

幸运的是,C语言的两个功能可以避免这种混乱.

  • 无论函数的返回类型和参数如何,函数指针都保证大小相同.
  • C调用约定(技术上编译器依赖性和体系结构相关的实施细节!)

由于函数指针的大小都相同,所以可以将它们全部转换为void (*callback)(void),这就是GCallbacktypedef.GCallback在所有GLib平台API中用于可以具有可变数量和类型的参数的回调.这就是为什么你要投child_test_set_ageGCallback你的代码示例以上.

但即使你可以传递函数指针就好像它们都是一样的,你如何确保函数实际得到它们的参数?这就是C调用约定的用途.编译器生成代码,调用者将函数的参数推送到堆栈,函数从堆栈中读取参数但不弹出它们,当它返回时,调用者将参数从堆栈中弹出.因此,只要函数可以找到它试图访问的所有参数,调用者就可以推送不同于函数所需的参数数量!

让我们用你的例子来说明:调用方法child_test_set_age(ChildTest *childTest, guint ageIn, GError **error).让我们假设你的平台上的指针和整数大小相同,我将跳过一些细节,以便了解整体思路.

调用者将参数放入堆栈:

+------------+
| &childTest |   arg1
+------------+
| 25         |   arg2
+------------+
| NULL       |   arg3
+------------+
Run Code Online (Sandbox Code Playgroud)

...并调用该函数.该函数获取该堆栈并在那里查找其参数:

+------------+
| &childTest |   ChildTest *childTest
+------------+
| 25         |   guint ageIn
+------------+
| NULL       |   GError **error
+------------+
Run Code Online (Sandbox Code Playgroud)

一切都好.然后函数返回,调用者弹出堆栈中的参数.

但是,现在,如果你给你的函数一个不同于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!!     |
| ...        |
Run Code Online (Sandbox Code Playgroud)

前两个参数很好.第三个,因为DBus不知道你期待另一个guint,所以将成为一个GError **演员guint.如果你足够幸运,那个指针是NULL,那么otherNumberIn将等于0,但否则它将是一个转换为整数的内存位置:垃圾.

第四个参数特别危险,因为它会尝试读取堆栈中的内容,而你不知道那里有什么.因此,如果您尝试访问error指针,则可能会使用段错误导致程序崩溃.

但是,如果你设法在没有段错误的情况下完成该功能,那么在函数返回后,调用者将在堆栈中巧妙地弹出三个参数,一切都将恢复正常.这就是为什么你的程序似乎仍然有效.

"out"参数是使用指针参数实现的,这就是为什么它们不起作用; 该函数几乎可以保证写入无效的内存地址,因为指针是垃圾.

总之,如果满足以下条件,则您的函数可以具有不同的签名:

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