其他分享
首页 > 其他分享> > 为什么即使回调参数与XML中的回调参数不匹配,仍会调用GObject方法?

为什么即使回调参数与XML中的回调参数不匹配,仍会调用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)

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

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

虽然我认为这没有任何区别,但我使用的是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 calling convention(技术上依赖于编译器和体系结构的实现细节!)

由于函数指针的大小都相同,因此可以将它们全部转换为void(* callback)(void),这就是GCallback的typedef. 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 **错误),假设相同的参数被压入堆栈,但您的函数以不同方式解释它们:

+------------+
| &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,否则它将是一个转换为整数的内存位置:垃圾.

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

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

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

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

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

标签:c-3,glib,linux,calling-convention,dbus
来源: https://codeday.me/bug/20190825/1716409.html