Variable Traces
Define and remove trace functions to Tcl variables.Define a trace function
Tcl allows users to define trace functions to monitor or even control the access of a Tcl variable.Starting from version 1.1.4.001, C++/Tcl provides easy interfaces to define read and write traces for Tcl variables. If a tcl variable is associated with a read trace, the trace function is called before the variable is read; therefor, the trace function can modify the variable value which results in a different value been read. On the other hand, for a write trace, it is called after the associated variable is written; therefore, the trace function can modify the actual value written to the variable. It is possible to use a write trace to implement a read-only variable.
The trace function allowed in the C++/Tcl library must have the following format:
template<typename VT, typename CDT>
VT trace_function (VT const & var, CDT * cData);
where
VT
is the value type of the Tcl variable, and CDT
defines the type of user data structure passed to the trace function. As long as a data type
T
is supported by get<T>
(see Objects and Lists, 4), it can be used as VT
for the trace function.The trace function reads in the current value of the variable
var
and returns a new value. If the returned value is different with var
, the returned value will be written to the traced Tcl variable.Currently, only a pointer pointing to a user data can be passed to the trace function (an absolute data will be lost).
Several member methods are provided in
Tcl::interpreter
to define trace functions:// define read trace
template<typename VT, typename CDT>
void interpreter::def_read_trace(const string& VarName,
const string& FunName,
VT (*proc)(VT const &, CDT *), CDT *cData = NULL);
template<typename VT, typename CDT>
void interpreter::def_read_trace(const string& VarName, unsigned int index,
const string& FunName,
VT (*proc)(VT const &, CDT *), CDT *cData = NULL);
// define write trace
template<typename VT, typename CDT>
void interpreter::def_write_trace(const string& VarName,
const string& FunName,
VT (*proc)(VT const &, CDT *), CDT *cData = NULL);
template<typename VT, typename CDT>
void interpreter::def_write_trace(const string& VarName, unsigned int index,
const string& FunName,
VT (*proc)(VT const &, CDT *), CDT *cData = NULL);
where
VarName
is the name of the Tcl variable to be traced; index
identifies the the index if only one element in an variable array is traced; FunName
is an unique string to identify a trace function, otherwise the trace function is lost; proc
is the real function pointer pointing to the trace function; and cData
is the user data to be passed to the trace function.Tcl allows to trace a variable before it is define. This is actually saying a trace can be pre-set to an unknown variable and it will be called when the variable is actually set in Tcl.
FunName
is used as an id to identify a trace function. It is not necessary to pair a FunName
with a trace function proc
. Therefore, it is legal to define a trace function proc
more than one time to the same variable using different FunName
s. Tracing different variables using the same trace function is always allowed. When no user data is passed, argument cData
can be omitted as it is NULL
by default.Remove a defined trace
It is possible to remove a trace on the run. Several member methods are provide for this purpose:// remove a read trace
void interpreter::undef_read_trace(const string& VarName,
const string& FunName = "");
void interpreter::undef_read_trace(const string& VarName, unsigned int index,
const string& FunName = "");
// remove a write trace
void interpreter::undef_write_trace(const string& VarName,
const string& FunName = "");
void interpreter::undef_write_trace(const string& VarName, unsigned int index,
const string& FunName = "");
// delete all traces
void interpreter::undef_all_trace(const string& VarName);
void interpreter::undef_all_trace(const string& VarName, unsigned int index);
When
FunName
is not provided or assigned empty, all trace functions associated with the target variable are removed; otherwise, only the trace function identified by FunName
is removed. undef_all_trace()
is used to remove all traces associated with a variable.Example
The following simple example demonstrates how to use this feature. A more complicated example can be found intest8.cc
.// example of variable traces
#include "cpptcl.h"
#include <iostream>
using std::cout;
using std::endl;
using std::string;
using namespace Tcl;
int read_trace(const int& v, void *) {
cout << "read trace triggered." << endl;
return v;
}
int write_trace(const int& v, int * cd) {
cout << "write trace triggered." << endl;
// set the user data to the current variable value
*cd = v;
// modify the variable value
return v+1;
}
int main() {
interpreter i;
int env = 5; // user data
i.def_read_trace("tcl_var", "read", read_trace);
i.def_write_trace("tcl_var", "write", write_trace, &env);
// test write trace
string rv = i.eval("set tcl_var 20");
// stdout: write trace triggered.
assert(rv == "21"); // the actual value is 20 + 1
// test read trace
i.eval("set tcl_var");
// stdout: read trace triggered.
i.undef_read_trace("tcl_var", "read");
i.eval("set tcl_var");
// stdout: [nothing]
// since read trace is removed
i.undef_all_trace("tcl_var");
string rv1 = i.eval("set tcl_var 20");
// stdout: [nothing]
// since write trace is removed
assert(rv1 == "20"); // back to normal behaviour
}