[pygi] Implementation callback support with scoping and basic argument support.



commit 610dd1eec87fab5c8c3badb4d104cba74477c745
Author: Zach Goldberg <zach zachgoldberg com>
Date:   Sat Apr 17 09:17:14 2010 -0400

    Implementation callback support with scoping and basic argument support.
    
    This patch was originally written by
    Zach Goldberg <zach zachgoldberg com> with modifications and
    review by Simon van der Linden <svdlinden src gnome org> and
    Colin Walters <walters verbum org>.
    
    This impementation enforces the assumption that any one function
    signature can only have one (callback, userdata, destronotify) tuple.
    This allows us to move callback creation into the actual function
    invoke pipeline and also to keep just one destroy notify callback
    around, vastly simplifying the code.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=603095

 configure.ac        |    2 +
 gi/Makefile.am      |    4 +
 gi/pygi-argument.c  |   12 ++-
 gi/pygi-callbacks.c |  216 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gi/pygi-callbacks.h |   47 +++++++++++
 gi/pygi-closure.c   |  205 ++++++++++++++++++++++++++++++++++++++++++++++++
 gi/pygi-closure.h   |   57 ++++++++++++++
 gi/pygi-info.c      |   49 +++++++++++-
 gi/pygi-private.h   |    2 +
 tests/test_gi.py    |   64 +++++++++++++++-
 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)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]