This is the section for developers writing, or wishing to write, a package embedding C into Tcl via
critcl.
I guess that we are allowed to asssume that you, gentle reader, are here because you have written some
Tcl code which is not fast enough (any more) and you wish to make it "go faster" by replacing parts (or
all) of it with speedy C.
Another, and I believe reasonable assumption to make would be that you have already investigated and
ruled out or done things like changes to data structures and algorithms which reduce O(n*n) work to O
(n*log n), O(n), or even O(1). Of course, nothing prevents you from forging ahead here even if you have
not done such. Still, even in that case I would recommend that you consider investigating this line of
making your code go faster as well.
Now, with these introductory words out of the way, lets jump into the meat of things.
ASIMPLEPROCEDURE
Starting simple, let us assume that the Tcl code in question is something like
proc math {x y z} {
return [expr {(sin($x)*rand())/$y**log($z)}]
}
with the expression pretending to be something very complex and slow. Converting this to C we get:
critcl::cproc math {double x double y double z} double {
double up = rand () * sin (x);
double down = pow(y, log (z));
return up/down;
}
Notable about this translation:
[1] All the arguments got type information added to them, here "double". Like in C the type precedes
the argument name. Other than that it is pretty much a Tcl dictionary, with keys and values
swapped.
[2] We now also have to declare the type of the result, here "double", again.
[3] The reference manpage lists all the legal C types supported as arguments and results.
CUSTOMTYPES,INTRODUCTION
When writing bindings to external libraries critcl::cproc is usually the most convenient way of writing
the lower layers. This is however hampered by the fact that critcl on its own only supports a few
standard (arguably the most import) standard types, whereas the functions we wish to bind most certainly
will use much more, specific to the library's function.
The critcl commands argtype, resulttype and their adjuncts are provided to help here, by allowing a
developer to extend critcl's type system with custom conversions.
This and the three following sections will demonstrate this, from trivial to complex.
The most trivial use is to create types which are aliases of existing types, standard or other. As an
alias it simply copies and uses the conversion code from the referenced types.
Our example is pulled from an incomplete project of mine, a binding to JeffreyKegler's libmarpa library
managing Earley parsers. Several custom types simply reflect the typedef's done by the library, to make
the critcl::cprocs as self-documenting as the underlying library functions themselves.
critcl::argtype Marpa_Symbol_ID = int
critcl::argtype Marpa_Rule_ID = int
critcl::argtype Marpa_Rule_Int = int
critcl::argtype Marpa_Rank = int
critcl::argtype Marpa_Earleme = int
critcl::argtype Marpa_Earley_Set_ID = int
...
method sym-rank: proc {
Marpa_Symbol_ID sym
Marpa_Rank rank
} Marpa_Rank {
return marpa_g_symbol_rank_set (instance->grammar, sym, rank);
}
...
CUSTOMTYPES,SEMI-TRIVIAL
A more involved custom argument type would be to map from Tcl strings to some internal representation,
like an integer code.
The first example is taken from the tclyaml package, a binding to the libyaml library. In a few places we
have to map readable names for block styles, scalar styles, etc. to the internal enumeration.
critcl::argtype yaml_sequence_style_t {
if (!encode_sequence_style (interp, @@, &@A)) return TCL_ERROR;
}
...
critcl::ccode {
static const char* ty_block_style_names [] = {
"any", "block", "flow", NULL
};
static int
encode_sequence_style (Tcl_Interp* interp, Tcl_Obj* style,
yaml_sequence_style_t* estyle)
{
int value;
if (Tcl_GetIndexFromObj (interp, style, ty_block_style_names,
"sequence style", 0, &value) != TCL_OK) {
return 0;
}
*estyle = value;
return 1;
}
}
...
method sequence_start proc {
pstring anchor
pstring tag
int implicit
yaml_sequence_style_t style
} ok {
/* Syntax: <instance> seq_start <anchor> <tag> <implicit> <style> */
...
}
...
It should be noted that this code precedes the advent of the supporting generator package critcl::emap.
using the generator the definition of the mapping becomes much simpler:
critcl::emap::def yaml_sequence_style_t {
any 0
block 1
flow 2
}
Note that the generator will not only provide the conversions, but also define the argument and result
types needed for their use by critcl::cproc. Another example of such a semi-trivial argument type can be
found in the CRIMP package, which defines a Tcl_ObjType for image values. This not only provides a basic
argument type for any image, but also derived types which check that the image has a specific format.
Here we see for the first time non-integer arguments, and the need to define the C types used for
variables holding the C level value, and the type of function parameters (Due to C promotion rules we may
need different types).
critcl::argtype image {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
} crimp_image* crimp_image*
...
set map [list <<type>> $type]
critcl::argtype image_$type [string map $map {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
if (@A->itype != crimp_imagetype_find ("crimp::image::<<type>>")) {
Tcl_SetObjResult (interp,
Tcl_NewStringObj ("expected image type <<type>>",
-1));
return TCL_ERROR;
}
}] crimp_image* crimp_image*
...
CUSTOMTYPES,SUPPORTSTRUCTURES
The adjunct command critcl::argtypesupport is for when the conversion needs additional definitions, for
example a helper structure.
An example of this can be found among the standard types of critcl itself, the pstring type. This type
provides the C function with not only the string pointer, but also the string length, and the Tcl_Obj*
this data came from. As critcl::cproc's calling conventions allow us only one argument for the data of
the parameter a structure is needed to convey these three pieces of information.
Thus the argument type is defined as
critcl::argtype pstring {
@A.s = Tcl_GetStringFromObj(@@, &(@A.len));
@A.o = @@;
} critcl_pstring critcl_pstring
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
In the case of such a structure being large we may wish to allocate it on the heap instead of having it
taking space on the stack. If we do that we need another adjunct command, critcl::argtyperelease. This
command specifies the code required to release dynamically allocated resources when the worker function
returns, before the shim returns to the caller in Tcl. To keep things simple our example is synthetic, a
modification of pstring above, to demonstrate the technique. An actual, but more complex example is the
code to support the variadic args argument of critcl::cproc.
critcl::argtype pstring {
@A = (critcl_pstring*) ckalloc(sizeof(critcl_pstring));
@A->s = Tcl_GetStringFromObj(@@, &(@A->len));
@A->o = @@;
} critcl_pstring* critcl_pstring*
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
critcl::argtyperelease pstring {
ckfree ((char*)) @A);
}
Note, the above example shows only the most simple case of an allocated argument, with a conversion that
cannot fail (namely, string retrieval). If the conversion can fail then either the allocation has to be
defered to happen only on successful conversion, or the conversion code has to release the allocated
memory itself in the failure path, because it will never reach the code defined via
critcl::argtyperelease in that case.
CUSTOMTYPES,RESULTS
All of the previous sections dealt with argument conversions, i.e. going from Tcl into C. Custom result
types are for the reverse direction, from C to Tcl. This is usually easier, as most of the time errors
should not be possible. Supporting structures, or allocating them on the heap are not really required and
therefore not supported.
The example of a result type shown below was pulled from KineTcl. It is a variant of the builtin result
type Tcl_Obj*, aka object. The builtin conversion assumes that the object returned by the function has a
refcount of 1 (or higher), with the function having held the reference, and releases that reference after
placing the value into the interp result. The conversion below on the other hand assumes that the value
has a refcount of 0 and thus that decrementing it is forbidden, lest it be released much to early, and
crashing the system.
critcl::resulttype KTcl_Obj* {
if (rv == NULL) { return TCL_ERROR; }
Tcl_SetObjResult(interp, rv);
/* No refcount adjustment */
return TCL_OK;
} Tcl_Obj*
This type of definition is also found in Marpa and recent hacking hacking on CRIMP introduced it there as
well. Which is why this definition became a builtin type starting with version 3.1.16, under the names
Tcl_Obj*0 and object0.
Going back to errors and their handling, of course, if a function we are wrapping signals them in-band,
then the conversion of such results has to deal with that. This happens for example in KineTcl, where we
find
critcl::resulttype XnStatus {
if (rv != XN_STATUS_OK) {
Tcl_AppendResult (interp, xnGetStatusString (rv), NULL);
return TCL_ERROR;
}
return TCL_OK;
}
critcl::resulttype XnDepthPixel {
if (rv == ((XnDepthPixel) -1)) {
Tcl_AppendResult (interp,
"Inheritance error: Not a depth generator",
NULL);
return TCL_ERROR;
}
Tcl_SetObjResult (interp, Tcl_NewIntObj (rv));
return TCL_OK;
}
HANDLINGAVARIABLENUMBEROFARGUMENTS
In ASimpleProcedure we demonstrated how easy a translation to C can be. Is it still as easy when we
introduce something moderately complex like handling a variable number of arguments ? A feature which is
needed to handle commands with options and optional arguments ?
Well, starting with version 3.1.16 critcl::cproc does have full support for optional arguments, args-
style variadics, and default values, extending its range to everything covered by the builtin proc. The
only thing not truly supported are options (i.e. flag arguments) of any kind.
For the moment, and the example, let us pretend that we can use critcl::cproc only if the number of
arguments is fully known beforehand, i.e. at the time of declaration. Then we have to use
critcl::ccommand to handle the translation of the procedure shown below:
proc math {args} {
set sum 0
foreach y $args { set sum [expr {$sum + $y}] }
return $sum
}
Its advantage: Access to the low-level C arguments representing the Tcl arguments of the command. Full
control over argument conversion, argument validation, etc.
Its disadvantage: Access to the low-level C arguments representing the Tcl arguments of the command.
Assuming the burden of having to write argument conversion, argument validation, etc. Where critcl::cproc
handles the task of converting from Tcl to C values (for arguments) and back (for the result), with
critcl::command it is the developer who has to write all this code.
Under our restriction the translation of the example is:
critcl::ccommand math {cd ip oc ov} {
double sum = 0;
double y;
oc --;
while (oc) {
if (Tcl_GetDoubleFromObj (ip, ov[oc], &y) != TCL_OK) {
return TCL_ERROR;
}
sum += y;
oc --;
}
Tcl_SetObjResult (ip, Tcl_NewDoubleObj (sum));
return TCL_OK:
}
Notable about this translation:
[1] As promised/threatened, all the conversions between the Tcl and C domains are exposed, and the
developer should know her way around Tcl's C API.
[2] The four arguments "cd ip oc ov" are our names for the low-level arguments holding
[1] ClientData (reference)
[2] Tcl_Interp (reference)
[3] Number of arguments, and
[4] Array of argument values, each a Tcl_Obj*.
This list of arguments, while not optional in itself, is allowed to be empty, and/or to contain
empty strings as argument names. If we do that critcl will supply standard names for the missing
pieces, namely:
[1] clientdata
[2] interp
[3] objc
[4] objv
Now, letting go of our pretenses regarding the limitations of critcl::cproc, due to the support it does
have for args-style variadics (since version 3.1.16) we can write a much simpler translation:
critcl::cproc math {double args} double {
double sum = 0;
args.c --;
while (args.c) {
sum += args.v[args.c];
args.c --;
}
return sum;
}
DATAASATCLCOMMAND
Here we assume that we have a Tcl procedure which returns a fixed string. In the final product we are
going to C to hide this string from the casual user.
proc somedata {} {
return {... A large blob of characters ...}
}
The translation of this is simple and easy:
package require critcl
critcl::cdata somedata {... A large blob of characters ...}
There is nothing really notable here.
BLOCKSOFARBITRARYC
Often just defining Tcl commands in C, as demonstrated in the sections ASimpleProcedure, HandlingAVariableNumberOfArguments, and DataAsATclCommand is not really enough. For example we may have
several of our new C commands using the same code over and over, and we wish avoid this duplication. Or
we wish to pull in declarations and definitions from some external library.
In both cases we require the ability to embed an unstructured block of C code which can contain whatever
we want, defines, functions, includes, etc. without being directly tied to Tcl commands. The command
critcl::code provides us with exactly that. As our example now an excerpt taken from real code, the top
of the "sha1c.tcl" critcl file in the sha1 module of Tcllib:
critcl::ccode {
#include "sha1.h"
#include <stdlib.h>
#include <assert.h>
static
Tcl_ObjType sha1_type; /* fast internal access representation */
static void
sha1_free_rep(Tcl_Obj* obj)
{
SHA1_CTX* mp = (SHA1_CTX*) obj->internalRep.otherValuePtr;
Tcl_Free(mp);
}
...
}
We see here the beginning of the C code defining a custom Tcl_ObjType holding the data of a SHA1 context
used during the incremental calculation of sha1 hashes.
CONSTANTVALUES
While one might believe that there is no need for commands which returns constant values that is not
true. Commands reporting on compile-time configuration, like version numbers, available features, etc.
are at least one use case for such commands.
The reason for creating critcl commands to support them ? Convenience to the user, yes, but further than
that, the ability to optimize the internals, i.e. the generated code.
A cproc would be easy to write, but incurs overhead due to a superfluous work function. A ccommand has no
overhead, except that of the user having to write the argument checking and result conversion.
Using critcl::cconst is both convenient and without code overhead. Our example is a function found in
package tcl-linenoise, that is, if cconst had existed at the time of writing. It returns a configuration
value reporting to the policy layer if an extended mode for hidden input is available from the bound
linenoise, or not.
critcl::cconst linenoise::hidden_extended boolean 1
LIFTINGCONSTANTS
When writing a critcl-based package to make a third-party library available to scripts we do not only
have to make the relevant functions available as commands, often we also have to know all the various
constants, flags, etc. these functions take.
Rather than writing such magic numbers directly we would greatly prefer to use symbolic names instead.
Instead of providing one or more commands to list and map the magic numbers to strings critcl only
provides a single command which allows the export of C defines and enumeration values, mapping them to
Tcl variables of the given names, whose values are the associated magic numbers.
This is good enough because the developers of the third-party library were very likely like us and wanted
to use symbolic names instead of magic numbers. Which in C are declared as via defines and enumeration
types. We just have to lift them up.
Our example comes from cryptkit, a Tcl binding to cryptlib, a cryptography library. The command
critcl::cdefines CRYPT_* ::crypt
maps all Cryptlib specific #defines and enums into the namespace ::crypt, telling critcl to create
aliases to the symbols.
Similarly
critcl::cdefines {
NULL
TRUE
FALSE
TCL_OK
TCL_ERROR
} ::crypt
maps the listed defines into the namespace ::crypt.
An important thing to note: These commands donotcreate the defines in the C level. They only lift pre-
existing material. Which can come from the headers of the third-party library, the usual case, but also
from BlocksofarbitraryC.
A corrollary to the above: What is not where, cannot be lifted. All listed names and patterns which have
no actual C code declaring them are ignored, i.e. not mapped.
FINDINGHEADERFILES
A notable thing in the example shown in the section about BlocksofarbitraryC is the
#include "sha1.h"
statement. Where does this header come from ? Looking at the Tcllib module we will find that the header
is actually a sibling to the "sha1c.tcl" file containing the embedded C code. However, critcl does not
know that. It has to be told. While without that knowledge it will invoke the compiler just fine, the
compilation will fail because the header is not on the include paths used by the compiler, and therefore
will not be found.
For this we have the critcl::cheaders command. It enables us to either tell the compiler the path(s)
where the required headers can be found, using
critcl::cheaders -I/path/to/headers/
or to tell it directly which headers we are using and where they live:
critcl::cheaders sha1.h
And now critcl knows that "sha1.h" is important, and that it lives besides the ".critcl" file which
referenced it (because of the relative path used). Note that this doesn't absolve us of the need to
"#include" the header through a critcl::ccode block. This only tells critcl where it lives so that it can
configure the compiler with the proper include paths to actually find it on use.
Further note that a C development environment is usually configured to find all the system headers,
obviating the need for a critcl::cheaders declaration when such are used. For these a plain "#include" in
a critcl::ccode block is good enough. In other words, the second form of invoking critcl::cheaders is
pretty much only for headers which accompany the ".critcl" file.
SEPARATECSOURCES
In all of the examples shown so far the C code was fully embedded in a ".critcl" file. However, if the C
part is large it can make sense to break it out of the ".critcl" file into one or more separate proper
".c" file(s).
The critcl::csources command can then be used to make this code known to the original ".critcl" file
again. This command accepts the paths to the ".c" files as arguments, and glob patterns as well. Our
example comes from the struct::graph package in Tcllib. Its core C functions are in separate files, and
the ".critcl" code then makes them known via:
namespace eval ::struct {
# Supporting code for the main command.
critcl::cheaders graph/*.h
critcl::csources graph/*.c
...
}
which tells critcl that these files are in the subdirectory "graph" relative to the location of
"graph_c.tcl", which is the relevant ".critcl" file.
This example also demonstrates again the use of critcl::cheaders, which we also saw in section Findingheaderfiles.
FINDINGEXTERNALLIBRARIES
When creating a package exposing some third-party library to Tcl Findingheaderfiles is only the first
part, to enable failure-free compilation. We also have to find the library/ies themselves so that they
can be linked to our package. This is described here. The last issue, Liftingconstants from C to Tcl for
the use by scripts is handled in a separate section and example.
The relevant command is critcl::clibraries. Its basic semantics are like that of critcl::cheaders, i.e.
It enables us to tell the linker the path(s) where the required libraries can be found, using
critcl::clibraries -L/path/to/libraries/
name them
critcl::clibraries -lfoo
or tell it directly which libraries we are using and where they live:
critcl::clibraries /path/to/library/foo.so
This last way of using should be avoided however, as it intermingles searching and naming, plus the name
is platform dependent.
For OS X we additionally have the critcl::framework command which enables us to name the frameworks used
by our package. Note that this command can be used unconditionally. If the build target is not OS X it
is ignored.
CUSTOMIZINGTHECOMPILEANDLINKSTEPS
The commands critcl::cflags and critcl::ldflags enable you to provide custom options to the compile and
link phases for a ".critcl" file.
This usually becomes necessary if the C code in question comes from an external library we are writing a
Tcl binding for, with multiple configurations to select, non-standard header locations, and other things.
Among the latter, especially platform-specific settings, for example byteorder.
This makes critcl::check an important adjunct command, as this is the API for CheckingTheEnvironment,
and then selecting the compile & link flags to use.
Icurrentlyhavenospecificexampletodemonstratethesecommands.HAVINGBOTHCANDTCLFUNCTIONALITY
Often enough only pieces of a package require recoding in C to boost the whole system. Or, alternatively,
the package in question consists of a low-level layer C with a Tcl layer above encoding policies and
routing to the proper low-level calls, creating a nicer (high-level) API to the low-level functionality,
etc.
For all of this we have to be able to write a package which contains both C and Tcl, nevermind the fact
the C parts are embedded in Tcl.
The easiest way to structure such a package is to have several files, each with a different duty. First,
a ".critcl" file containing the embedded C, and second one or more ".tcl" files providing the Tcl parts.
Then use the critcl::tsources command in the ".critcl" file to link the two parts together, declaring the
".tcl" files as necessary companions of the C part.
package require critcl
critcl::tsources your-companion.tcl ; # Companion file to use
... embedded C via critcl commands ...
With a declaration as shown above the companion file will be automatically sourced when the C parts are
made available, thus making the Tcl parts available as well.
USINGCWITHTCLFUNCTIONALITYASFALLBACK
There is one special case of HavingbothCandTclfunctionality which deserves its own section.
The possibility of not having the fast C code on some platform, and using a slower Tcl implementation of
the functionality. In other words, a fallback which keeps the package working in the face of failure to
build the C parts. A more concrete example of this would be a module implementing the SHA hash, in both C
and Tcl, and using the latter if and only if the C implementation is not available.
There two major possibilities in handling such a situation.
[1] Keep all the pieces separated. In that scenario our concrete example would be spread over three
packages. Two low-level packages sha::c and sha::tcl containing the two implementations of the
algorithm, and, thirdly, a coordinator package sha which loads either of them, based on
availability.
The Tcllib bundle of packages contains a number of packages structured in this manner, mostly in
the struct module.
Writing the C and Tcl parts should be simple by now, with all the examples we had so far. The only
non-trivial part is the coordinator, and even that if and only if we wish to make it easy to write
a testsuite which can check both branches, C, and Tcl without gymnastics. So, the most basic
coordinator would be
set sha::version 1
if {[catch {
package require sha::c $sha::version
}]} {
package require sha::tcl $sha::version
}
package provide sha $sha::version
It tries to load the C implementation first, and falls back to the Tcl implementation if that
fails. The code as is assumes that both implementations create exactly the same command names,
leaving the caller unaware of the choice of implementations.
A concrete example of this scheme can be found in Tcllib's md5 package. While it actually uses
ythe Trf as its accelerator, and not a critcl-based package the principle is the same. It also
demonstrates the need for additional glue code when the C implementation doesn't exactly match the
signature and semantics of the Tcl implementation.
This basic coordinator can be easily extended to try more than two packages to get the needed
implementation. for example, the C implementation may not just exist in a sha::c package, but also
bundled somewhere else. Tcllib, for example, has a tcllibc package which bundles all the C parts
of its packages which have them in a single binary.
Another direction to take it in is to write code which allows the loading of multiple
implementations at the same time, and then switching between them at runtime. Doing this requires
effort to keep the implementations out of each others way, i.e. they cannot provide the same
command names anymore, and a more complex coordinator as well, which is able to map from the
public command names to whatever is provided by the implementation.
The main benefit of this extension is that it makes testing the two different implementations
easier, simply run through the same set of tests multiple times, each time with different
implementation active. The disadvantage is the additional complexity of the coordinator's
internals. As a larger example of this technique here is the coordinator
"modules/struct/queue.tcl" handling the C and Tcl implementations of Tcllib's struct::queue
package:
# queue.tcl --
# Implementation of a queue data structure for Tcl.
package require Tcl 8.4
namespace eval ::struct::queue {}
## Management of queue implementations.
# ::struct::queue::LoadAccelerator --
# Loads a named implementation, if possible.
proc ::struct::queue::LoadAccelerator {key} {
variable accel
set r 0
switch -exact -- $key {
critcl {
# Critcl implementation of queue requires Tcl 8.4.
if {![package vsatisfies [package provide Tcl] 8.4]} {return 0}
if {[catch {package require tcllibc}]} {return 0}
set r [llength [info commands ::struct::queue_critcl]]
}
tcl {
variable selfdir
if {
[package vsatisfies [package provide Tcl] 8.5] &&
![catch {package require TclOO}]
} {
source [file join $selfdir queue_oo.tcl]
} else {
source [file join $selfdir queue_tcl.tcl]
}
set r 1
}
default {
return -code error "invalid accelerator/impl. package $key: must be one of [join [KnownImplementations] {, }]"
}
}
set accel($key) $r
return $r
}
# ::struct::queue::SwitchTo --
# Activates a loaded named implementation.
proc ::struct::queue::SwitchTo {key} {
variable accel
variable loaded
if {[string equal $key $loaded]} {
# No change, nothing to do.
return
} elseif {![string equal $key ""]} {
# Validate the target implementation of the switch.
if {![info exists accel($key)]} {
return -code error "Unable to activate unknown implementation \"$key\""
} elseif {![info exists accel($key)] || !$accel($key)} {
return -code error "Unable to activate missing implementation \"$key\""
}
}
# Deactivate the previous implementation, if there was any.
if {![string equal $loaded ""]} {
rename ::struct::queue ::struct::queue_$loaded
}
# Activate the new implementation, if there is any.
if {![string equal $key ""]} {
rename ::struct::queue_$key ::struct::queue
}
# Remember the active implementation, for deactivation by future
# switches.
set loaded $key
return
}
# ::struct::queue::Implementations --
# Determines which implementations are
# present, i.e. loaded.
proc ::struct::queue::Implementations {} {
variable accel
set res {}
foreach n [array names accel] {
if {!$accel($n)} continue
lappend res $n
}
return $res
}
# ::struct::queue::KnownImplementations --
# Determines which implementations are known
# as possible implementations.
proc ::struct::queue::KnownImplementations {} {
return {critcl tcl}
}
proc ::struct::queue::Names {} {
return {
critcl {tcllibc based}
tcl {pure Tcl}
}
}
## Initialization: Data structures.
namespace eval ::struct::queue {
variable selfdir [file dirname [info script]]
variable accel
array set accel {tcl 0 critcl 0}
variable loaded {}
}
## Initialization: Choose an implementation,
## most preferred first. Loads only one of the
## possible implementations. And activates it.
namespace eval ::struct::queue {
variable e
foreach e [KnownImplementations] {
if {[LoadAccelerator $e]} {
SwitchTo $e
break
}
}
unset e
}
## Ready
namespace eval ::struct {
# Export the constructor command.
namespace export queue
}
package provide struct::queue 1.4.2
In this implementation the coordinator renames the commands of the low-level packages to the
public commands, making the future dispatch as fast as if the commands had these names anyway, but
also forcing a spike of bytecode recompilation if switching is ever done at the runtime of an
application, and not just used for testing, and possibly disrupting introspection by the commands,
especially if they move between different namespaces.
A different implementation would be to provide the public commands as procedures which consult a
variable to determine which of the loaded implementations is active, and then call on its
commands. This doesn't disrupt introspection, nor does it trigger bytecode recompilation on
switching. But it takes more time to dispatch to the actual implementation, in every call of the
public API for the package in question.
A concrete example of this scheme can be found in Tcllib's crc32 package.
[2] Mix the pieces together. Please note that while I am describing how to make this work I strongly
prefer and recommend to use the previously shown approach using separate files/packages. It is
much easier to understand and maintain. With this warning done, lets go into the nuts and bolts.
If we care only about mode "compile & run" things are easy:
package require critcl
if {![critcl::compiling]} {
proc mycommand {...} {
...
}
} else {
critcl::cproc mycommand {...} {
...
}
}
The command critcl::compiling tells us whether we have a compiler available or not, and in the
latter case we implement our command in Tcl.
Now what happens when we invoke mode "generate package" ? ... compiler failure ... ... ok - C
code - everything fine ... fail - no package ? or just no C code ? declare self as tsource, to be
used ? ... platform-specific C/Tcl -- uuid.
UNLAZYPACKAGES
By default critcl is a bit inconsistent between modes "compile & run" and "generate package". The result
of the latter is a standard Tcl package which loads and sources all of its files immediately when it is
required. Whereas "compile & run" defers actual compilation, linking, and loading until the first time
one of the declared commands is actually used, making this very lazy.
This behaviour can be quite unwanted if Tcl companion files, or other users of the C commands use
introspection to determine the features they have available. Just using [infocommands] doesn't cut it,
the auto_index array has to be checked as well, making things quite inconvenient for the users.
To fix this issue at the source, instead of in each user, be it inside of the package itself, or other
packages, we have the command critcl::load. Used as the last command in a ".critcl" file it forces the
compile, link, and load trinity, ensuring that all C commands are available immediately.
package require critcl
... Declare C procedures, commands, etc.
critcl::load ; # Force build and loading.
Note that is not allowed, nor possible to use critcl commands declaring anything after critcl::load has
been called. I.e., code like
package require critcl
... Declare C procedures, commands, etc.
critcl::load ; # Force build and loading.
... More declarations of C code, ...
critcl::code { ... }
will result in an error. The only package-related commands still allowed are
[1] critcl::done
[2] critcl::failed
[3] critcl::load
as these only query information, namely the build status, and are protected against multiple calls.