Chapter 2. Porting custom commands

One of the strengths of SpecTcl and the Tcl scripting language it uses is its extensibility. Complex tailored SpecTcl programs have used this to extend the Tcl interpreter, adding commands of their own to the base Tcl/Tk core and SpecTcl extensions to that core. When SpecTcl is running as an MPI program, only one of the processes has an interactive interpreter. Therefore, your custom commands must be wrapped in appropriate classes that will relay your command to the rest of the program from the interactive interpreter process. For more about this, see How mpiSpecTcl works in parallel mode.

This chapter will introduce two classes which:

I will also describe how your code can recognize if the application is running serially or as an MPI application and how to know, in the latter case, the role of the process it is executing in.

The techniques I will describe have been used withn the SpecTcl command extensions and examples will be drawn from that code.

2.1. The command wrapper classes

In order to function in the MPI application environment, your commands must be wrapped in one of two classes: CMPITclCommand defined in CMPITclCommand.h or CMPITclCommandAll defined in CMPITclCommandAll.h

Your command must still be created in MySpecTclApp.cpp's AddCommand method. The procedure is to first create an instanced of your command then to wrap that instanced in the class you chose.

In most cases you won't need your command to run in the interactive interpreter process. Again, see How mpiSpecTcl works in parallel mode.. If that is the case you should wrap your command in CMPITclCommand. If, on the other hand, you need your command to run in the main interpreter, wrap it in CMPITclCommandAll

The example below is the CMySpecTclApp::AddCommands from CCUSBSpecTcl, howing how the parammap is wrapped in CMPITclCommand. The procedures is identical for CMPITclCommandAll as its constructor has the same argument signature as CMPITclCommand's constructor.

Example 2-1. Wrapping a command in CMPITclCommand


#include "MPITclCommand.h"

...

void 
CMySpecTclApp::AddCommands(CTCLInterpreter& rInterp)  
{ 
  CTclGrammerApp::AddCommands(rInterp);         (1)
  CParamMapCommand::create(rInterp);            (2)
  auto pParamMap = CParamMapCommand::getInstance(); (3)
  new CMPITclCommand(rInterp, "parammap", pParamMap); (4)
}  
                

This example is made a bit more complicated than most of your commands will be because CCUSB has a static creation method to create and store the instance of the command and another static method to retrieve a pointer to that method. For a detailed description of the sample code, see below where the numbers in the list correspond to numbered callouts in the code itself.

(1)
As usual, TclGrammerApp::AddCommands is called to define the standard SpecTcl commands (like e.g. spectrum).
(2)
This creates the parammap command handler and stores a pointer to is instance. Note that it's just fine to register the command with the interpreter. The wrapper will override that registration.
(3)
Retrieves a pointer to the command processor instance. The processor instance must have been implemented as a class derived eventually from CTCLObjectProcessor. For classes derived from the older, obsolete CTCLProcessor, see Wrapping old argc, argv commands. for some simple techniques to convert those to CTCLObjectProcessor processors.
(4)
This is the magic that does the actual wrapping. The constructor for both CMPITclCommand.h takes three parameters:

  • A reference to the CTCLInterpreter on which the command is defined. This was passed in as rIterp to AddCommands

  • The command name under which the wrapped command shoulid be invoked. Normally this shoulid be the same command name used to create your wrapped command. In this case it is parammap

  • The wrapped command itself, a pointer to an object from a class derived from CTCLObjectProcessor.

The pman SpecTcl core command processor uses an interesting trick that allows you to leave the initialization code in MySpecTclApp.cpp unmodified. It also is wrapped in a CMPITclCommandAll.

The original command processor class, CPipelineCommand is renamed to CPipelineCommandActual. This just a simple global string replace for the most part. A new class named CPipelineCommand derived, in this case, from CMPITclCommandAll with the same constructor signature as CPipelineCommandActual which constructs that actual command processor and wraps it. This method is suitable when the wrapped command processor is not exporting any services to the external code.

The example below shows excerpts from the header CPipelineCommand.h.

Example 2-2. Transparently wrapping commands by renaming the actual processor


#include <PITclCommandAll.h>
#include <TCLObjectProcessor.h>

...
class CPipelineCommandActual : public CTCLObjectProcess  (1)
or
{
...
public:

    CPipelineCommandActual(CTCLInterpreter& interp);
    int operator()(
        CTCLInterpreter& interp, 
        std::vector<CTCLObject>&; objv
    );

...
};

class CPipelineCommand : public CMPITclCommandAll {  (2)
public:
    CPipelineCommand(CTCLInterpreter& rInterp) ;
    ~CPipelineCommand() {}
};

                
(1)
Here we see the renamed original class. Note that in the implementation you will want to replace OriginalClassName:: with OriginalClassNameActual::
(2)
Here is the definition of the replacement for the original class.

where the implementation of CPipelineCommand, CPipelineCommand.cpp is now:

Example 2-3. Implementing the replacement class


CPipelineCommand::CPipelineCommand(CTCLInterpreter& rInterp) : (1)
  CMPITclCommandAll(rInterp, "pman", new CPipelineCommandActual(rInterp)) {}
                

Doing this allows your wrapped command to be constructed in exactly the same way the original command was. If you have to wrap a lot of commands this can be conceptually simpler.

(1)
Here you can see how we implement the constructor of the wrapper class so that it wraps the actual class.