View on GitHub

Cpptcl

C++/Tcl, a library that allows to easily integrate C++ and Tcl.

Download this project as a .zip file Download this project as a tar.gz file
[prev][top][next]

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 FunNames. 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 in test8.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
}