Tk2portableTk - how to make your Tk source portable to other interpreted languages.
Ilya Zakharevich <ilya@math.ohio-state.edu> has contributed most of this document. Many thanks.
PortableTk is an attempt to make Tk useful from other languages. Currently tk4.0 runs under Perl using this approach. Below, Lang is the notation for an external language to which PortableTk glues Tk code.
The main problem with using the code developed for
TCL with different languages is the absence of data
types: almost anything is char*
. It makes automatic
translation hopeless. However, if you typedef
several new
symbols to be char*
, you can still use your code in
TCL, and it will make the automatic
translation possible.
Another problem with the approach that everything is a string is
impossibility to have a result that says NotApplicable without setting
an error. Thus different Tk command return different
string values that mean error happened, like ""
,
" "
or "??"
. Other languages can be more
flexible, so in portableTk you should inform the
compiler that what you want to return means error (see Setting
variables).
Currently PortableTk uses several different approachs to simplify translation: several TCL functions that are especially dangerous to use are undefined, so you can easily find places that need to be updated to use Language-independent functions based on compiler warnings. Eventually a way to use these Language-independent functions under proper TCL will be also provided. The end of this document provides a starting point for such a project.
pTk, that is a port of Tk, is very special with respect to porting of other code to portableTk. The problem is that currently there is very little hope to merge the modifications back into Tk, so a special strategy is needed to maintain this port. Do not use this strategy to port your own code.
pTk is produced from Tk via a
two-step process: first, some manual editing (the result is in the
subdirectory mTk
), and second, automatic conversion by the
munge
script (written in Perl). Thus the subdirectory
pTk/mTk
contains code with minimal possible difference from
the virgin Tk code, so it is easier to
merge (1) the differences between Tk
versions into modified code.
It looks like the strategy for a portable code should be exactly
opposite: starting from TCL-based code, apply
munge
, and then hand-edit the resulting code. Probably it
is also possible to target your code to portableTk from
scratch, since this will make it possible to run it under a lot of
Languages.
The only reason anyone would like to look into contents of
pTk/mTk
directory is to find out which constructs are not
supported by munge
. On the other hand, pTk
directory contains code that is conformant to
portableTk, so you can look there to find example
code.
munge
is the script that converts most common
Tk constructs to their portableTk
equivalent. For your code to qualify, you should follow
Tk conventions on indentation and names of variables,
in particular, the array of arguments for the ...CmdProc
should be called argv
.
For details on what munge
can do, see Translation of
some TCL functions.
PortableTk provides a symbol ????
. If
this symbol is defined, your source is compiled with it.
PortableTk defines several new types of configuration options:
TK_CONFIG_CALLBACK TK_CONFIG_LANGARG TK_CONFIG_SCALARVAR TK_CONFIG_HASHVAR TK_CONFIG_ARRAYVAR TK_CONFIG_IMAGE
You should use them instead of TK_CONFIG_STRING whenever appropriate. This allows your application to receive a direct representation of the corresponding resource instead of the string representation, if this is possible under given language.
???? It looks like TK_CONFIG_IMAGE
and
TK_CONFIG_SCALARVAR
set variables of type
char*
.
The following data types are defined:
is the main datatype of the language. This is a type that your C
function gets pointers to for arguments when the corresponding
Lang function is called. The corresponding config type is
TK_CONFIG_LANGARG
. This is also a type that keeps
information about contents of Lang variable.
Is a substitute for a char *
that contains name of
variable. In Lang it is an object that contains reference to
another Lang variable.
????
LangCallback*
a substitute for a char *
that contains command to call. The corresponding config type is
TK_CONFIG_CALLBACK
.
It is the type that the Lang_SplitList
sets. Before you
call it, declare Args *args; LangFreeProc *freeProc = NULL; ... code =
Lang_SplitList(interp, value, &argc, &args, &freeProc);
After you use the split values, call if (args != NULL &&
freeProc) (*freeProc)(argc,args); It is not guaranteed that the
args
can survive deletion of value
.
The following macros and functions are used for conversion between strings and the additional types:
LangCallback * LangMakeCallback(Tcl_Obj *) Tcl_Obj * LangCallbackArg(LangCallback *) char * LangString(Tcl_Obj *)
After you use the result of LangCallbackArg(), you
should free it with freeProc
LANG_DYNAMIC
(it
is not guaranteed that any change of Tcl_Obj *
will not be
reflected in <LangCallback>, so you cannot do LangSet...() in
between, and you should reset it to NULL
if you want to do
any further assignments to this Tcl_Obj *
).
The following function returns the Tcl_Obj *
that is a
reference to Var
:
Tcl_Obj * LangVarArg(Var)
???? It is very anti-intuitive, I hope the name is changed.
int LangCmpCallback(LangCallback *a,Tcl_Obj * b)
(currently only a stub), and, at last,
LangCallback * LangCopyCallback(LangCallback *)
Above we have seen the new datatype LangCallback
and the
corresponding Config option TK_CONFIG_CALLBACK
.
The following functions are provided for manipulation of
LangCallback
s:
void LangFreeCallback(LangCallback *) int LangDoCallback(Tcl_Interp *,LangCallback *, int result,int argc, char *format,...)
The argument format
of LangDoCallback
should contain a string that is suitable for sprintf
with
optional arguments of LangDoCallback
. result
should be false if result of callback is not needed.
int LangMethodCall(Tcl_Interp *,Tcl_Obj *,char *method, int result,int argc,...)
????
Conceptually, LangCallback*
is a substitute for
ubiquitous char *
in TCL. So you should
use LangFreeCallback
instead of ckfree
or
free
if appropriate.
void LangFreeArg (Tcl_Obj *, Tcl_FreeProc *freeProc) Tcl_Obj * LangCopyArg (Tcl_Obj *); void Tcl_AppendArg (Tcl_Interp *interp, Tcl_Obj *) void LangSetString(Tcl_Obj * *, char *s) void LangSetDefault(Tcl_Obj * *, char *s)
These two are equivalent unless s is an empty string. In this case
LangSetDefault
behaves like LangSetString
with
s==NULL
, i.e., it sets the current value of the
Lang variable to be false.
void LangSetInt(Tcl_Obj * *,int) void LangSetDouble(Tcl_Obj * *,double)
The Lang functions separate uninitialized and initialized
data comparing data with NULL
. So the declaration for an
Tcl_Obj *
should look like
Tcl_Obj * arg = NULL;
if you want to use this arg
with the above functions.
After you are done, you should use LangFreeArg
with
TCL_DYNAMIC
as freeProc
.
Use
to check that an object is false;
????
to make a proper shutdown;
to call Lang eval
;
currently stubs only;
to save the structure arg
into Lang variable
*varPtr
;
to free the result;
????
unsupported????;
Another useful construction is
Tcl_Obj * variable = LangFindVar(interp, Tk_Window tkwin, char *name);
After using the above function, you should call
LangFreeVar(Var variable);
???? Note discrepancy in types!
If you want to find the value of a variable (of type
Tcl_Obj *
) given the variable name, use
Tcl_GetVar(interp, varName, flags)
. If you are interested
in the string value of this variable, use
LangString(Tcl_GetVar(...))
.
To get a C array of Tcl_Obj *
of length
n
, use
Tcl_Obj * *args = LangAllocVec(n); ... LangFreeVec(n,args);
You can set the values of the Tcl_Obj *
s using
LangSet...
functions, and get string value using
LangString
.
If you want to merge an array of Tcl_Obj *
s into one
Tcl_Obj *
(that will be an array variable), use
result = Tcl_Merge(listLength, list);
We mark items that can be dealt with by munge
by
Autoconverted.
does not take (char*)NULL
, but NULL
as
delimiter. Autoconverted.
Tk_CreateWidget
, Tk_DeleteWidget
, the
second argument is the window itself, not the pathname.
Autoconverted.
Tcl_IntResults(interp,4,0,...)
.
Autoconverted.
Tcl_SetResult(interp,"1", TCL_STATIC)
.
Autoconverted.
Tcl_GetResult(interp)
. Autoconverted.
Tk_WidgetResult(interp,textPtr->tkwin)
.
Autoconverted.
Use a single command void Tcl_DoubleResults(Tcl_Interp *interp, int
append, int argc,...); append
governs whether it is
required to clear the result first. A similar command for
int
arguments is Tcl_IntResults
.
Use Lang_SplitList
(see the description above).
To use your portableTk program with TCL, put
#include "ptcl.h"
before inclusion of tk.h
, and link the
resulting code with ptclGlue.c
.
These files currently implement the following:
TK_CONFIG_CALLBACK TK_CONFIG_LANGARG TK_CONFIG_SCALARVAR TK_CONFIG_HASHVAR TK_CONFIG_ARRAYVAR TK_CONFIG_IMAGE
Var, Tcl_Obj *, LangCallback, LangFreeProc.
Lang_SplitList, LangString, LangSetString, LangSetDefault, LangSetInt, LangSetDouble Tcl_ArgResult, LangCallbackArg, LangSaveVar, LangFreeVar, LangFreeSplitProc, LangFreeArg, Tcl_DoubleResults, Tcl_IntResults, LangDoCallback, Tk_WidgetResult, Tcl_CreateCommand, Tcl_DeleteCommand, Tcl_GetResult.
Current implementation contains enough to make it possible to compile
mTk/tkText*.[ch]
with the virgin Tk.
PortableTk defines following new types of events:
TK_EVENTTYPE_NONE TK_EVENTTYPE_STRING TK_EVENTTYPE_NUMBER TK_EVENTTYPE_WINDOW TK_EVENTTYPE_ATOM TK_EVENTTYPE_DISPLAY TK_EVENTTYPE_DATA
and a function
char * Tk_EventInfo(int letter, Tk_Window tkwin, XEvent *eventPtr, KeySym keySym, int *numPtr, int *isNum, int *type, int num_size, char *numStorage)
If you start with working TCL code, you can start conversion using
the above hints. Good indication that you are doing is OK is absence of
sprintf
and sscanf
in your code (at least in
the part that is working with interpreter).
What is described here is not included into base portableTk distribution. Currently it is coded in TCL and as Perl macros (core is coded as functions, so theoretically you can use the same object files with different interpreted languages).
Dynamic arrays in TCL are used for two different purposes: to construct strings, and to construct lists. These two usages will have separate interfaces in other languages (since list is a different type from a string), so you should use a different interface in your code.
The type for construction of dynamic lists is
ListFactory
. The API below is a counterpart of the API for
construction of dynamic lists in TCL:
void ListFactoryInit(ListFactory *) void ListFactoryFinish(ListFactory *) void ListFactoryFree(ListFactory *) Tcl_Obj * * ListFactoryArg(ListFactory *) void ListFactoryAppend(ListFactory *, Tcl_Obj * *arg) void ListFactoryAppendCopy(ListFactory *, Tcl_Obj * *arg) ListFactory * ListFactoryNewLevel(ListFactory *) ListFactory * ListFactoryEndLevel(ListFactory *) void ListFactoryResult(Tcl_Interp *, ListFactory *)
The difference is that a call to ListFactoryFinish
should precede the actual usage of the value of
ListFactory
, and there are two different ways to append an
Tcl_Obj *
to a ListFactory
:
ListFactoryAppendCopy() guarantees that the value of
arg
is copied to the list, but
ListFactoryAppend() may append to the list a reference
to the current value of arg
. If you are not going to change
the value of arg
after appending, the call to
ListFactoryAppend may be quicker.
As in TCL, the call to
ListFactoryFree() does not free the
ListFactory
, only the objects it references.
The functions ListFactoryNewLevel() and
ListFactoryEndLevel() return a pointer to a
ListFactory
to fill. The argument of
ListFactoryEndLevel() cannot be used after a call to
this function.
Production of strings are still supported in portableTk.
The following functions for getting a value of an
Tcl_Obj *
may be provided:
double LangDouble(Tcl_Obj *) int LangInt(Tcl_Obj *) long LangLong(Tcl_Obj *) int LangIsList(Tcl_Obj * arg)
The function LangIsList() is supported only
partially under TCL, since there is no data types. It
checks whether there is a space inside the string arg
.
While LangSetDouble() and
LangSetInt() are supported ways to assign numbers to
assign an integer value to a variable, for the sake of efficiency under
TCL it is supposed that the destination of these
commands was massaged before the call so it contains a long enough
string to sprintf() the numbers inside it. If you are
going to immediately use the resulting Tcl_Obj *
, the best
way to do this is to declare a buffer in the beginning of a block by
dArgBuffer;
and assign this buffer to the Tcl_Obj *
by
void LangSetDefaultBuffer(Tcl_Obj * *)
You can also create the buffer(s) manually and assign them using
void LangSetBuffer(Tcl_Obj * *, char *)
This is the only choice if you need to assign numeric values to
several Tcl_Obj *
s simultaneously. The advantage of the
first approach is that the above declarations can be made
nop
s in different languages.
Note that if you apply LangSetDefaultBuffer
to an
Tcl_Obj *
that contains some value, you can create a leak
if you do not free that Tcl_Obj *
first. This is a
non-problem in real languages, but can be a trouble in TCL
,
unless you use only the above API.
The API for creating a new Tcl_Obj *
is
void LangNewArg(Tcl_Obj * *, LangFreeProc *)
The API for creating a new Tcl_Obj *
is absent. Just
initialize Tcl_Obj *
to be NULL
, and apply one
of LangSet...
methods.
After you use this Tcl_Obj *
, it should be freed
thusly:
LangFreeArg(arg, freeProc)
.
Use
int LangArgEval(Tcl_Interp *, Tcl_Obj * arg)
Here arg
should be a list to evaluate, in particular,
the first element should be a LangCallback
massaged to be
an Tcl_Obj *
. The arguments can be send to the subroutine
by reference or by value in different languages.
Use Tcl_ArgResult
. It is not guaranteed that result
survives this operation, so the Tcl_Obj *
you get should be
the only mean to access the data from this moment on. After you use this
Tcl_Obj *
, you should free it with freeProc
LANG_DYNAMIC
(you can do LangSet...() in between).