Почему метод GObject по-прежнему вызывается, даже если аргументы обратного вызова не совпадают с аргументами в XML?

Предположим, у меня есть такой метод

<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.


person Alex C    schedule 14.03.2014    source источник


Ответы (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 (технически это деталь реализации, зависящая от компилятора и архитектуры!)

Поскольку все указатели на функции имеют одинаковый размер, их можно привести к void (*callback)(void), для чего GCallback является определением типа. GCallback используется во всех API платформы GLib для обратных вызовов, которые могут иметь переменные числа и типы аргументов. Вот почему вы должны преобразовать 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
+------------+

Все отлично. Затем функция возвращается, и вызывающая сторона извлекает аргументы из стека.

Теперь, однако, если вы зададите своей функции тип, отличный от сигнатуры DBus в XML, скажем, 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, вы, вероятно, сломаете свою программу из-за segfault.

Однако, если вам удастся выполнить функцию без segfault, то вызывающая программа аккуратно вытолкнет три аргумента из стека после возврата функции, и все вернется в нормальное русло. Вот почему ваша программа все еще работает.

Параметры «out» реализованы с использованием аргументов-указателей, поэтому они не работают; функция почти гарантированно записывает в недопустимый адрес памяти, потому что указатели являются мусором.

Таким образом, ваша функция может иметь другую подпись, если выполняются следующие условия:

  • ваш компилятор использует соглашение о вызовах C
  • ваша функция имеет то же количество аргументов (или меньше), чем ожидает вызывающая сторона
  • ваши аргументы имеют тот же размер, что и аргументы, которые вызывающий абонент ожидает нажать
  • ваши типы аргументов имеют смысл при приведении аргументов, которые вызывающая сторона ожидает нажать
person ptomato    schedule 15.03.2014