summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac2
-rw-r--r--gi/Makefile.am4
-rw-r--r--gi/pygi-argument.c12
-rw-r--r--gi/pygi-callbacks.c216
-rw-r--r--gi/pygi-callbacks.h47
-rw-r--r--gi/pygi-closure.c205
-rw-r--r--gi/pygi-closure.h57
-rw-r--r--gi/pygi-info.c49
-rw-r--r--gi/pygi-private.h2
-rw-r--r--tests/test_gi.py64
10 files changed, 648 insertions, 10 deletions
diff --git a/configure.ac b/configure.ac
index 3a596c3..544bcf7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -33,6 +33,8 @@ CPPFLAGS+="${PYTHON_INCLUDES}"
AC_CHECK_HEADER(Python.h, , AC_MSG_ERROR(Python headers not found))
CPPFLAGS="${save_CPPFLAGS}"
+# FFI
+PKG_CHECK_MODULES(FFI, libffi >= 3.0)
# GNOME
PKG_CHECK_MODULES(GNOME,
diff --git a/gi/Makefile.am b/gi/Makefile.am
index 0299d01..ff32761 100644
--- a/gi/Makefile.am
+++ b/gi/Makefile.am
@@ -42,6 +42,10 @@ _gi_la_SOURCES = \
pygi-type.h \
pygi-boxed.c \
pygi-boxed.h \
+ pygi-closure.c \
+ pygi-closure.h \
+ pygi-callbacks.c \
+ pygi-callbacks.h \
pygi.h \
pygi-private.h \
pygobject-external.h \
diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
index 335074e..0737bb7 100644
--- a/gi/pygi-argument.c
+++ b/gi/pygi-argument.c
@@ -29,6 +29,7 @@
#include <datetime.h>
#include <pygobject.h>
+
static void
_pygi_g_type_tag_py_bounds (GITypeTag type_tag,
PyObject **lower,
@@ -379,8 +380,11 @@ check_number_release:
switch (info_type) {
case GI_INFO_TYPE_CALLBACK:
- /* TODO */
- PyErr_SetString(PyExc_NotImplementedError, "callback marshalling is not supported yet");
+ if (!PyCallable_Check(object)) {
+ PyErr_Format(PyExc_TypeError, "Must be callable, not %s",
+ object->ob_type->tp_name);
+ retval = 0;
+ }
break;
case GI_INFO_TYPE_ENUM:
retval = _pygi_g_registered_type_info_check_object(
@@ -882,8 +886,8 @@ array_item_error:
switch (info_type) {
case GI_INFO_TYPE_CALLBACK:
- PyErr_SetString(PyExc_NotImplementedError, "callback marshalling is not supported yet");
- /* TODO */
+ /* This should be handled in invoke() */
+ g_assert_not_reached();
break;
case GI_INFO_TYPE_BOXED:
case GI_INFO_TYPE_STRUCT:
diff --git a/gi/pygi-callbacks.c b/gi/pygi-callbacks.c
new file mode 100644
index 0000000..0096a8b
--- /dev/null
+++ b/gi/pygi-callbacks.c
@@ -0,0 +1,216 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * pygi-closure.c: PyGI C Closure functions
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#include "pygi-private.h"
+
+static PyGICClosure *global_destroy_notify;
+
+static void
+_pygi_destroy_notify_callback_closure(ffi_cif *cif,
+ void *result,
+ void **args,
+ void *data)
+{
+ PyGICClosure *info = *(void**)(args[0]);
+
+ g_assert(info);
+
+ _pygi_invoke_closure_free(info);
+}
+
+
+PyGICClosure*
+_pygi_destroy_notify_create(void)
+{
+ if (!global_destroy_notify) {
+
+ ffi_status status;
+ PyGICClosure *destroy_notify = g_slice_new0(PyGICClosure);
+
+ g_assert(destroy_notify);
+
+ GIBaseInfo* glib_destroy_notify = g_irepository_find_by_name(NULL, "GLib", "DestroyNotify");
+ g_assert(glib_destroy_notify != NULL);
+ g_assert(g_base_info_get_type(glib_destroy_notify) == GI_INFO_TYPE_CALLBACK);
+
+ destroy_notify->closure = g_callable_info_prepare_closure((GICallableInfo*)glib_destroy_notify,
+ &destroy_notify->cif,
+ _pygi_destroy_notify_callback_closure,
+ NULL);
+
+ global_destroy_notify = destroy_notify;
+ }
+
+ return global_destroy_notify;
+}
+
+
+gboolean
+_pygi_scan_for_callbacks (PyGIBaseInfo *function_info,
+ gboolean is_method,
+ guint8 *callback_index,
+ guint8 *user_data_index,
+ guint8 *destroy_notify_index)
+{
+ guint i, n_args;
+
+ *callback_index = G_MAXUINT8;
+ *user_data_index = G_MAXUINT8;
+ *destroy_notify_index = G_MAXUINT8;
+
+ n_args = g_callable_info_get_n_args((GICallableInfo *)function_info->info);
+ for (i = 0; i < n_args; i++) {
+ GIDirection direction;
+ GIArgInfo *arg_info;
+ GITypeInfo *type_info;
+ guint8 destroy, closure;
+ GITypeTag type_tag;
+
+ arg_info = g_callable_info_get_arg((GICallableInfo*) function_info->info, i);
+ type_info = g_arg_info_get_type(arg_info);
+ type_tag = g_type_info_get_tag(type_info);
+
+ if (type_tag == GI_TYPE_TAG_INTERFACE) {
+ GIBaseInfo* interface_info;
+ GIInfoType interface_type;
+
+ interface_info = g_type_info_get_interface(type_info);
+ interface_type = g_base_info_get_type(interface_info);
+ if (interface_type == GI_INFO_TYPE_CALLBACK &&
+ !(strcmp(g_base_info_get_namespace((GIBaseInfo*) interface_info), "GLib") == 0 &&
+ strcmp(g_base_info_get_name((GIBaseInfo*) interface_info), "DestroyNotify") == 0)) {
+ if (*callback_index != G_MAXUINT8) {
+ PyErr_Format(PyExc_TypeError, "Function %s.%s has multiple callbacks, not supported",
+ g_base_info_get_namespace((GIBaseInfo*) function_info->info),
+ g_base_info_get_name((GIBaseInfo*) function_info->info));
+ g_base_info_unref(interface_info);
+ return FALSE;
+ }
+ *callback_index = i;
+ }
+ g_base_info_unref(interface_info);
+ }
+ destroy = g_arg_info_get_destroy(arg_info);
+ if (is_method)
+ --destroy;
+ closure = g_arg_info_get_closure(arg_info);
+ if (is_method)
+ --closure;
+ direction = g_arg_info_get_direction(arg_info);
+
+ if (destroy > 0 && destroy < n_args) {
+ if (*destroy_notify_index != G_MAXUINT8) {
+ PyErr_Format(PyExc_TypeError, "Function %s has multiple GDestroyNotify, not supported",
+ g_base_info_get_name((GIBaseInfo*)function_info->info));
+ return FALSE;
+ }
+ *destroy_notify_index = destroy;
+ }
+
+ if (closure > 0 && closure < n_args) {
+ if (*user_data_index != G_MAXUINT8) {
+ PyErr_Format(PyExc_TypeError, "Function %s has multiple user_data arguments, not supported",
+ g_base_info_get_name((GIBaseInfo*)function_info->info));
+ return FALSE;
+ }
+ *user_data_index = closure;
+ }
+
+ g_base_info_unref((GIBaseInfo*)arg_info);
+ g_base_info_unref((GIBaseInfo*)type_info);
+ }
+
+ return TRUE;
+}
+
+gboolean
+_pygi_create_callback (PyGIBaseInfo *function_info,
+ gboolean is_method,
+ int n_args,
+ Py_ssize_t py_argc,
+ PyObject *py_argv,
+ guint8 callback_index,
+ guint8 user_data_index,
+ guint8 destroy_notify_index,
+ PyGICClosure **closure_out)
+{
+ GIArgInfo *callback_arg;
+ GITypeInfo *callback_type;
+ GICallbackInfo *callback_info;
+ GIScopeType scope;
+ gboolean found_py_function;
+ PyObject *py_function;
+ guint8 i, py_argv_pos;
+ PyObject *py_user_data;
+
+ callback_arg = g_callable_info_get_arg((GICallableInfo*) function_info->info, callback_index);
+ scope = g_arg_info_get_scope(callback_arg);
+
+ callback_type = g_arg_info_get_type(callback_arg);
+ g_assert(g_type_info_get_tag(callback_type) == GI_TYPE_TAG_INTERFACE);
+
+ callback_info = (GICallbackInfo*)g_type_info_get_interface(callback_type);
+ g_assert(g_base_info_get_type((GIBaseInfo*)callback_info) == GI_INFO_TYPE_CALLBACK);
+
+ /* Find the Python function passed for the callback */
+ found_py_function = FALSE;
+ py_function = Py_None;
+ py_user_data = NULL;
+
+ /* if its a method then we need to skip over 'self' */
+ if (is_method)
+ py_argv_pos = 1;
+ else
+ py_argv_pos = 0;
+
+ for (i = 0; i < n_args && i < py_argc; i++) {
+ if (i == callback_index) {
+ py_function = PyTuple_GetItem(py_argv, py_argv_pos);
+ found_py_function = TRUE;
+ } else if (i == user_data_index){
+ py_user_data = PyTuple_GetItem(py_argv, py_argv_pos);
+ }
+ py_argv_pos++;
+ }
+
+ if (!found_py_function
+ || (py_function == Py_None || !PyCallable_Check(py_function))) {
+ PyErr_Format(PyExc_TypeError, "Error invoking %s.%s: Invalid callback given for argument %s",
+ g_base_info_get_namespace((GIBaseInfo*) function_info->info),
+ g_base_info_get_name((GIBaseInfo*) function_info->info),
+ g_base_info_get_name((GIBaseInfo*) callback_arg));
+ g_base_info_unref((GIBaseInfo*) callback_info);
+ g_base_info_unref((GIBaseInfo*) callback_type);
+ return FALSE;
+ }
+
+
+ /** Now actually build the closure **/
+ *closure_out = _pygi_make_native_closure((GICallableInfo *)callback_info,
+ callback_arg,
+ py_function,
+ py_user_data);
+
+ g_base_info_unref((GIBaseInfo*) callback_info);
+ g_base_info_unref((GIBaseInfo*) callback_type);
+
+ return TRUE;
+}
diff --git a/gi/pygi-callbacks.h b/gi/pygi-callbacks.h
new file mode 100644
index 0000000..7b3401d
--- /dev/null
+++ b/gi/pygi-callbacks.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#ifndef __PYGI_CALLBACKS_H__
+#define __PYGI_CALLBACKS_H__
+
+G_BEGIN_DECLS
+
+void _pygi_callback_notify_info_free(gpointer user_data);
+
+PyGICClosure*_pygi_destroy_notify_create(void);
+
+gboolean _pygi_scan_for_callbacks (PyGIBaseInfo *self,
+ gboolean is_method,
+ guint8 *callback_index,
+ guint8 *user_data_index,
+ guint8 *destroy_notify_index);
+
+gboolean _pygi_create_callback (PyGIBaseInfo *self,
+ gboolean is_method,
+ int n_args,
+ Py_ssize_t py_argc,
+ PyObject *py_argv,
+ guint8 callback_index,
+ guint8 user_data_index,
+ guint8 destroy_notify_index,
+ PyGICClosure **closure_out);
+
+G_END_DECLS
+
+#endif
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
new file mode 100644
index 0000000..0ad8fef
--- /dev/null
+++ b/gi/pygi-closure.c
@@ -0,0 +1,205 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * pygi-closure.c: PyGI C Closure functions
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#include "pygi-private.h"
+
+/* This maintains a list of closures which can be free'd whenever
+ as they have been called. We will free them on the next
+ library function call.
+ */
+static GSList* async_free_list;
+
+void
+_pygi_closure_handle (ffi_cif *cif,
+ void *result,
+ void **args,
+ void *data)
+{
+ PyGILState_STATE state;
+ PyGICClosure *closure = data;
+ gint n_args, i;
+ GIArgInfo *arg_info;
+ GIDirection arg_direction;
+ GITypeInfo *arg_type;
+ GITransfer arg_transfer;
+ GITypeTag arg_tag;
+ GITypeTag return_tag;
+ GITransfer return_transfer;
+ GITypeInfo *return_type;
+ PyObject *retval;
+ PyObject *py_args;
+ PyObject *pyarg;
+ gint n_in_args, n_out_args;
+
+
+ /* Lock the GIL as we are coming into this code without the lock and we
+ may be executing python code */
+ state = PyGILState_Ensure();
+
+ return_type = g_callable_info_get_return_type(closure->info);
+ return_tag = g_type_info_get_tag(return_type);
+ return_transfer = g_callable_info_get_caller_owns(closure->info);
+
+ n_args = g_callable_info_get_n_args (closure->info);
+
+ py_args = PyTuple_New(n_args);
+ if (py_args == NULL) {
+ PyErr_Clear();
+ goto end;
+ }
+
+ n_in_args = 0;
+
+ for (i = 0; i < n_args; i++) {
+ arg_info = g_callable_info_get_arg (closure->info, i);
+ arg_type = g_arg_info_get_type (arg_info);
+ arg_transfer = g_arg_info_get_ownership_transfer(arg_info);
+ arg_tag = g_type_info_get_tag(arg_type);
+ arg_direction = g_arg_info_get_direction(arg_info);
+ switch (arg_tag){
+ case GI_TYPE_TAG_VOID:
+ {
+ if (g_type_info_is_pointer(arg_type)) {
+ if (PyTuple_SetItem(py_args, n_in_args, closure->user_data) != 0) {
+ PyErr_Clear();
+ goto end;
+ }
+ n_in_args++;
+ continue;
+ }
+ }
+ case GI_TYPE_TAG_ERROR:
+ {
+ continue;
+ }
+ default:
+ {
+ pyarg = _pygi_argument_to_object (args[i],
+ arg_type,
+ arg_transfer);
+
+ if(PyTuple_SetItem(py_args, n_in_args, pyarg) != 0) {
+ PyErr_Clear();
+ goto end;
+ }
+ n_in_args++;
+ g_base_info_unref((GIBaseInfo*)arg_info);
+ g_base_info_unref((GIBaseInfo*)arg_type);
+ }
+ }
+
+ }
+
+ if(_PyTuple_Resize (&py_args, n_in_args) != 0) {
+ PyErr_Clear();
+ goto end;
+ }
+
+ retval = PyObject_CallObject((PyObject *)closure->function, py_args);
+
+ Py_DECREF(py_args);
+
+ if (retval == NULL) {
+ goto end;
+ }
+
+ *(GArgument*)result = _pygi_argument_from_object(retval, return_type, return_transfer);
+
+end:
+ g_base_info_unref((GIBaseInfo*)return_type);
+
+ PyGILState_Release(state);
+
+ if (closure->user_data)
+ Py_XDECREF(closure->user_data);
+
+ /* Now that the closure has finished we can make a decision about how
+ to free it. Scope call gets free'd now, scope notified will be freed
+ when the notify is called and we can free async anytime we want
+ once we return from this function */
+ switch (closure->scope) {
+ case GI_SCOPE_TYPE_CALL:
+ _pygi_invoke_closure_free(closure);
+ break;
+ case GI_SCOPE_TYPE_NOTIFIED:
+ break;
+ case GI_SCOPE_TYPE_ASYNC:
+ /* Append this PyGICClosure to a list of closure that we will free
+ after we're done with this function invokation */
+ async_free_list = g_slist_prepend(async_free_list, closure);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void _pygi_invoke_closure_free(gpointer data)
+{
+ PyGICClosure* invoke_closure = (PyGICClosure *)data;
+
+
+ Py_DECREF(invoke_closure->function);
+
+ g_callable_info_free_closure(invoke_closure->info,
+ invoke_closure->closure);
+
+ if (invoke_closure->info)
+ g_base_info_unref((GIBaseInfo*)invoke_closure->info);
+
+ g_slice_free(PyGICClosure, invoke_closure);
+}
+
+
+PyGICClosure*
+_pygi_make_native_closure (GICallableInfo* info,
+ GIArgInfo* arg_info,
+ PyObject *py_function,
+ gpointer py_user_data)
+{
+ PyGICClosure *closure;
+ ffi_closure *fficlosure;
+
+ /* Begin by cleaning up old async functions */
+ g_slist_foreach(async_free_list, (GFunc)_pygi_invoke_closure_free, NULL);
+ g_slist_free(async_free_list);
+ async_free_list = NULL;
+
+ /* Build the closure itself */
+ closure = g_slice_new0(PyGICClosure);
+ closure->info = (GICallableInfo *) g_base_info_ref ((GIBaseInfo *) info);
+ closure->function = py_function;
+ closure->user_data = py_user_data;
+
+ Py_INCREF(py_function);
+ if (closure->user_data)
+ Py_INCREF(closure->user_data);
+
+ fficlosure =
+ g_callable_info_prepare_closure (info, &closure->cif, _pygi_closure_handle,
+ closure);
+ closure->closure = fficlosure;
+
+ /* Give the closure the information it needs to determine when
+ to free itself later */
+ closure->scope = g_arg_info_get_scope(arg_info);
+
+ return closure;
+}
diff --git a/gi/pygi-closure.h b/gi/pygi-closure.h
new file mode 100644
index 0000000..c03e69d
--- /dev/null
+++ b/gi/pygi-closure.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#ifndef __PYGI_CLOSURE_H__
+#define __PYGI_CLOSURE_H__
+
+#include <Python.h>
+#include <girffi.h>
+#include <ffi.h>
+
+G_BEGIN_DECLS
+
+
+/* Private */
+
+typedef struct _PyGICClosure
+{
+ GICallableInfo *info;
+ PyObject *function;
+
+ ffi_closure *closure;
+ ffi_cif cif;
+
+ GIScopeType scope;
+
+ PyObject* user_data;
+} PyGICClosure;
+
+void _pygi_closure_handle(ffi_cif *cif, void *result, void
+ **args, void *userdata);
+
+void _pygi_invoke_closure_free(gpointer user_data);
+
+PyGICClosure* _pygi_make_native_closure (GICallableInfo* info,
+ GIArgInfo* arg_info,
+ PyObject *function,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __PYGI_CLOSURE_H__ */
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index 18981a2..dbcf999 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -503,6 +503,11 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
gsize n_aux_out_args;
gsize n_return_values;
+ guint8 callback_index;
+ guint8 user_data_index;
+ guint8 destroy_notify_index;
+ PyGICClosure *closure;
+
glong error_arg_pos;
GIArgInfo **arg_infos;
@@ -538,6 +543,10 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
n_backup_args = 0;
n_aux_in_args = 0;
n_aux_out_args = 0;
+
+ /* Check the argument count. */
+ n_py_args = PyTuple_Size(py_args);
+ g_assert(n_py_args >= 0);
error_arg_pos = -1;
@@ -547,11 +556,32 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
args_is_auxiliary = g_newa(gboolean, n_args);
memset(args_is_auxiliary, 0, sizeof(args_is_auxiliary) * n_args);
+ if (!_pygi_scan_for_callbacks (self, is_method, &callback_index, &user_data_index,
+ &destroy_notify_index))
+ return NULL;
+
+ if (callback_index != G_MAXUINT8) {
+ if (!_pygi_create_callback (self, is_method,
+ n_args, n_py_args, py_args, callback_index,
+ user_data_index,
+ destroy_notify_index, &closure))
+ return NULL;
+
+ args_is_auxiliary[callback_index] = FALSE;
+ if (destroy_notify_index != G_MAXUINT8) {
+ args_is_auxiliary[destroy_notify_index] = TRUE;
+ n_aux_in_args += 1;
+ }
+ }
+
if (is_method) {
/* The first argument is the instance. */
n_in_args += 1;
}
+ /* We do a first (well, second) pass here over the function to scan for special cases.
+ * This is currently array+length combinations and GError.
+ */
for (i = 0; i < n_args; i++) {
GIDirection direction;
GITransfer transfer;
@@ -559,7 +589,7 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
arg_infos[i] = g_callable_info_get_arg((GICallableInfo *)self->info, i);
arg_type_infos[i] = g_arg_info_get_type(arg_infos[i]);
-
+
direction = g_arg_info_get_direction(arg_infos[i]);
transfer = g_arg_info_get_ownership_transfer(arg_infos[i]);
arg_type_tag = g_type_info_get_tag(arg_type_infos[i]);
@@ -639,10 +669,6 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
gsize n_py_args_expected;
Py_ssize_t py_args_pos;
- /* Check the argument count. */
- n_py_args = PyTuple_Size(py_args);
- g_assert(n_py_args >= 0);
-
n_py_args_expected = n_in_args
+ (is_constructor ? 1 : 0)
- n_aux_in_args
@@ -799,6 +825,19 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
for (i = 0; i < n_args; i++) {
GIDirection direction;
+ if (i == callback_index) {
+ args[i]->v_pointer = closure->closure;
+ py_args_pos++;
+ continue;
+ } else if (i == user_data_index) {
+ args[i]->v_pointer = closure;
+ py_args_pos++;
+ continue;
+ } else if (i == destroy_notify_index) {
+ args[i]->v_pointer = _pygi_destroy_notify_create();
+ continue;
+ }
+
if (args_is_auxiliary[i]) {
continue;
}
diff --git a/gi/pygi-private.h b/gi/pygi-private.h
index 9f39d0d..74716be 100644
--- a/gi/pygi-private.h
+++ b/gi/pygi-private.h
@@ -25,6 +25,8 @@
#include "pygi-argument.h"
#include "pygi-type.h"
#include "pygi-foreign.h"
+#include "pygi-closure.h"
+#include "pygi-callbacks.h"
G_BEGIN_DECLS
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 9b9a849..a9076b6 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -12,7 +12,7 @@ from datetime import datetime
import sys
sys.path.insert(0, "../")
-from gi.repository import GIMarshallingTests
+from gi.repository import GIMarshallingTests, Everything
CONSTANT_UTF8 = "const \xe2\x99\xa5 utf8"
@@ -1399,3 +1399,65 @@ class TestOverrides(unittest.TestCase):
object_ = GIMarshallingTests.overrides_object_return()
self.assertTrue(isinstance(object_, GIMarshallingTests.OverridesObject))
+
+
+class TestCallbacks(unittest.TestCase):
+ called = False
+ def testCallback(self):
+ TestCallbacks.called = False
+ def callback():
+ TestCallbacks.called = True
+
+ Everything.test_simple_callback(callback)
+ self.assertTrue(TestCallbacks.called)
+
+ def testCallbackException(self):
+ """
+ This test ensures that we get errors from callbacks correctly
+ and in particular that we do not segv when callbacks fail
+ """
+ def callback():
+ x = 1 / 0
+
+ try:
+ Everything.test_simple_callback(callback)
+ except ZeroDivisionError:
+ pass
+
+ def testDoubleCallbackException(self):
+ """
+ This test ensures that we get errors from callbacks correctly
+ and in particular that we do not segv when callbacks fail
+ """
+ def badcallback():
+ x = 1 / 0
+
+ def callback():
+ GIMarshallingTests.boolean_return_true()
+ GIMarshallingTests.boolean_return_false()
+ Everything.test_simple_callback(badcallback())
+
+ try:
+ Everything.test_simple_callback(callback)
+ except ZeroDivisionError:
+ pass
+
+ def testReturnValueCallback(self):
+ TestCallbacks.called = False
+ def callback():
+ TestCallbacks.called = True
+ return 44
+
+ self.assertEquals(Everything.test_callback(callback), 44)
+ self.assertTrue(TestCallbacks.called)
+
+ def testCallbackAsync(self):
+ TestCallbacks.called = False
+ def callback(foo):
+ TestCallbacks.called = True
+ return foo
+
+ Everything.test_callback_async(callback, 44);
+ i = Everything.test_callback_thaw_async();
+ self.assertEquals(44, i);
+ self.assertTrue(TestCallbacks.called)