[Patches] Using Tcl_Obj in _tkinter

Martin v. Loewis martin@loewis.home.cs.tu-berlin.de
Sat, 25 Mar 2000 19:27:36 +0100


With the patch included below, _tkinter uses the Tcl_Obj API for
command invocations. This means a more direct exchange of data between
Tcl and Python; I have not yet measured how that impacts performance
of _tkinter. Currently, it uses the object interface only for
TkApp_Call; other potential candidates would be global_call, and the
setvar family. For compatibility with Tkinter applications, it never
attempts to use the object interface when returning values to Python -
in these cases, strings are produced as before.

According to Tcl's changes document, this patch should work for Tcl
versions starting with 8.0; it has been tested with Tcl 8.3 on
Linux. The code is disabled for earlier versions.

In addition, it also calls Tcl_FindExecutable, which is documented as
being available since Tcl 8.1. This is necessary so Tcl finds its
library, in particular its character set conversion tables.

The patch is essentially the same that has been send to string-sig
before, the only change is that it won't use Tcl_NewUnicodeString,
anymore.

Please let me know if you find any problems with that code.

Regards,
Martin

Index: _tkinter.c
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Modules/_tkinter.c,v
retrieving revision 1.90
diff -u -p -r1.90 _tkinter.c
--- _tkinter.c	2000/02/29 13:59:22	1.90
+++ _tkinter.c	2000/03/25 18:00:25
@@ -130,6 +130,11 @@ PERFORMANCE OF THIS SOFTWARE.
 #define WAIT_FOR_STDIN
 #endif
 
+/* If Tcl provides the Tcl_Obj type, we use that interface. */
+#if TKMAJORMINOR >= 8000
+#define USING_OBJECTS
+#endif
+
 #ifdef WITH_THREAD
 
 /* The threading situation is complicated.  Tcl is not thread-safe, except for
@@ -441,6 +446,44 @@ Split(list)
 	return v;
 }
 
+#ifdef USING_OBJECTS
+
+static Tcl_Obj*
+AsObj(value)
+	PyObject *value;
+{
+	Tcl_Obj *result;
+	if (PyUnicode_Check(value)) {
+		PyObject* utf8 = PyUnicode_AsUTF8String (value);
+		if (!utf8)
+			return 0;
+		return Tcl_NewStringObj (PyString_AS_STRING (utf8),
+					 PyString_GET_SIZE (utf8));
+	}
+	else if (PyString_Check(value))
+		return Tcl_NewStringObj(PyString_AS_STRING(value),
+					PyString_GET_SIZE(value));
+	else if (PyTuple_Check(value)) {
+		Tcl_Obj **argv = (Tcl_Obj**)ckalloc(PyTuple_Size(value)*sizeof(Tcl_Obj*));
+		int i;
+		if(!argv)
+		  return 0;
+		for(i=0;i<PyTuple_Size(value);i++)
+		  argv[i] = AsObj(PyTuple_GetItem(value,i));
+		result = Tcl_NewListObj(PyTuple_Size(value), argv);
+		ckfree(FREECAST argv);
+		return result;
+	}
+	else {
+		PyObject *v = PyObject_Str(value);
+		if (!v)
+			return 0;
+		result = AsObj(v);
+		Py_DECREF(v);
+		return result;
+	}
+}
+#endif
 
 
 /**** Tkapp Object ****/
@@ -538,6 +581,76 @@ Tkapp_New(screenName, baseName, classNam
 
 /** Tcl Eval **/
 
+#ifdef USING_OBJECTS
+static PyObject *
+Tkapp_Call(self, args)
+	PyObject *self;
+	PyObject *args;
+{
+	Tcl_Obj *objStore[ARGSZ];
+	Tcl_Obj **objv = NULL;
+	int objc = 0, i;
+	PyObject *res = NULL;
+	Tcl_Interp *interp = Tkapp_Interp(self);
+	/* Could add TCL_EVAL_GLOBAL if wrapped by GlobalCall... */
+	int flags = TCL_EVAL_DIRECT;
+
+	objv = objStore;
+
+	if (args == NULL)
+		objc = 0;
+
+	else if (!PyTuple_Check(args)) {
+		objc = 1;
+		objv[0] = AsObj(args);
+		if (objv[0] == 0)
+			goto finally;
+		Tcl_IncrRefCount(objv[0]);
+	}
+	else {
+		objc = PyTuple_Size(args);
+
+		if (objc > ARGSZ) {
+			objv = (Tcl_Obj **)ckalloc(objc * sizeof(char *));
+			if (objv == NULL) {
+				PyErr_NoMemory();
+				goto finally;
+			}
+		}
+
+		for (i = 0; i < objc; i++) {
+			PyObject *v = PyTuple_GetItem(args, i);
+			objv[i] = AsObj(v);
+			if (!objv[i])
+				goto finally;
+			Tcl_IncrRefCount(objv[i]);
+		}
+	}
+
+	ENTER_TCL
+
+	i = Tcl_EvalObjv(interp, objc, objv, flags);
+
+	ENTER_OVERLAP
+	if (i == TCL_ERROR)
+		Tkinter_Error(self);
+	else
+		/* We could request the object result here, but doing
+		   so would confuse applications that expect a string. */
+		res = PyString_FromString(Tcl_GetStringResult(interp));
+
+	LEAVE_OVERLAP_TCL
+
+  finally:
+	for (i = 0; i < objc; i++)
+		Tcl_DecrRefCount(objv[i]);
+	if (objv != objStore)
+		ckfree(FREECAST objv);
+	return res;
+}
+
+#else /* !USING_OBJECTS */
+
 static PyObject *
 Tkapp_Call(self, args)
 	PyObject *self;
@@ -650,8 +763,8 @@ Tkapp_Call(self, args)
 	Py_DECREF(tmp);
 	return res;
 }
+#endif /* USING_OBJECTS */
 
-
 static PyObject *
 Tkapp_GlobalCall(self, args)
 	PyObject *self;
@@ -2044,6 +2157,16 @@ init_tkinter()
 
 	Tktt_Type.ob_type = &PyType_Type;
 	PyDict_SetItemString(d, "TkttType", (PyObject *)&Tktt_Type);
+
+#if TKMAJORMINOR >= 8001
+	/* Starting with Tcl 8.1, the library initialisation won't
+	   work correctly if it is not told a guess of what the
+	   executable name is. In particular, it won't find its
+	   encodings. It will ignore the name, and then proceed with
+	   the compiled-in defaults. So passing ProgramName should be
+	   ok.  */
+	Tcl_FindExecutable(Py_GetProgramName());
+#endif
 
 	if (PyErr_Occurred())
 		return;