diff options
author | Johan Dahlin <jdahlin@async.com.br> | 2007-04-29 21:45:33 +0000 |
---|---|---|
committer | Johan Dahlin <johan@src.gnome.org> | 2007-04-29 21:45:33 +0000 |
commit | 986ca14bd42f0cae3551f5b6aa97ef5e66e9a02c (patch) | |
tree | 7df3465c83bfc758e3b0e0fb4f535f44231e863f | |
parent | f7a7b38d5dd5de98d9bccd3e0b6d400601cd4c26 (diff) | |
download | pygobject-986ca14bd42f0cae3551f5b6aa97ef5e66e9a02c.tar.gz pygobject-986ca14bd42f0cae3551f5b6aa97ef5e66e9a02c.tar.xz pygobject-986ca14bd42f0cae3551f5b6aa97ef5e66e9a02c.zip |
Add a generic CClosure marshaller based on ffi. This makes it possible to
2007-04-29 Johan Dahlin <jdahlin@async.com.br>
* README:
* configure.ac:
* gobject/Makefile.am:
* gobject/ffi-marshaller.c: (g_value_to_ffi_type),
(g_value_from_ffi_type), (g_cclosure_marshal_generic_ffi):
* gobject/ffi-marshaller.h:
* gobject/gobjectmodule.c: (create_signal), (init_gobject):
* pygobject-2.0.pc.in:
* tests/test_signal.py:
* tests/testhelpermodule.c: (test1_callback),
(test1_callback_swapped), (test2_callback), (test3_callback),
(test4_callback), (test_float_callback), (test_double_callback),
(test_string_callback), (test_object_callback), (connectcallbacks),
(_wrap_connectcallbacks), (inittesthelper):
Add a generic CClosure marshaller based on ffi.
This makes it possible to connect to signals on PyGObjects from C.
libffi is now an optional dependency
Fixes #353816 (Edward Hervey)
svn path=/trunk/; revision=651
-rw-r--r-- | ChangeLog | 22 | ||||
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | gobject/Makefile.am | 6 | ||||
-rw-r--r-- | gobject/ffi-marshaller.c | 194 | ||||
-rw-r--r-- | gobject/ffi-marshaller.h | 31 | ||||
-rw-r--r-- | gobject/gobjectmodule.c | 22 | ||||
-rw-r--r-- | pygobject-2.0.pc.in | 1 | ||||
-rw-r--r-- | tests/test_signal.py | 63 | ||||
-rw-r--r-- | tests/testhelpermodule.c | 158 |
10 files changed, 499 insertions, 14 deletions
@@ -1,3 +1,25 @@ +2007-04-29 Johan Dahlin <jdahlin@async.com.br> + + * README: + * configure.ac: + * gobject/Makefile.am: + * gobject/ffi-marshaller.c: (g_value_to_ffi_type), + (g_value_from_ffi_type), (g_cclosure_marshal_generic_ffi): + * gobject/ffi-marshaller.h: + * gobject/gobjectmodule.c: (create_signal), (init_gobject): + * pygobject-2.0.pc.in: + * tests/test_signal.py: + * tests/testhelpermodule.c: (test1_callback), + (test1_callback_swapped), (test2_callback), (test3_callback), + (test4_callback), (test_float_callback), (test_double_callback), + (test_string_callback), (test_object_callback), (connectcallbacks), + (_wrap_connectcallbacks), (inittesthelper): + + Add a generic CClosure marshaller based on ffi. + This makes it possible to connect to signals on PyGObjects from C. + libffi is now an optional dependency + Fixes #353816 (Edward Hervey) + 2007-04-23 Gustavo J. A. M. Carneiro <gjc@inescporto.pt> * configure.ac: Post-release version bump. @@ -39,6 +39,7 @@ Requirements * C compiler (GCC and MSVC supported) * Python 2.3.5 or higher * Glib 2.8.0 or higher + * libffi (optional) Copyright Information ===================== diff --git a/configure.ac b/configure.ac index a425d05..c2855c2 100644 --- a/configure.ac +++ b/configure.ac @@ -127,6 +127,17 @@ AC_SUBST([pygobject_CODEGEN_DEFINES]) AS_AC_EXPAND(DATADIR, $datadir) +dnl libffi +AC_MSG_CHECKING(for ffi.h) +AC_TRY_CPP([#include <ffi.h>], pygobject_ffi_h=yes, pygobject_ffi_h=no) +if test $pygobject_ffi_h = yes; then + AC_DEFINE(HAVE_FFI_H,1,[Have ffi.h include file]) + FFI_LIBS="-lffi" +fi +AC_MSG_RESULT([$pygobject_ffi_h]) +AM_CONDITIONAL(HAVE_LIBFFI, test "$pygobject_ffi_h" = "yes") +AC_SUBST(FFI_LIBS) + dnl add required cflags ... JH_ADD_CFLAG([-Wall]) JH_ADD_CFLAG([-std=c9x]) @@ -143,3 +154,7 @@ AC_CONFIG_FILES( tests/Makefile PKG-INFO) AC_OUTPUT + +echo +echo "libffi support: $pygobject_ffi_h" +echo diff --git a/gobject/Makefile.am b/gobject/Makefile.am index eba8e6c..6c4f242 100644 --- a/gobject/Makefile.am +++ b/gobject/Makefile.am @@ -21,7 +21,7 @@ endif _gobject_la_CFLAGS = $(GLIB_CFLAGS) _gobject_la_LDFLAGS = $(common_ldflags) -export-symbols-regex init_gobject -_gobject_la_LIBADD = $(GLIB_LIBS) +_gobject_la_LIBADD = $(GLIB_LIBS) $(FFI_LIBS) _gobject_la_SOURCES = \ gobjectmodule.c \ pygboxed.c \ @@ -40,6 +40,10 @@ _gobject_la_SOURCES = \ pygoptioncontext.c \ pygoptiongroup.c +if HAVE_LIBFFI +_gobject_la_SOURCES += ffi-marshaller.c +endif + if PLATFORM_WIN32 _gobject_la_CFLAGS += -DPLATFORM_WIN32 endif diff --git a/gobject/ffi-marshaller.c b/gobject/ffi-marshaller.c new file mode 100644 index 0000000..177f7c1 --- /dev/null +++ b/gobject/ffi-marshaller.c @@ -0,0 +1,194 @@ +/* -*- Mode: C; c-basic-offset: 4 -*- + * pygtk- Python bindings for the GTK toolkit. + * Copyright (C) 2007 Johan Dahlin + * + * ffi-marshaller: Generic CMarshaller using libffi + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <glib-object.h> +#include <ffi.h> + +static ffi_type * +g_value_to_ffi_type (const GValue *gvalue, gpointer *value) +{ + ffi_type *rettype = NULL; + GType type = g_type_fundamental (G_VALUE_TYPE (gvalue)); + g_assert (type != G_TYPE_INVALID); + + switch (type) { + case G_TYPE_BOOLEAN: + case G_TYPE_CHAR: + case G_TYPE_INT: + rettype = &ffi_type_sint; + *value = (gpointer)&(gvalue->data[0].v_int); + break; + case G_TYPE_UCHAR: + case G_TYPE_UINT: + rettype = &ffi_type_uint; + *value = (gpointer)&(gvalue->data[0].v_uint); + break; + case G_TYPE_STRING: + case G_TYPE_OBJECT: + case G_TYPE_BOXED: + case G_TYPE_POINTER: + rettype = &ffi_type_pointer; + *value = (gpointer)&(gvalue->data[0].v_pointer); + break; + case G_TYPE_FLOAT: + rettype = &ffi_type_float; + *value = (gpointer)&(gvalue->data[0].v_float); + break; + case G_TYPE_DOUBLE: + rettype = &ffi_type_double; + *value = (gpointer)&(gvalue->data[0].v_double); + break; + case G_TYPE_LONG: + rettype = &ffi_type_slong; + *value = (gpointer)&(gvalue->data[0].v_long); + break; + case G_TYPE_ULONG: + rettype = &ffi_type_ulong; + *value = (gpointer)&(gvalue->data[0].v_ulong); + break; + case G_TYPE_INT64: + rettype = &ffi_type_sint64; + *value = (gpointer)&(gvalue->data[0].v_int64); + break; + case G_TYPE_UINT64: + rettype = &ffi_type_uint64; + *value = (gpointer)&(gvalue->data[0].v_uint64); + break; + default: + rettype = &ffi_type_pointer; + *value = NULL; + g_warning ("Unsupported fundamental type: %s", g_type_name (type)); + break; + } + return rettype; +} + +static void +g_value_from_ffi_type (GValue *gvalue, gpointer *value) +{ + switch (g_type_fundamental (G_VALUE_TYPE (gvalue))) { + case G_TYPE_INT: + g_value_set_int (gvalue, *(gint*)value); + break; + case G_TYPE_FLOAT: + g_value_set_float (gvalue, *(gfloat*)value); + break; + case G_TYPE_DOUBLE: + g_value_set_double (gvalue, *(gdouble*)value); + break; + case G_TYPE_BOOLEAN: + g_value_set_boolean (gvalue, *(gboolean*)value); + break; + case G_TYPE_STRING: + g_value_set_string (gvalue, *(gchar**)value); + break; + case G_TYPE_CHAR: + g_value_set_char (gvalue, *(gchar*)value); + break; + case G_TYPE_UCHAR: + g_value_set_uchar (gvalue, *(guchar*)value); + break; + case G_TYPE_UINT: + g_value_set_uint (gvalue, *(guint*)value); + break; + case G_TYPE_POINTER: + g_value_set_pointer (gvalue, *(gpointer*)value); + break; + case G_TYPE_LONG: + g_value_set_long (gvalue, *(glong*)value); + break; + case G_TYPE_ULONG: + g_value_set_ulong (gvalue, *(gulong*)value); + break; + case G_TYPE_INT64: + g_value_set_int64 (gvalue, *(gint64*)value); + break; + case G_TYPE_UINT64: + g_value_set_uint64 (gvalue, *(guint64*)value); + break; + case G_TYPE_BOXED: + g_value_set_boxed (gvalue, *(gpointer*)value); + break; + default: + g_warning ("Unsupported fundamental type: %s", + g_type_name (g_type_fundamental (G_VALUE_TYPE (gvalue)))); + } + +} + +void +g_cclosure_marshal_generic_ffi (GClosure *closure, + GValue *return_gvalue, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + ffi_type *rtype; + void *rvalue; + int n_args; + ffi_type **atypes; + void **args; + int i; + ffi_cif cif; + GCClosure *cc = (GCClosure*) closure; + + if (return_gvalue && G_VALUE_TYPE (return_gvalue)) + { + rtype = g_value_to_ffi_type (return_gvalue, &rvalue); + } + else + { + rtype = &ffi_type_void; + } + + rvalue = g_alloca (MAX (rtype->size, sizeof (ffi_arg))); + + n_args = n_param_values + 1; + atypes = g_alloca (sizeof (ffi_type *) * n_args); + args = g_alloca (sizeof (gpointer) * n_args); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + atypes[n_args-1] = g_value_to_ffi_type (param_values + 0, + &args[n_args-1]); + atypes[0] = &ffi_type_pointer; + args[0] = &closure->data; + } + else + { + atypes[0] = g_value_to_ffi_type (param_values + 0, &args[0]); + atypes[n_args-1] = &ffi_type_pointer; + args[n_args-1] = &closure->data; + } + + for (i = 1; i < n_args - 1; i++) + atypes[i] = g_value_to_ffi_type (param_values + i, &args[i]); + + if (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, n_args, rtype, atypes) != FFI_OK) + return; + + ffi_call (&cif, marshal_data ? marshal_data : cc->callback, rvalue, args); + + if (return_gvalue && G_VALUE_TYPE (return_gvalue)) + g_value_from_ffi_type (return_gvalue, rvalue); +} diff --git a/gobject/ffi-marshaller.h b/gobject/ffi-marshaller.h new file mode 100644 index 0000000..81404b0 --- /dev/null +++ b/gobject/ffi-marshaller.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; c-basic-offset: 4 -*- + * pygtk- Python bindings for the GTK toolkit. + * Copyright (C) 2007 Johan Dahlin + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifndef _FFI_MARSHALLER_H_ +#define _FFI_MARSHALLER_H_ + +void g_cclosure_marshal_generic_ffi (GClosure *closure, + GValue *return_gvalue, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +#endif /* _FFI_MARSHALLER_H_ */ diff --git a/gobject/gobjectmodule.c b/gobject/gobjectmodule.c index 32256a6..8316a20 100644 --- a/gobject/gobjectmodule.c +++ b/gobject/gobjectmodule.c @@ -28,6 +28,13 @@ #include "pythread.h" #include <gobject/gvaluecollector.h> +#ifdef HAVE_FFI_H +#include "ffi-marshaller.h" +static GSignalCMarshaller marshal_generic = g_cclosure_marshal_generic_ffi; +#else +static GSignalCMarshaller marshal_generic = 0; +#endif + static PyObject *gerror_exc = NULL; static gboolean use_gil_state_api = FALSE; static PyObject *_pyg_signal_accumulator_true_handled_func; @@ -594,7 +601,7 @@ create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple) signal_id = g_signal_newv(signal_name, instance_type, signal_flags, pyg_signal_class_closure_get(), accumulator, accum_data, - (GSignalCMarshaller)0, + marshal_generic, return_type, n_params, param_types); g_free(param_types); @@ -3452,7 +3459,7 @@ struct _PyGObject_Functions pygobject_api_functions = { DL_EXPORT(void) init_gobject(void) { - PyObject *m, *d, *o, *tuple; + PyObject *m, *d, *o, *tuple, *features; PyObject *descr; PyObject *warning; @@ -3556,7 +3563,16 @@ init_gobject(void) PyDict_SetItemString(d, "_PyGObject_API", o=PyCObject_FromVoidPtr(&pygobject_api_functions,NULL)); Py_DECREF(o); - + + + /* features */ + features = PyDict_New(); +#ifdef HAVE_FFI_H + PyDict_SetItemString(features, "generic-c-marshaller", Py_True); +#endif + PyDict_SetItemString(d, "features", features); + Py_DECREF(features); + /* some constants */ PyModule_AddIntConstant(m, "SIGNAL_RUN_FIRST", G_SIGNAL_RUN_FIRST); PyModule_AddIntConstant(m, "SIGNAL_RUN_LAST", G_SIGNAL_RUN_LAST); diff --git a/pygobject-2.0.pc.in b/pygobject-2.0.pc.in index a4d3fbc..5e29935 100644 --- a/pygobject-2.0.pc.in +++ b/pygobject-2.0.pc.in @@ -15,4 +15,5 @@ Name: PyGObject Description: Python bindings for GObject Requires: gobject-2.0 Version: @VERSION@ +Libs: -L${libdir} @FFI_LIBS@ Cflags: -I${pygtkincludedir} diff --git a/tests/test_signal.py b/tests/test_signal.py index d22281e..99bb45b 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -3,7 +3,7 @@ import gc import unittest -from common import gobject +from common import gobject, testhelper class C(gobject.GObject): __gsignals__ = { 'my_signal': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -303,5 +303,66 @@ class TestSigProp(unittest.TestCase): obj = SigPropClass() self.failIf(obj.signal_emission_failed) +f = gobject.SIGNAL_RUN_FIRST +l = gobject.SIGNAL_RUN_LAST +float = gobject.TYPE_FLOAT +double = gobject.TYPE_DOUBLE +uint = gobject.TYPE_UINT +ulong = gobject.TYPE_ULONG + +class CM(gobject.GObject): + __gsignals__ = dict( + test1=(f, None, ()), + test2=(l, None, (str,)), + test3=(l, int, (double,)), + test4=(f, None, (bool, long, float, double, int, uint, ulong)), + test_float=(l, float, (float,)), + test_double=(l, double, (double, )), + test_string=(l, str, (str, )), + test_object=(l, object, (object, )), + ) + +class _TestCMarshaller: + def setUp(self): + self.obj = CM() + testhelper.connectcallbacks(self.obj) + + def testTest1(self): + self.obj.emit("test1") + + def testTest2(self): + self.obj.emit("test2", "string") + + def testTest3(self): + rv = self.obj.emit("test3", 42.0) + self.assertEqual(rv, 20) + + def testTest4(self): + self.obj.emit("test4", True, 10L, 3.14, 1.78, 20, 30L, 31L) + + def testTestReturnFloat(self): + rv = self.obj.emit("test-float", 1.234) + self.failUnless(rv >= 1.233999 and rv <= 1.2400001, rv) + + def testTestReturnDouble(self): + rv = self.obj.emit("test-double", 1.234) + self.assertEqual(rv, 1.234) + + def testTestReturnString(self): + rv = self.obj.emit("test-string", "str") + self.assertEqual(rv, "str") + + def testTestReturnObject(self): + rv = self.obj.emit("test-object", self) + self.assertEqual(rv, self) + +if 'generic-c-marshaller' in gobject.features: + class TestCMarshaller(_TestCMarshaller, unittest.TestCase): + pass +else: + print + print '** WARNING: LIBFFI disabled, not testing' + print + if __name__ == '__main__': unittest.main() diff --git a/tests/testhelpermodule.c b/tests/testhelpermodule.c index 48f6f6b..c811876 100644 --- a/tests/testhelpermodule.c +++ b/tests/testhelpermodule.c @@ -87,14 +87,6 @@ _wrap_test_g_object_new (PyObject * self) return rv; } -static PyMethodDef testhelper_methods[] = { - { "get_test_thread", (PyCFunction)_wrap_get_test_thread, METH_NOARGS }, - { "get_unknown", (PyCFunction)_wrap_get_unknown, METH_NOARGS }, - { "create_test_type", (PyCFunction)_wrap_create_test_type, METH_NOARGS }, - { "test_g_object_new", (PyCFunction)_wrap_test_g_object_new, METH_NOARGS }, - { NULL, NULL } -}; - /* TestUnknown */ static PyObject * _wrap_test_interface_iface_method(PyGObject *self, PyObject *args, PyObject *kwargs) @@ -319,6 +311,154 @@ static const GInterfaceInfo __TestInterface__iinfo = { NULL }; +#include <string.h> +#include <glib-object.h> + +static void +test1_callback (GObject *object, char *data) +{ + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (!strcmp (data, "user-data")); +} + +static void +test1_callback_swapped (char *data, GObject *object) +{ + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (!strcmp (data, "user-data")); +} + +static void +test2_callback (GObject *object, char *string) +{ + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (!strcmp (string, "string")); +} + +static int +test3_callback (GObject *object, double d) +{ + g_return_val_if_fail (G_IS_OBJECT (object), -1); + g_return_val_if_fail (d == 42.0, -1); + + return 20; +} + +static void +test4_callback (GObject *object, + gboolean b, long l, float f, double d, guint uint, gulong ulong, + gpointer user_data) +{ + g_return_if_fail (b == TRUE); + g_return_if_fail (l == 10L); + g_return_if_fail (f <= 3.14001 && f >= 3.13999); + g_return_if_fail (d == 1.78); + g_return_if_fail (uint == 20); + g_return_if_fail (ulong == 30L); +} + +static float +test_float_callback (GObject *object, float f) +{ + g_return_val_if_fail (G_IS_OBJECT (object), -1); + g_return_val_if_fail (f <= 1.234001 && f >= 1.123999, -1); + + return f; +} + +static double +test_double_callback (GObject *object, double d) +{ + g_return_val_if_fail (G_IS_OBJECT (object), -1); + g_return_val_if_fail (d <= 1.234001 && d >= 1.123999, -1); + + return d; +} + +static char * +test_string_callback (GObject *object, char *s) +{ + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + g_return_val_if_fail (!strcmp(s, "str"), NULL); + + return g_strdup (s); +} + +static GObject * +test_object_callback (GObject *object, GObject *o) +{ + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + + return o; +} + +void +connectcallbacks (GObject *object) +{ + + gchar *data = "user-data"; + + g_signal_connect (G_OBJECT (object), + "test1", + G_CALLBACK (test1_callback), + data); + g_signal_connect_swapped (G_OBJECT (object), + "test1", + G_CALLBACK (test1_callback_swapped), + data); + g_signal_connect (G_OBJECT (object), + "test2", + G_CALLBACK (test2_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test3", + G_CALLBACK (test3_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test4", + G_CALLBACK (test4_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test_float", + G_CALLBACK (test_float_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test_double", + G_CALLBACK (test_double_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test_string", + G_CALLBACK (test_string_callback), + NULL); + g_signal_connect (G_OBJECT (object), + "test_object", + G_CALLBACK (test_object_callback), + NULL); +} + +static PyObject * +_wrap_connectcallbacks(PyObject * self, PyObject *args) +{ + PyGObject *obj; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return NULL; + + connectcallbacks (G_OBJECT (obj->obj)); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef testhelper_functions[] = { + { "get_test_thread", (PyCFunction)_wrap_get_test_thread, METH_NOARGS }, + { "get_unknown", (PyCFunction)_wrap_get_unknown, METH_NOARGS }, + { "create_test_type", (PyCFunction)_wrap_create_test_type, METH_NOARGS }, + { "test_g_object_new", (PyCFunction)_wrap_test_g_object_new, METH_NOARGS }, + { "connectcallbacks", (PyCFunction)_wrap_connectcallbacks, METH_VARARGS }, + { NULL, NULL } +}; + void inittesthelper () { @@ -327,7 +467,7 @@ inittesthelper () init_pygobject(); g_thread_init(NULL); - m = Py_InitModule ("testhelper", testhelper_methods); + m = Py_InitModule ("testhelper", testhelper_functions); d = PyModule_GetDict(m); |