--- /dev/null
+
+/*
+ * bltWinDde.c --
+ *
+ * This file provides procedures that implement the "send"
+ * command, allowing commands to be passed from interpreter
+ * to interpreter.
+ *
+ * Copyright (c) 1997 by Sun Microsystems, Inc.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id: bltWinDde.c,v 1.1.1.1 2009/05/09 16:27:07 pcmacdon Exp $
+ */
+
+#include "bltInt.h"
+
+#ifndef NO_DDE
+
+#include <ddeml.h>
+
+/*
+ * The following structure is used to keep track of the interpreters
+ * registered by this process.
+ */
+
+typedef struct RegisteredInterp {
+ struct RegisteredInterp *nextPtr;
+ /* The next interp this application knows
+ * about. */
+ Tcl_Interp *interp; /* The interpreter attached to this name. */
+ char name[1]; /* Interpreter's name. Malloc-ed as
+ * part of the structure. */
+} RegisteredInterp;
+
+/*
+ * Used to keep track of conversations.
+ */
+
+typedef struct Conversation {
+ struct Conversation *nextPtr;
+ /* The next conversation in the list. */
+ RegisteredInterp *riPtr; /* The info we know about the conversation. */
+ HCONV hConv; /* The DDE handle for this conversation. */
+ Tcl_Obj *returnPackagePtr; /* The result package for this conversation. */
+
+} Conversation;
+
+static Conversation *conversations; /* A list of conversations currently
+ * being processed. */
+static RegisteredInterp *interps; /* List of all interpreters registered
+ * in the current process. */
+static HSZ globalService;
+static DWORD instance; /* The application instance handle given
+ * to us by DdeInitialize. */
+static int isServer;
+
+#define TCL_DDE_VERSION "1.2"
+#define TCL_DDE_PACKAGE_NAME "dde"
+#define TCL_DDE_SERVICE_NAME "TclEval"
+
+/*
+ * Forward declarations for procedures defined later in this file.
+ */
+
+static Tcl_Obj *ExecuteRemoteObject _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Obj *objPtr));
+static int MakeConnection _ANSI_ARGS_((Tcl_Interp *interp, char *name,
+ HCONV *convPtr));
+static HDDEDATA CALLBACK ServerProc _ANSI_ARGS_((UINT uType, UINT uFmt,
+ HCONV hConv, HSZ topic, HSZ item, HDDEDATA hData, DWORD dwData1,
+ DWORD dwData2));
+
+static Tcl_ExitProc ExitProc;
+static Tcl_CmdDeleteProc DeleteProc;
+static void SetError _ANSI_ARGS_((Tcl_Interp *interp));
+
+static Tcl_ObjCmdProc DdeObjCmd;
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Initialize --
+ *
+ * Initialize the global DDE instance.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Registers the DDE server proc.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+Initialize(void)
+{
+ int nameFound = 0;
+
+ /*
+ * See if the application is already registered; if so, remove its
+ * current name from the registry. The deletion of the command
+ * will take care of disposing of this entry.
+ */
+
+ if (interps != NULL) {
+ nameFound = 1;
+ }
+
+ /*
+ * Make sure that the DDE server is there. This is done only once,
+ * add an exit handler tear it down.
+ */
+
+ if (instance == 0) {
+ if (instance == 0) {
+ unsigned int flags;
+
+ flags = (CBF_SKIP_REGISTRATIONS | CBF_SKIP_UNREGISTRATIONS |
+ CBF_FAIL_POKES);
+ if (DdeInitialize(&instance, ServerProc, flags, 0)
+ != DMLERR_NO_ERROR) {
+ instance = 0;
+ }
+ }
+ }
+ if ((globalService == 0) && (nameFound != 0)) {
+ if ((globalService == 0) && (nameFound != 0)) {
+ isServer = TRUE;
+ Tcl_CreateExitHandler(ExitProc, NULL);
+ globalService = DdeCreateStringHandle(instance,
+ TCL_DDE_SERVICE_NAME, 0);
+ DdeNameService(instance, globalService, 0L, DNS_REGISTER);
+ } else {
+ isServer = FALSE;
+ }
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * SetServerName --
+ *
+ * This procedure is called to associate an ASCII name with a Dde
+ * server. If the interpreter has already been named, the
+ * name replaces the old one.
+ *
+ * Results:
+ * The return value is the name actually given to the interp.
+ * This will normally be the same as name, but if name was already
+ * in use for a Dde Server then a name of the form "name #2" will
+ * be chosen, with a high enough number to make the name unique.
+ *
+ * Side effects:
+ * Registration info is saved, thereby allowing the "send" command
+ * to be used later to invoke commands in the application. In
+ * addition, the "send" command is created in the application's
+ * interpreter. The registration will be removed automatically
+ * if the interpreter is deleted or the "send" command is removed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static char *
+SetServerName(
+ Tcl_Interp *interp,
+ char *name /* The name that will be used to
+ * refer to the interpreter in later
+ * "send" commands. Must be globally
+ * unique. */
+ )
+{
+ int suffix, offset;
+ RegisteredInterp *riPtr, *prevPtr;
+ Tcl_DString dString;
+
+ /*
+ * See if the application is already registered; if so, remove its
+ * current name from the registry. The deletion of the command
+ * will take care of disposing of this entry.
+ */
+
+ for (riPtr = interps, prevPtr = NULL; riPtr != NULL;
+ prevPtr = riPtr, riPtr = riPtr->nextPtr) {
+ if (riPtr->interp == interp) {
+ if (name != NULL) {
+ if (prevPtr == NULL) {
+ interps = interps->nextPtr;
+ } else {
+ prevPtr->nextPtr = riPtr->nextPtr;
+ }
+ break;
+ } else {
+ /*
+ * the name was NULL, so the caller is asking for
+ * the name of the current interp.
+ */
+
+ return riPtr->name;
+ }
+ }
+ }
+
+ if (name == NULL) {
+ /*
+ * the name was NULL, so the caller is asking for
+ * the name of the current interp, but it doesn't
+ * have a name.
+ */
+
+ return "";
+ }
+
+ /*
+ * Pick a name to use for the application. Use "name" if it's not
+ * already in use. Otherwise add a suffix such as " #2", trying
+ * larger and larger numbers until we eventually find one that is
+ * unique.
+ */
+
+ suffix = 1;
+ offset = 0;
+ Tcl_DStringInit(&dString);
+
+ /*
+ * We have found a unique name. Now add it to the registry.
+ */
+
+ riPtr = Blt_Malloc(sizeof(RegisteredInterp) + strlen(name));
+ riPtr->interp = interp;
+ riPtr->nextPtr = interps;
+ interps = riPtr;
+ strcpy(riPtr->name, name);
+
+ Tcl_CreateObjCommand(interp, "dde", DdeObjCmd, riPtr, DeleteProc);
+ if (Tcl_IsSafe(interp)) {
+ Tcl_HideCommand(interp, "dde", "dde");
+ }
+ Tcl_DStringFree(&dString);
+
+ /*
+ * re-initialize with the new name
+ */
+ Initialize();
+
+ return riPtr->name;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * DeleteProc
+ *
+ * This procedure is called when the command "dde" is destroyed.
+ *
+ * Results:
+ * none
+ *
+ * Side effects:
+ * The interpreter given by riPtr is unregistered.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+DeleteProc(clientData)
+ ClientData clientData; /* The interp we are deleting passed
+ * as ClientData. */
+{
+ RegisteredInterp *riPtr = clientData;
+ RegisteredInterp *searchPtr, *prevPtr;
+
+ for (searchPtr = interps, prevPtr = NULL;
+ (searchPtr != NULL) && (searchPtr != riPtr);
+ prevPtr = searchPtr, searchPtr = searchPtr->nextPtr) {
+ /*
+ * Empty loop body.
+ */
+ }
+
+ if (searchPtr != NULL) {
+ if (prevPtr == NULL) {
+ interps = interps->nextPtr;
+ } else {
+ prevPtr->nextPtr = searchPtr->nextPtr;
+ }
+ }
+ Tcl_EventuallyFree(clientData, TCL_DYNAMIC);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ExecuteRemoteObject --
+ *
+ * Takes the package delivered by DDE and executes it in
+ * the server's interpreter.
+ *
+ * Results:
+ * A list Tcl_Obj * that describes what happened. The first
+ * element is the numerical return code (TCL_ERROR, etc.).
+ * The second element is the result of the script. If the
+ * return result was TCL_ERROR, then the third element
+ * will be the value of the global "errorCode", and the
+ * fourth will be the value of the global "errorInfo".
+ * The return result will have a refCount of 0.
+ *
+ * Side effects:
+ * A Tcl script is run, which can cause all kinds of other
+ * things to happen.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+ExecuteRemoteObject(
+ Tcl_Interp *interp, /* Remote interpreter. */
+ Tcl_Obj *objPtr) /* The object to execute. */
+{
+ Tcl_Obj *listObjPtr;
+ int result;
+
+ result = Tcl_GlobalEval(interp, Tcl_GetString(objPtr));
+ listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ Tcl_ListObjAppendElement(NULL, listObjPtr, Tcl_NewIntObj(result));
+ Tcl_ListObjAppendElement(NULL, listObjPtr, Tcl_GetObjResult(interp));
+ if (result == TCL_ERROR) {
+ char *value;
+ Tcl_Obj *objPtr;
+
+ value = Tcl_GetVar2(interp, "errorCode", NULL, TCL_GLOBAL_ONLY);
+ objPtr = Tcl_NewStringObj(value, -1);
+ Tcl_ListObjAppendElement(NULL, listObjPtr, objPtr);
+ value = Tcl_GetVar2(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
+ objPtr = Tcl_NewStringObj(value, -1);
+ Tcl_ListObjAppendElement(NULL, listObjPtr, objPtr);
+ }
+ return listObjPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ServerProc --
+ *
+ * Handles all transactions for this server. Can handle
+ * execute, request, and connect protocols. Dde will
+ * call this routine when a client attempts to run a dde
+ * command using this server.
+ *
+ * Results:
+ * A DDE Handle with the result of the dde command.
+ *
+ * Side effects:
+ * Depending on which command is executed, arbitrary
+ * Tcl scripts can be run.
+ *
+ *--------------------------------------------------------------
+ */
+
+static HDDEDATA CALLBACK
+ServerProc (
+ UINT uType, /* The type of DDE transaction we
+ * are performing. */
+ UINT uFmt, /* The format that data is sent or
+ * received. */
+ HCONV hConv, /* The conversation associated with the
+ * current transaction. */
+ HSZ topic, /* A string handle. Transaction-type
+ * dependent. */
+ HSZ item, /* A string handle. Transaction-type
+ * dependent. */
+ HDDEDATA hData, /* DDE data. Transaction-type dependent. */
+ DWORD dwData1, /* Transaction-dependent data. */
+ DWORD dwData2) /* Transaction-dependent data. */
+{
+ Tcl_DString dString;
+ char *utilString;
+ Tcl_Obj *objPtr;
+ HDDEDATA code = NULL;
+ RegisteredInterp *riPtr;
+ Conversation *convPtr, *prevConvPtr;
+
+ switch(uType) {
+ case XTYP_CONNECT:
+ {
+ int length;
+
+ /*
+ * Dde is trying to initialize a conversation with us. Check
+ * and make sure we have a valid topic.
+ */
+
+ length = DdeQueryString(instance, topic, NULL, 0, 0);
+ Tcl_DStringInit(&dString);
+ Tcl_DStringSetLength(&dString, length);
+ utilString = Tcl_DStringValue(&dString);
+ DdeQueryString(instance, topic, utilString, length + 1,
+ CP_WINANSI);
+
+ for (riPtr = interps; riPtr != NULL; riPtr = riPtr->nextPtr) {
+ if (strcasecmp(utilString, riPtr->name) == 0) {
+ Tcl_DStringFree(&dString);
+ return (HDDEDATA) TRUE;
+ }
+ }
+
+ Tcl_DStringFree(&dString);
+ return (HDDEDATA) FALSE;
+ }
+ case XTYP_CONNECT_CONFIRM:
+ {
+ DWORD length;
+
+ /*
+ * Dde has decided that we can connect, so it gives us a
+ * conversation handle. We need to keep track of it
+ * so we know which execution result to return in an
+ * XTYP_REQUEST.
+ */
+
+ length = DdeQueryString(instance, topic, NULL, 0, 0);
+ Tcl_DStringInit(&dString);
+ Tcl_DStringSetLength(&dString, length);
+ utilString = Tcl_DStringValue(&dString);
+ DdeQueryString(instance, topic, utilString, length + 1,
+ CP_WINANSI);
+ for (riPtr = interps; riPtr != NULL; riPtr = riPtr->nextPtr) {
+ if (strcasecmp(riPtr->name, utilString) == 0) {
+ convPtr = Blt_Malloc(sizeof(Conversation));
+ convPtr->nextPtr = conversations;
+ convPtr->returnPackagePtr = NULL;
+ convPtr->hConv = hConv;
+ convPtr->riPtr = riPtr;
+ conversations = convPtr;
+ break;
+ }
+ }
+ Tcl_DStringFree(&dString);
+ return (HDDEDATA) TRUE;
+ }
+ case XTYP_DISCONNECT:
+ {
+ /*
+ * The client has disconnected from our server. Forget this
+ * conversation.
+ */
+
+ for (convPtr = conversations, prevConvPtr = NULL;
+ convPtr != NULL;
+ prevConvPtr = convPtr, convPtr = convPtr->nextPtr) {
+ if (hConv == convPtr->hConv) {
+ if (prevConvPtr == NULL) {
+ conversations = convPtr->nextPtr;
+ } else {
+ prevConvPtr->nextPtr = convPtr->nextPtr;
+ }
+ if (convPtr->returnPackagePtr != NULL) {
+ Tcl_DecrRefCount(convPtr->returnPackagePtr);
+ }
+ Blt_Free(convPtr);
+ break;
+ }
+ }
+ return (HDDEDATA) TRUE;
+ }
+ case XTYP_REQUEST:
+ {
+ int length;
+
+ /*
+ * This could be either a request for a value of a Tcl variable,
+ * or it could be the send command requesting the results of the
+ * last execute.
+ */
+
+ if (uFmt != CF_TEXT) {
+ return (HDDEDATA) FALSE;
+ }
+
+ code = (HDDEDATA) FALSE;
+ for (convPtr = conversations; (convPtr != NULL)
+ && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
+ /*
+ * Empty loop body.
+ */
+ }
+
+ if (convPtr != NULL) {
+ length = DdeQueryString(instance, item, NULL, 0, CP_WINANSI);
+ Tcl_DStringInit(&dString);
+ Tcl_DStringSetLength(&dString, length);
+ utilString = Tcl_DStringValue(&dString);
+ DdeQueryString(instance, item, utilString, length + 1,
+ CP_WINANSI);
+ if (strcasecmp(utilString, "$TCLEVAL$EXECUTE$RESULT") == 0) {
+ char *value;
+
+ value = Tcl_GetStringFromObj(convPtr->returnPackagePtr,
+ &length);
+ code = DdeCreateDataHandle(instance, value, length+1, 0,
+ item, CF_TEXT, 0);
+ } else {
+ char *value;
+
+ value = Tcl_GetVar2(convPtr->riPtr->interp, utilString,
+ NULL, TCL_GLOBAL_ONLY);
+ if (value != NULL) {
+ length = strlen(value);
+ code = DdeCreateDataHandle(instance, value, length+1,
+ 0, item, CF_TEXT, 0);
+ } else {
+ code = NULL;
+ }
+ }
+ Tcl_DStringFree(&dString);
+ }
+ return code;
+ }
+ case XTYP_EXECUTE:
+ {
+ DWORD length;
+ /*
+ * Execute this script. The results will be saved into
+ * a list object which will be retreived later. See
+ * ExecuteRemoteObject.
+ */
+
+ Tcl_Obj *returnPackagePtr;
+
+ for (convPtr = conversations; (convPtr != NULL)
+ && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
+ /*
+ * Empty loop body.
+ */
+
+ }
+
+ if (convPtr == NULL) {
+ return (HDDEDATA) DDE_FNOTPROCESSED;
+ }
+
+ utilString = (char *) DdeAccessData(hData, &length);
+ objPtr = Tcl_NewStringObj(utilString, -1);
+ Tcl_IncrRefCount(objPtr);
+ DdeUnaccessData(hData);
+ if (convPtr->returnPackagePtr != NULL) {
+ Tcl_DecrRefCount(convPtr->returnPackagePtr);
+ }
+ convPtr->returnPackagePtr = NULL;
+ returnPackagePtr = ExecuteRemoteObject(convPtr->riPtr->interp,
+ objPtr);
+ for (convPtr = conversations; (convPtr != NULL)
+ && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
+ /*
+ * Empty loop body.
+ */
+
+ }
+ if (convPtr != NULL) {
+ Tcl_IncrRefCount(returnPackagePtr);
+ convPtr->returnPackagePtr = returnPackagePtr;
+ }
+ Tcl_DecrRefCount(objPtr);
+ if (returnPackagePtr == NULL) {
+ return (HDDEDATA) DDE_FNOTPROCESSED;
+ } else {
+ return (HDDEDATA) DDE_FACK;
+ }
+ }
+ case XTYP_WILDCONNECT:
+ {
+ DWORD length;
+
+ /*
+ * Dde wants a list of services and topics that we support.
+ */
+
+ HSZPAIR *returnPtr;
+ int i;
+ int numItems;
+
+ for (i = 0, riPtr = interps; riPtr != NULL;
+ i++, riPtr = riPtr->nextPtr) {
+ /*
+ * Empty loop body.
+ */
+
+ }
+
+ numItems = i;
+ code = DdeCreateDataHandle(instance, NULL,
+ (numItems + 1) * sizeof(HSZPAIR), 0, 0, 0, 0);
+ returnPtr = (HSZPAIR *) DdeAccessData(code, &length);
+ for (i = 0, riPtr = interps; i < numItems;
+ i++, riPtr = riPtr->nextPtr) {
+ returnPtr[i].hszSvc = DdeCreateStringHandle(
+ instance, "TclEval", CP_WINANSI);
+ returnPtr[i].hszTopic = DdeCreateStringHandle(
+ instance, riPtr->name, CP_WINANSI);
+ }
+ returnPtr[i].hszSvc = NULL;
+ returnPtr[i].hszTopic = NULL;
+ DdeUnaccessData(code);
+ return code;
+ }
+ }
+ return NULL;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ExitProc --
+ *
+ * Gets rid of our DDE server when we go away.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The DDE server is deleted.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+ExitProc(
+ ClientData clientData) /* Not used in this handler. */
+{
+ DdeNameService(instance, NULL, 0, DNS_UNREGISTER);
+ DdeUninitialize(instance);
+ instance = 0;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MakeConnection --
+ *
+ * This procedure is a utility used to connect to a DDE
+ * server when given a server name and a topic name.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ *
+ * Side effects:
+ * Passes back a conversation through ddeConvPtr
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+MakeConnection(
+ Tcl_Interp *interp, /* Used to report errors. */
+ char *name, /* The connection to use. */
+ HCONV *convPtr)
+{
+ HSZ topic, service;
+ HCONV conv;
+
+ service = DdeCreateStringHandle(instance, "TclEval", 0);
+ topic = DdeCreateStringHandle(instance, name, 0);
+
+ conv = DdeConnect(instance, service, topic, NULL);
+ DdeFreeStringHandle(instance, service);
+ DdeFreeStringHandle(instance, topic);
+
+ if (conv == NULL) {
+ if (interp != NULL) {
+ Tcl_AppendResult(interp, "no registered server named \"", name,
+ "\"", (char *) NULL);
+ }
+ return TCL_ERROR;
+ }
+
+ *convPtr = conv;
+ return TCL_OK;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * SetError --
+ *
+ * Sets the interp result to a cogent error message
+ * describing the last DDE error.
+ *
+ * Results:
+ * None.
+ *
+ *
+ * Side effects:
+ * The interp's result object is changed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+SetError(
+ Tcl_Interp *interp) /* The interp to put the message in.*/
+{
+ int err;
+ char *mesg;
+
+ err = DdeGetLastError(instance);
+ switch (err) {
+ case DMLERR_DATAACKTIMEOUT:
+ case DMLERR_EXECACKTIMEOUT:
+ case DMLERR_POKEACKTIMEOUT:
+ mesg = "remote interpreter did not respond";
+ break;
+
+ case DMLERR_BUSY:
+ mesg = "remote server is busy";
+ break;
+
+ case DMLERR_NOTPROCESSED:
+ mesg = "remote server cannot handle this command";
+ break;
+
+ default:
+ mesg = "dde command failed";
+ break;
+ }
+ Tcl_SetResult(interp, mesg, TCL_VOLATILE);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * DdeObjCmd --
+ *
+ * This procedure is invoked to process the "dde" Tcl command.
+ * See the user documentation for details on what it does.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+DdeObjCmd(
+ ClientData clientData, /* Used only for deletion */
+ Tcl_Interp *interp, /* The interp we are sending from */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[]) /* The arguments */
+{
+ enum {
+ DDE_SERVERNAME,
+ DDE_EXECUTE,
+ DDE_POKE,
+ DDE_REQUEST,
+ DDE_SERVICES,
+ DDE_EVAL
+ };
+
+ static char *commands[] = {
+ "servername", "execute", "poke", "request", "services", "eval",
+ (char *) NULL
+ };
+ static char *options[] = {
+ "-async", (char *) NULL
+ };
+ int index, argIndex;
+ int async = 0, binary = 0;
+ int result = TCL_OK;
+ HSZ service = NULL;
+ HSZ topic = NULL;
+ HSZ item = NULL;
+ HDDEDATA data = NULL;
+ HDDEDATA itemData = NULL;
+ HCONV hConv = NULL;
+ HSZ cookie = 0;
+ char *serviceName, *topicName, *itemString, *dataString;
+ char *string;
+ int firstArg, length, dataLength;
+ HDDEDATA code;
+ RegisteredInterp *riPtr;
+ Tcl_Interp *sendInterp;
+ Tcl_Obj *objPtr;
+
+ /*
+ * Initialize DDE server/client
+ */
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "?-async? serviceName topicName value");
+ return TCL_ERROR;
+ }
+
+ if (Tcl_GetIndexFromObj(interp, objv[1], commands, "command", 0,
+ &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ serviceName = NULL; /* Suppress compiler warning. */
+ firstArg = 1;
+ switch (index) {
+ case DDE_SERVERNAME:
+ if ((objc != 3) && (objc != 2)) {
+ Tcl_WrongNumArgs(interp, 1, objv, "servername ?serverName?");
+ return TCL_ERROR;
+ }
+ firstArg = (objc - 1);
+ break;
+
+ case DDE_EXECUTE:
+ if ((objc < 5) || (objc > 6)) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "execute ?-async? serviceName topicName value");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(NULL, objv[2], options, "option", 0,
+ &argIndex) != TCL_OK) {
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "execute ?-async? serviceName topicName value");
+ return TCL_ERROR;
+ }
+ async = 0;
+ firstArg = 2;
+ } else {
+ if (objc != 6) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "execute ?-async? serviceName topicName value");
+ return TCL_ERROR;
+ }
+ async = 1;
+ firstArg = 3;
+ }
+ break;
+ case DDE_POKE:
+ if (objc != 6) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "poke serviceName topicName item value");
+ return TCL_ERROR;
+ }
+ firstArg = 2;
+ break;
+
+ case DDE_REQUEST:
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "request serviceName topicName value");
+ return TCL_ERROR;
+ }
+ binary = 0;
+ firstArg = 2;
+ break;
+
+ case DDE_SERVICES:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "services serviceName topicName");
+ return TCL_ERROR;
+ }
+ firstArg = 2;
+ break;
+
+ case DDE_EVAL:
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "eval ?-async? serviceName args");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(NULL, objv[2], options, "option", 0,
+ &argIndex) != TCL_OK) {
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "eval ?-async? serviceName args");
+ return TCL_ERROR;
+ }
+ async = 0;
+ firstArg = 2;
+ } else {
+ if (objc < 5) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "eval ?-async? serviceName args");
+ return TCL_ERROR;
+ }
+ async = 1;
+ firstArg = 3;
+ }
+ break;
+ }
+
+ Initialize();
+
+ if (firstArg != 1) {
+ serviceName = Tcl_GetStringFromObj(objv[firstArg], &length);
+ } else {
+ length = 0;
+ }
+
+ if (length == 0) {
+ serviceName = NULL;
+ } else if ((index != DDE_SERVERNAME) && (index != DDE_EVAL)) {
+ service = DdeCreateStringHandle(instance, serviceName,
+ CP_WINANSI);
+ }
+
+ if ((index != DDE_SERVERNAME) &&(index != DDE_EVAL)) {
+ topicName = Tcl_GetStringFromObj(objv[firstArg + 1], &length);
+ if (length == 0) {
+ topicName = NULL;
+ } else {
+ topic = DdeCreateStringHandle(instance, topicName, CP_WINANSI);
+ }
+ }
+
+ switch (index) {
+ case DDE_SERVERNAME:
+ serviceName = SetServerName(interp, serviceName);
+ if (serviceName != NULL) {
+ Tcl_SetStringObj(Tcl_GetObjResult(interp),
+ serviceName, -1);
+ } else {
+ Tcl_ResetResult(interp);
+ }
+ break;
+
+ case DDE_EXECUTE:
+ {
+ dataString = Tcl_GetStringFromObj(objv[firstArg + 2], &dataLength);
+ if (dataLength == 0) {
+ Tcl_SetStringObj(Tcl_GetObjResult(interp),
+ "cannot execute null data", -1);
+ result = TCL_ERROR;
+ break;
+ }
+ hConv = DdeConnect(instance, service, topic, NULL);
+ DdeFreeStringHandle(instance, service);
+ DdeFreeStringHandle(instance, topic);
+
+ if (hConv == NULL) {
+ SetError(interp);
+ result = TCL_ERROR;
+ break;
+ }
+
+ data = DdeCreateDataHandle(instance, dataString, dataLength + 1,
+ 0, 0, CF_TEXT, 0);
+ if (data != NULL) {
+ if (async) {
+ DWORD status;
+
+ DdeClientTransaction((LPBYTE) data, 0xFFFFFFFF, hConv, 0,
+ CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, &status);
+ DdeAbandonTransaction(instance, hConv, status);
+ } else {
+ code = DdeClientTransaction((LPBYTE) data, 0xFFFFFFFF,
+ hConv, 0, CF_TEXT, XTYP_EXECUTE, 30000, NULL);
+ if (code == 0) {
+ SetError(interp);
+ result = TCL_ERROR;
+ }
+ }
+ DdeFreeDataHandle(data);
+ } else {
+ SetError(interp);
+ result = TCL_ERROR;
+ }
+ break;
+ }
+ case DDE_REQUEST:
+ {
+ itemString = Tcl_GetStringFromObj(objv[firstArg + 2], &length);
+ if (length == 0) {
+ Tcl_SetStringObj(Tcl_GetObjResult(interp),
+ "cannot request value of null data", -1);
+ return TCL_ERROR;
+ }
+ hConv = DdeConnect(instance, service, topic, NULL);
+ DdeFreeStringHandle(instance, service);
+ DdeFreeStringHandle(instance, topic);
+
+ if (hConv == NULL) {
+ SetError(interp);
+ result = TCL_ERROR;
+ } else {
+ item = DdeCreateStringHandle(instance, itemString, CP_WINANSI);
+ if (item != NULL) {
+ data = DdeClientTransaction(NULL, 0, hConv, item, CF_TEXT,
+ XTYP_REQUEST, 5000, NULL);
+ if (data == NULL) {
+ SetError(interp);
+ result = TCL_ERROR;
+ } else {
+ Tcl_Obj *objPtr;
+ DWORD dataLength;
+
+ dataString = DdeAccessData(data, &dataLength);
+ objPtr = Tcl_NewStringObj(dataString, -1);
+ DdeUnaccessData(data);
+ DdeFreeDataHandle(data);
+ Tcl_SetObjResult(interp, objPtr);
+ }
+ } else {
+ SetError(interp);
+ result = TCL_ERROR;
+ }
+ }
+
+ break;
+ }
+ case DDE_POKE:
+ {
+ itemString = Tcl_GetStringFromObj(objv[firstArg + 2], &length);
+ if (length == 0) {
+ Tcl_SetStringObj(Tcl_GetObjResult(interp),
+ "cannot have a null item", -1);
+ return TCL_ERROR;
+ }
+ dataString = Tcl_GetStringFromObj(objv[firstArg + 3], &length);
+
+ hConv = DdeConnect(instance, service, topic, NULL);
+ DdeFreeStringHandle(instance, service);
+ DdeFreeStringHandle(instance, topic);
+
+ if (hConv == NULL) {
+ SetError(interp);
+ result = TCL_ERROR;
+ } else {
+ item = DdeCreateStringHandle(instance, itemString,
+ CP_WINANSI);
+ if (item != NULL) {
+ data = DdeClientTransaction(dataString,length+1,
+ hConv, item, CF_TEXT, XTYP_POKE, 5000, NULL);
+ if (data == NULL) {
+ SetError(interp);
+ result = TCL_ERROR;
+ }
+ } else {
+ SetError(interp);
+ result = TCL_ERROR;
+ }
+ }
+ break;
+ }
+
+ case DDE_SERVICES:
+ {
+ HCONVLIST hConvList;
+ CONVINFO convInfo;
+ Tcl_Obj *convListObjPtr, *elementObjPtr;
+ Tcl_DString dString;
+ char *name;
+
+ convInfo.cb = sizeof(CONVINFO);
+ hConvList = DdeConnectList(instance, service,
+ topic, 0, NULL);
+ DdeFreeStringHandle(instance, service);
+ DdeFreeStringHandle(instance, topic);
+ hConv = 0;
+ convListObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ Tcl_DStringInit(&dString);
+
+ while (hConv = DdeQueryNextServer(hConvList, hConv), hConv != 0) {
+ elementObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ DdeQueryConvInfo(hConv, QID_SYNC, &convInfo);
+ length = DdeQueryString(instance,
+ convInfo.hszSvcPartner, NULL, 0, CP_WINANSI);
+ Tcl_DStringSetLength(&dString, length);
+ name = Tcl_DStringValue(&dString);
+ DdeQueryString(instance, convInfo.hszSvcPartner,
+ name, length + 1, CP_WINANSI);
+ Tcl_ListObjAppendElement(interp, elementObjPtr,
+ Tcl_NewStringObj(name, length));
+ length = DdeQueryString(instance, convInfo.hszTopic,
+ NULL, 0, CP_WINANSI);
+ Tcl_DStringSetLength(&dString, length);
+ name = Tcl_DStringValue(&dString);
+ DdeQueryString(instance, convInfo.hszTopic, name,
+ length + 1, CP_WINANSI);
+ Tcl_ListObjAppendElement(interp, elementObjPtr,
+ Tcl_NewStringObj(name, length));
+ Tcl_ListObjAppendElement(interp, convListObjPtr,
+ elementObjPtr);
+ }
+ DdeDisconnectList(hConvList);
+ Tcl_SetObjResult(interp, convListObjPtr);
+ Tcl_DStringFree(&dString);
+ break;
+ }
+ case DDE_EVAL:
+ {
+ objc -= (async + 3);
+ ((Tcl_Obj **) objv) += (async + 3);
+
+ /*
+ * See if the target interpreter is local. If so, execute
+ * the command directly without going through the DDE
+ * server. Don't exchange objects between interps. The
+ * target interp could compile an object, producing a
+ * bytecode structure that refers to other objects owned
+ * by the target interp. If the target interp is then
+ * deleted, the bytecode structure would be referring to
+ * deallocated objects.
+ */
+
+ for (riPtr = interps; riPtr != NULL;
+ riPtr = riPtr->nextPtr) {
+ if (strcasecmp(serviceName, riPtr->name) == 0) {
+ break;
+ }
+ }
+
+ if (riPtr != NULL) {
+ /*
+ * This command is to a local interp. No need to go through
+ * the server.
+ */
+
+ Tcl_Preserve(riPtr);
+ sendInterp = riPtr->interp;
+ Tcl_Preserve(sendInterp);
+
+ /*
+ * Don't exchange objects between interps. The target interp
+ * would compile an object, producing a bytecode structure that
+ * refers to other objects owned by the target interp. If the
+ * target interp is then deleted, the bytecode structure would
+ * be referring to deallocated objects.
+ */
+
+ if (objc == 1) {
+ result = Tcl_GlobalEval(sendInterp,Tcl_GetString(objv[0]));
+ } else {
+ objPtr = Tcl_ConcatObj(objc, objv);
+ Tcl_IncrRefCount(objPtr);
+ result = Tcl_GlobalEval(sendInterp, Tcl_GetString(objPtr));
+ Tcl_DecrRefCount(objPtr);
+ }
+ if (interp != sendInterp) {
+ if (result == TCL_ERROR) {
+ char *value;
+ /*
+ * An error occurred, so transfer error information
+ * from the destination interpreter back to our
+ * interpreter.
+ */
+
+ Tcl_ResetResult(interp);
+ value = Tcl_GetVar2(sendInterp, "errorInfo", NULL,
+ TCL_GLOBAL_ONLY);
+ Tcl_AddObjErrorInfo(interp, value, length);
+
+ value = Tcl_GetVar2(sendInterp, "errorCode", NULL,
+ TCL_GLOBAL_ONLY);
+ Tcl_SetErrorCode(interp, value, (char *)NULL);
+ }
+ Tcl_SetObjResult(interp, Tcl_GetObjResult(sendInterp));
+ }
+ Tcl_Release(riPtr);
+ Tcl_Release(sendInterp);
+ } else {
+ /*
+ * This is a non-local request. Send the script to the server
+ * and poll it for a result.
+ */
+
+ if (MakeConnection(interp, serviceName, &hConv) != TCL_OK) {
+ goto error;
+ }
+
+ objPtr = Tcl_ConcatObj(objc, objv);
+ string = Tcl_GetStringFromObj(objPtr, &length);
+ itemData = DdeCreateDataHandle(instance, string,
+ length+1, 0, 0, CF_TEXT, 0);
+
+ if (async) {
+ DWORD status;
+
+ data = DdeClientTransaction((LPBYTE) itemData, 0xFFFFFFFF,
+ hConv, 0, CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC,
+ &status);
+ DdeAbandonTransaction(instance, hConv, status);
+ } else {
+ data = DdeClientTransaction((LPBYTE) itemData,
+ 0xFFFFFFFF, hConv, 0,
+ CF_TEXT, XTYP_EXECUTE, 30000, NULL);
+ if (data != 0) {
+
+ cookie = DdeCreateStringHandle(instance,
+ "$TCLEVAL$EXECUTE$RESULT", CP_WINANSI);
+ data = DdeClientTransaction(NULL, 0, hConv,
+ cookie, CF_TEXT, XTYP_REQUEST, 30000, NULL);
+ }
+ }
+
+ Tcl_DecrRefCount(objPtr);
+
+ if (data == 0) {
+ SetError(interp);
+ goto errorNoResult;
+ }
+
+ if (async == 0) {
+ Tcl_Obj *resultPtr;
+
+ /*
+ * The return handle has a two or four element list in
+ * it. The first element is the return code (TCL_OK,
+ * TCL_ERROR, etc.). The second is the result of the
+ * script. If the return code is TCL_ERROR, then the third
+ * element is the value of the variable "errorCode", and
+ * the fourth is the value of the variable "errorInfo".
+ */
+
+ resultPtr = Tcl_NewObj();
+ length = DdeGetData(data, NULL, 0, 0);
+ Tcl_SetObjLength(resultPtr, length);
+ string = Tcl_GetString(resultPtr);
+ DdeGetData(data, string, length, 0);
+ Tcl_SetObjLength(resultPtr, strlen(string));
+
+ if (Tcl_ListObjIndex(NULL, resultPtr, 0, &objPtr)
+ != TCL_OK) {
+ Tcl_DecrRefCount(resultPtr);
+ goto error;
+ }
+ if (Tcl_GetIntFromObj(NULL, objPtr, &result) != TCL_OK) {
+ Tcl_DecrRefCount(resultPtr);
+ goto error;
+ }
+ if (result == TCL_ERROR) {
+ Tcl_ResetResult(interp);
+
+ if (Tcl_ListObjIndex(NULL, resultPtr, 3, &objPtr)
+ != TCL_OK) {
+ Tcl_DecrRefCount(resultPtr);
+ goto error;
+ }
+ length = -1;
+ string = Tcl_GetStringFromObj(objPtr, &length);
+ Tcl_AddObjErrorInfo(interp, string, length);
+
+ Tcl_ListObjIndex(NULL, resultPtr, 2, &objPtr);
+ Tcl_SetObjErrorCode(interp, objPtr);
+ }
+ if (Tcl_ListObjIndex(NULL, resultPtr, 1, &objPtr)
+ != TCL_OK) {
+ Tcl_DecrRefCount(resultPtr);
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ Tcl_DecrRefCount(resultPtr);
+ }
+ }
+ }
+ }
+ if (cookie != NULL) {
+ DdeFreeStringHandle(instance, cookie);
+ }
+ if (item != NULL) {
+ DdeFreeStringHandle(instance, item);
+ }
+ if (itemData != NULL) {
+ DdeFreeDataHandle(itemData);
+ }
+ if (data != NULL) {
+ DdeFreeDataHandle(data);
+ }
+ if (hConv != NULL) {
+ DdeDisconnect(hConv);
+ }
+ return result;
+
+ error:
+ Tcl_SetStringObj(Tcl_GetObjResult(interp),
+ "invalid data returned from server", -1);
+
+ errorNoResult:
+ if (cookie != NULL) {
+ DdeFreeStringHandle(instance, cookie);
+ }
+ if (item != NULL) {
+ DdeFreeStringHandle(instance, item);
+ }
+ if (itemData != NULL) {
+ DdeFreeDataHandle(itemData);
+ }
+ if (data != NULL) {
+ DdeFreeDataHandle(data);
+ }
+ if (hConv != NULL) {
+ DdeDisconnect(hConv);
+ }
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_DdeInit --
+ *
+ * This procedure initializes the dde command.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Blt_DdeInit(interp)
+ Tcl_Interp *interp;
+{
+ Tcl_CreateObjCommand(interp, "dde", DdeObjCmd, NULL, NULL);
+ conversations = NULL;
+ interps = NULL;
+ Tcl_CreateExitHandler(ExitProc, NULL);
+ return Tcl_PkgProvide(interp, TCL_DDE_PACKAGE_NAME, TCL_DDE_VERSION);
+}
+
+#endif /* NO_DDE */