sl@0: /* sl@0: * tclLink.c -- sl@0: * sl@0: * This file implements linked variables (a C variable that is sl@0: * tied to a Tcl variable). The idea of linked variables was sl@0: * first suggested by Andreas Stolcke and this implementation is sl@0: * based heavily on a prototype implementation provided by sl@0: * him. sl@0: * sl@0: * Copyright (c) 1993 The Regents of the University of California. sl@0: * Copyright (c) 1994-1997 Sun Microsystems, Inc. sl@0: * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclLink.c,v 1.8.2.2 2007/05/10 18:23:58 dgp Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: sl@0: /* sl@0: * For each linked variable there is a data structure of the following sl@0: * type, which describes the link and is the clientData for the trace sl@0: * set on the Tcl variable. sl@0: */ sl@0: sl@0: typedef struct Link { sl@0: Tcl_Interp *interp; /* Interpreter containing Tcl variable. */ sl@0: Tcl_Obj *varName; /* Name of variable (must be global). This sl@0: * is needed during trace callbacks, since sl@0: * the actual variable may be aliased at sl@0: * that time via upvar. */ sl@0: char *addr; /* Location of C variable. */ sl@0: int type; /* Type of link (TCL_LINK_INT, etc.). */ sl@0: union { sl@0: int i; sl@0: double d; sl@0: Tcl_WideInt w; sl@0: } lastValue; /* Last known value of C variable; used to sl@0: * avoid string conversions. */ sl@0: int flags; /* Miscellaneous one-bit values; see below sl@0: * for definitions. */ sl@0: } Link; sl@0: sl@0: /* sl@0: * Definitions for flag bits: sl@0: * LINK_READ_ONLY - 1 means errors should be generated if Tcl sl@0: * script attempts to write variable. sl@0: * LINK_BEING_UPDATED - 1 means that a call to Tcl_UpdateLinkedVar sl@0: * is in progress for this variable, so sl@0: * trace callbacks on the variable should sl@0: * be ignored. sl@0: */ sl@0: sl@0: #define LINK_READ_ONLY 1 sl@0: #define LINK_BEING_UPDATED 2 sl@0: sl@0: /* sl@0: * Forward references to procedures defined later in this file: sl@0: */ sl@0: sl@0: static char * LinkTraceProc _ANSI_ARGS_((ClientData clientData, sl@0: Tcl_Interp *interp, CONST char *name1, sl@0: CONST char *name2, int flags)); sl@0: static Tcl_Obj * ObjValue _ANSI_ARGS_((Link *linkPtr)); sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_LinkVar -- sl@0: * sl@0: * Link a C variable to a Tcl variable so that changes to either sl@0: * one causes the other to change. sl@0: * sl@0: * Results: sl@0: * The return value is TCL_OK if everything went well or TCL_ERROR sl@0: * if an error occurred (the interp's result is also set after sl@0: * errors). sl@0: * sl@0: * Side effects: sl@0: * The value at *addr is linked to the Tcl variable "varName", sl@0: * using "type" to convert between string values for Tcl and sl@0: * binary values for *addr. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_LinkVar(interp, varName, addr, type) sl@0: Tcl_Interp *interp; /* Interpreter in which varName exists. */ sl@0: CONST char *varName; /* Name of a global variable in interp. */ sl@0: char *addr; /* Address of a C variable to be linked sl@0: * to varName. */ sl@0: int type; /* Type of C variable: TCL_LINK_INT, etc. sl@0: * Also may have TCL_LINK_READ_ONLY sl@0: * OR'ed in. */ sl@0: { sl@0: Tcl_Obj *objPtr, *resPtr; sl@0: Link *linkPtr; sl@0: int code; sl@0: sl@0: linkPtr = (Link *) ckalloc(sizeof(Link)); sl@0: linkPtr->interp = interp; sl@0: linkPtr->varName = Tcl_NewStringObj(varName, -1); sl@0: Tcl_IncrRefCount(linkPtr->varName); sl@0: linkPtr->addr = addr; sl@0: linkPtr->type = type & ~TCL_LINK_READ_ONLY; sl@0: if (type & TCL_LINK_READ_ONLY) { sl@0: linkPtr->flags = LINK_READ_ONLY; sl@0: } else { sl@0: linkPtr->flags = 0; sl@0: } sl@0: objPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(objPtr); sl@0: resPtr = Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, objPtr, sl@0: TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); sl@0: Tcl_DecrRefCount(objPtr); sl@0: if (resPtr == NULL) { sl@0: Tcl_DecrRefCount(linkPtr->varName); sl@0: ckfree((char *) linkPtr); sl@0: return TCL_ERROR; sl@0: } sl@0: code = Tcl_TraceVar(interp, varName, TCL_GLOBAL_ONLY|TCL_TRACE_READS sl@0: |TCL_TRACE_WRITES|TCL_TRACE_UNSETS, LinkTraceProc, sl@0: (ClientData) linkPtr); sl@0: if (code != TCL_OK) { sl@0: Tcl_DecrRefCount(linkPtr->varName); sl@0: ckfree((char *) linkPtr); sl@0: } sl@0: return code; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_UnlinkVar -- sl@0: * sl@0: * Destroy the link between a Tcl variable and a C variable. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If "varName" was previously linked to a C variable, the link sl@0: * is broken to make the variable independent. If there was no sl@0: * previous link for "varName" then nothing happens. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_UnlinkVar(interp, varName) sl@0: Tcl_Interp *interp; /* Interpreter containing variable to unlink. */ sl@0: CONST char *varName; /* Global variable in interp to unlink. */ sl@0: { sl@0: Link *linkPtr; sl@0: sl@0: linkPtr = (Link *) Tcl_VarTraceInfo(interp, varName, TCL_GLOBAL_ONLY, sl@0: LinkTraceProc, (ClientData) NULL); sl@0: if (linkPtr == NULL) { sl@0: return; sl@0: } sl@0: Tcl_UntraceVar(interp, varName, sl@0: TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, sl@0: LinkTraceProc, (ClientData) linkPtr); sl@0: Tcl_DecrRefCount(linkPtr->varName); sl@0: ckfree((char *) linkPtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_UpdateLinkedVar -- sl@0: * sl@0: * This procedure is invoked after a linked variable has been sl@0: * changed by C code. It updates the Tcl variable so that sl@0: * traces on the variable will trigger. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The Tcl variable "varName" is updated from its C value, sl@0: * causing traces on the variable to trigger. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_UpdateLinkedVar(interp, varName) sl@0: Tcl_Interp *interp; /* Interpreter containing variable. */ sl@0: CONST char *varName; /* Name of global variable that is linked. */ sl@0: { sl@0: Link *linkPtr; sl@0: int savedFlag; sl@0: Tcl_Obj *objPtr; sl@0: sl@0: linkPtr = (Link *) Tcl_VarTraceInfo(interp, varName, TCL_GLOBAL_ONLY, sl@0: LinkTraceProc, (ClientData) NULL); sl@0: if (linkPtr == NULL) { sl@0: return; sl@0: } sl@0: savedFlag = linkPtr->flags & LINK_BEING_UPDATED; sl@0: linkPtr->flags |= LINK_BEING_UPDATED; sl@0: objPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(objPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, objPtr, TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(objPtr); sl@0: linkPtr->flags = (linkPtr->flags & ~LINK_BEING_UPDATED) | savedFlag; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * LinkTraceProc -- sl@0: * sl@0: * This procedure is invoked when a linked Tcl variable is read, sl@0: * written, or unset from Tcl. It's responsible for keeping the sl@0: * C variable in sync with the Tcl variable. sl@0: * sl@0: * Results: sl@0: * If all goes well, NULL is returned; otherwise an error message sl@0: * is returned. sl@0: * sl@0: * Side effects: sl@0: * The C variable may be updated to make it consistent with the sl@0: * Tcl variable, or the Tcl variable may be overwritten to reject sl@0: * a modification. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static char * sl@0: LinkTraceProc(clientData, interp, name1, name2, flags) sl@0: ClientData clientData; /* Contains information about the link. */ sl@0: Tcl_Interp *interp; /* Interpreter containing Tcl variable. */ sl@0: CONST char *name1; /* First part of variable name. */ sl@0: CONST char *name2; /* Second part of variable name. */ sl@0: int flags; /* Miscellaneous additional information. */ sl@0: { sl@0: Link *linkPtr = (Link *) clientData; sl@0: int changed, valueLength; sl@0: CONST char *value; sl@0: char **pp, *result; sl@0: Tcl_Obj *objPtr, *valueObj, *tmpPtr; sl@0: sl@0: /* sl@0: * If the variable is being unset, then just re-create it (with a sl@0: * trace) unless the whole interpreter is going away. sl@0: */ sl@0: sl@0: if (flags & TCL_TRACE_UNSETS) { sl@0: if (Tcl_InterpDeleted(interp)) { sl@0: Tcl_DecrRefCount(linkPtr->varName); sl@0: ckfree((char *) linkPtr); sl@0: } else if (flags & TCL_TRACE_DESTROYED) { sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: Tcl_TraceVar(interp, Tcl_GetString(linkPtr->varName), sl@0: TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES sl@0: |TCL_TRACE_UNSETS, LinkTraceProc, (ClientData) linkPtr); sl@0: } sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * If we were invoked because of a call to Tcl_UpdateLinkedVar, then sl@0: * don't do anything at all. In particular, we don't want to get sl@0: * upset that the variable is being modified, even if it is sl@0: * supposed to be read-only. sl@0: */ sl@0: sl@0: if (linkPtr->flags & LINK_BEING_UPDATED) { sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * For read accesses, update the Tcl variable if the C variable sl@0: * has changed since the last time we updated the Tcl variable. sl@0: */ sl@0: sl@0: if (flags & TCL_TRACE_READS) { sl@0: switch (linkPtr->type) { sl@0: case TCL_LINK_INT: sl@0: case TCL_LINK_BOOLEAN: sl@0: changed = *(int *)(linkPtr->addr) != linkPtr->lastValue.i; sl@0: break; sl@0: case TCL_LINK_DOUBLE: sl@0: changed = *(double *)(linkPtr->addr) != linkPtr->lastValue.d; sl@0: break; sl@0: case TCL_LINK_WIDE_INT: sl@0: changed = *(Tcl_WideInt *)(linkPtr->addr) != linkPtr->lastValue.w; sl@0: break; sl@0: case TCL_LINK_STRING: sl@0: changed = 1; sl@0: break; sl@0: default: sl@0: return "internal error: bad linked variable type"; sl@0: } sl@0: if (changed) { sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: } sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * For writes, first make sure that the variable is writable. Then sl@0: * convert the Tcl value to C if possible. If the variable isn't sl@0: * writable or can't be converted, then restore the varaible's old sl@0: * value and return an error. Another tricky thing: we have to save sl@0: * and restore the interpreter's result, since the variable access sl@0: * could occur when the result has been partially set. sl@0: */ sl@0: sl@0: if (linkPtr->flags & LINK_READ_ONLY) { sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: return "linked variable is read-only"; sl@0: } sl@0: valueObj = Tcl_ObjGetVar2(interp, linkPtr->varName,NULL, TCL_GLOBAL_ONLY); sl@0: if (valueObj == NULL) { sl@0: /* sl@0: * This shouldn't ever happen. sl@0: */ sl@0: return "internal error: linked variable couldn't be read"; sl@0: } sl@0: sl@0: objPtr = Tcl_GetObjResult(interp); sl@0: Tcl_IncrRefCount(objPtr); sl@0: Tcl_ResetResult(interp); sl@0: result = NULL; sl@0: sl@0: switch (linkPtr->type) { sl@0: case TCL_LINK_INT: sl@0: if (Tcl_GetIntFromObj(interp, valueObj, &linkPtr->lastValue.i) sl@0: != TCL_OK) { sl@0: Tcl_SetObjResult(interp, objPtr); sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: result = "variable must have integer value"; sl@0: goto end; sl@0: } sl@0: *(int *)(linkPtr->addr) = linkPtr->lastValue.i; sl@0: break; sl@0: sl@0: case TCL_LINK_WIDE_INT: sl@0: if (Tcl_GetWideIntFromObj(interp, valueObj, &linkPtr->lastValue.w) sl@0: != TCL_OK) { sl@0: Tcl_SetObjResult(interp, objPtr); sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: result = "variable must have integer value"; sl@0: goto end; sl@0: } sl@0: *(Tcl_WideInt *)(linkPtr->addr) = linkPtr->lastValue.w; sl@0: break; sl@0: sl@0: case TCL_LINK_DOUBLE: sl@0: if (Tcl_GetDoubleFromObj(interp, valueObj, &linkPtr->lastValue.d) sl@0: != TCL_OK) { sl@0: Tcl_SetObjResult(interp, objPtr); sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: result = "variable must have real value"; sl@0: goto end; sl@0: } sl@0: *(double *)(linkPtr->addr) = linkPtr->lastValue.d; sl@0: break; sl@0: sl@0: case TCL_LINK_BOOLEAN: sl@0: if (Tcl_GetBooleanFromObj(interp, valueObj, &linkPtr->lastValue.i) sl@0: != TCL_OK) { sl@0: Tcl_SetObjResult(interp, objPtr); sl@0: tmpPtr = ObjValue(linkPtr); sl@0: Tcl_IncrRefCount(tmpPtr); sl@0: Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, tmpPtr, sl@0: TCL_GLOBAL_ONLY); sl@0: Tcl_DecrRefCount(tmpPtr); sl@0: result = "variable must have boolean value"; sl@0: goto end; sl@0: } sl@0: *(int *)(linkPtr->addr) = linkPtr->lastValue.i; sl@0: break; sl@0: sl@0: case TCL_LINK_STRING: sl@0: value = Tcl_GetStringFromObj(valueObj, &valueLength); sl@0: valueLength++; sl@0: pp = (char **)(linkPtr->addr); sl@0: if (*pp != NULL) { sl@0: ckfree(*pp); sl@0: } sl@0: *pp = (char *) ckalloc((unsigned) valueLength); sl@0: memcpy(*pp, value, (unsigned) valueLength); sl@0: break; sl@0: sl@0: default: sl@0: return "internal error: bad linked variable type"; sl@0: } sl@0: end: sl@0: Tcl_DecrRefCount(objPtr); sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ObjValue -- sl@0: * sl@0: * Converts the value of a C variable to a Tcl_Obj* for use in a sl@0: * Tcl variable to which it is linked. sl@0: * sl@0: * Results: sl@0: * The return value is a pointer to a Tcl_Obj that represents sl@0: * the value of the C variable given by linkPtr. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static Tcl_Obj * sl@0: ObjValue(linkPtr) sl@0: Link *linkPtr; /* Structure describing linked variable. */ sl@0: { sl@0: char *p; sl@0: sl@0: switch (linkPtr->type) { sl@0: case TCL_LINK_INT: sl@0: linkPtr->lastValue.i = *(int *)(linkPtr->addr); sl@0: return Tcl_NewIntObj(linkPtr->lastValue.i); sl@0: case TCL_LINK_WIDE_INT: sl@0: linkPtr->lastValue.w = *(Tcl_WideInt *)(linkPtr->addr); sl@0: return Tcl_NewWideIntObj(linkPtr->lastValue.w); sl@0: case TCL_LINK_DOUBLE: sl@0: linkPtr->lastValue.d = *(double *)(linkPtr->addr); sl@0: return Tcl_NewDoubleObj(linkPtr->lastValue.d); sl@0: case TCL_LINK_BOOLEAN: sl@0: linkPtr->lastValue.i = *(int *)(linkPtr->addr); sl@0: return Tcl_NewBooleanObj(linkPtr->lastValue.i != 0); sl@0: case TCL_LINK_STRING: sl@0: p = *(char **)(linkPtr->addr); sl@0: if (p == NULL) { sl@0: return Tcl_NewStringObj("NULL", 4); sl@0: } sl@0: return Tcl_NewStringObj(p, -1); sl@0: sl@0: /* sl@0: * This code only gets executed if the link type is unknown sl@0: * (shouldn't ever happen). sl@0: */ sl@0: default: sl@0: return Tcl_NewStringObj("??", 2); sl@0: } sl@0: }