1
[subsection {Using C with Tcl functionality as fallback}]
3
There is one special case of
4
[sectref {Having both C and Tcl functionality}]
5
which deserves its own section.
7
[para] The possibility of not having the fast C code on some platform,
8
and using a slower Tcl implementation of the functionality. In other
9
words, a fallback which keeps the package working in the face of
10
failure to build the C parts. A more concrete example of this would be
11
a module implementing the SHA hash, in both C and Tcl, and using the
12
latter if and only if the C implementation is not available.
14
[para] There two major possibilities in handling such a situation.
16
[list_begin enumerated]
18
[comment {======================================================}]
19
[enum] Keep all the pieces separated. In that scenario our concrete
20
example would be spread over three packages. Two low-level packages
21
[package sha::c] and [package sha::tcl] containing the two
22
implementations of the algorithm, and, thirdly, a coordinator package
23
[package sha] which loads either of them, based on availability.
25
[para] The Tcllib bundle of packages contains a number of packages
26
structured in this manner, mostly in the [term struct] module.
28
[para] Writing the C and Tcl parts should be simple by now, with all
29
the examples we had so far. The only non-trivial part is the
30
coordinator, and even that if and only if we wish to make it easy to
31
write a testsuite which can check both branches, C, and Tcl without
32
gymnastics. So, the most basic coordinator would be
37
package require sha::c $sha::version
39
package require sha::tcl $sha::version
41
package provide sha $sha::version
44
It tries to load the C implementation first, and falls back to the Tcl
45
implementation if that fails. The code as is assumes that both
46
implementations create exactly the same command names, leaving the
47
caller unaware of the choice of implementations.
49
[para] A concrete example of this scheme can be found in Tcllib's
50
[package md5] package. While it actually uses ythe [package Trf] as
51
its accelerator, and not a critcl-based package the principle is the
52
same. It also demonstrates the need for additional glue code when the
53
C implementation doesn't exactly match the signature and semantics of
54
the Tcl implementation.
56
[para] This basic coordinator can be easily extended to try more than
57
two packages to get the needed implementation. for example, the C
58
implementation may not just exist in a sha::c package, but also
59
bundled somewhere else. Tcllib, for example, has a tcllibc package
60
which bundles all the C parts of its packages which have them in a
63
[para] Another direction to take it in is to write code which allows
64
the loading of multiple implementations at the same time, and then
65
switching between them at runtime. Doing this requires effort to keep
66
the implementations out of each others way, i.e. they cannot provide
67
the same command names anymore, and a more complex coordinator as
68
well, which is able to map from the public command names to whatever
69
is provided by the implementation.
71
[para] The main benefit of this extension is that it makes testing the
72
two different implementations easier, simply run through the same set
73
of tests multiple times, each time with different implementation
74
active. The disadvantage is the additional complexity of the
75
coordinator's internals. As a larger example of this technique here is
76
the coordinator [file modules/struct/queue.tcl] handling the C and Tcl
77
implementations of Tcllib's [package struct::queue] package:
81
# Implementation of a queue data structure for Tcl.
83
package require Tcl 8.4
84
namespace eval ::struct::queue {}
86
## Management of queue implementations.
88
# ::struct::queue::LoadAccelerator --
89
# Loads a named implementation, if possible.
91
proc ::struct::queue::LoadAccelerator {key} {
94
switch -exact -- $key {
96
# Critcl implementation of queue requires Tcl 8.4.
97
if {![package vsatisfies [package provide Tcl] 8.4]} {return 0}
98
if {[catch {package require tcllibc}]} {return 0}
99
set r [llength [info commands ::struct::queue_critcl]]
104
[package vsatisfies [package provide Tcl] 8.5] &&
105
![catch {package require TclOO}]
107
source [file join $selfdir queue_oo.tcl]
109
source [file join $selfdir queue_tcl.tcl]
114
return -code error "invalid accelerator/impl. package $key:\
115
must be one of [join [KnownImplementations] {, }]"
122
# ::struct::queue::SwitchTo --
123
# Activates a loaded named implementation.
125
proc ::struct::queue::SwitchTo {key} {
129
if {[string equal $key $loaded]} {
130
# No change, nothing to do.
132
} elseif {![string equal $key ""]} {
133
# Validate the target implementation of the switch.
135
if {![info exists accel($key)]} {
136
return -code error "Unable to activate unknown implementation \"$key\""
137
} elseif {![info exists accel($key)] || !$accel($key)} {
138
return -code error "Unable to activate missing implementation \"$key\""
142
# Deactivate the previous implementation, if there was any.
144
if {![string equal $loaded ""]} {
145
rename ::struct::queue ::struct::queue_$loaded
148
# Activate the new implementation, if there is any.
150
if {![string equal $key ""]} {
151
rename ::struct::queue_$key ::struct::queue
154
# Remember the active implementation, for deactivation by future
161
# ::struct::queue::Implementations --
162
# Determines which implementations are
163
# present, i.e. loaded.
165
proc ::struct::queue::Implementations {} {
168
foreach n [array names accel] {
169
if {!$accel($n)} continue
175
# ::struct::queue::KnownImplementations --
176
# Determines which implementations are known
177
# as possible implementations.
179
proc ::struct::queue::KnownImplementations {} {
183
proc ::struct::queue::Names {} {
185
critcl {tcllibc based}
190
## Initialization: Data structures.
192
namespace eval ::struct::queue {
193
variable selfdir [file dirname [info script]]
195
array set accel {tcl 0 critcl 0}
199
## Initialization: Choose an implementation,
200
## most preferred first. Loads only one of the
201
## possible implementations. And activates it.
203
namespace eval ::struct::queue {
205
foreach e [KnownImplementations] {
206
if {[LoadAccelerator $e]} {
216
namespace eval ::struct {
217
# Export the constructor command.
218
namespace export queue
221
package provide struct::queue 1.4.2
224
In this implementation the coordinator renames the commands of the
225
low-level packages to the public commands, making the future dispatch
226
as fast as if the commands had these names anyway, but also forcing a
227
spike of bytecode recompilation if switching is ever done at the
228
runtime of an application, and not just used for testing, and possibly
229
disrupting introspection by the commands, especially if they move
230
between different namespaces.
232
[para] A different implementation would be to provide the public
233
commands as procedures which consult a variable to determine which of
234
the loaded implementations is active, and then call on its
235
commands. This doesn't disrupt introspection, nor does it trigger
236
bytecode recompilation on switching. But it takes more time to
237
dispatch to the actual implementation, in every call of the public API
238
for the package in question.
240
[para] A concrete example of this scheme can be found in Tcllib's
241
[package crc32] package.
243
[comment {======================================================}]
244
[enum] Mix the pieces together. Please note that while I am describing
245
how to make this work I strongly prefer and recommend to use the
246
previously shown approach using separate files/packages. It is much
247
easier to understand and maintain. With this warning done, lets go
248
into the nuts and bolts.
250
[para] If we care only about mode "compile & run" things are easy:
253
package require critcl
255
if {![critcl::compiling]} {
256
proc mycommand {...} {
261
critcl::cproc mycommand {...} {
267
The command [cmd critcl::compiling] tells us whether we have a
268
compiler available or not, and in the latter case we implement our
271
[para] Now what happens when we invoke mode "generate package" ?
273
... compiler failure ...
274
... ok - C code - everything fine
275
... fail - no package ? or just no C code ? declare self as tsource, to be used ?
276
... platform-specific C/Tcl -- uuid.