Commands written for today's SpecTcl should be writtena
as classes derived from CTCLObjectProcessor
. The MPI
wrapper classes are only able to wrap that sort of class.
However, originally SpecTcl only had a
CTCLProcessor
base class for writing commands and,
naturally, there may still be commands out there derived from
CTCLProcessor
. This section describes how to
convert CTCLProcessor
derived classes into
CTCLObjectProcesssor
derived classes so they can be wrapped
in e.g. CMPITclCommand
Several SpecTcl commands were still CTCLProcessor
derived
and this chapter provides lessons learned porting those.
Before staring let's look at the differences between the signatures for
operator()
for the two classes.
For CTCLProcessor
, operator()
looks like this:
Where argc
is the number of words in the command,
argv
are pointers to the verb text,
and result
is the result object for the result that will
be returned by the command.
On the other hand, for CTCLObjectProcesor
,
operator()
looks like this:
WHere objv
are the command words and the result should be set via
interp.setResult which has a few different overloads.
In addition to the relatively simple problem of changing the inheritance, porting
requires deciding how to handle the parameterization of operator()
where access to the command words may be sprinkled throughout the code.
The SpecTcl spectrum is an example of a command that had been
derived from CTCLProcessor
but had to be ported to be derived
from CTCLObjectProcessor
in order to be wrapped in
CMPITclCommandAll
.
The spectrum is a command ensemble. Each of the options
-new
, -list
, -delete
and
-trace
can be thought
of as a subcommand (if none of these options is present the command internally defaults to
-new
). In SpecTcl, such ensembles typically execute each subcommand in
another method with operator()
mostly just dispatching to the appropriate
subcommand method.
First lests look at a fragment of the header (SpectrumCommand.h).
Example 2-5. Porting CTCLProcessor
to
CTCLObjectProcessor
SpectrumCommand.h
... class CSpectrumCommand : public CTCLPackagedObjectProcessor { ... public: virtual int operator() (CTCLInterpreter& rInterpreter, std::vector<CTCLObject>& objv) ; Int_t New (CTCLInterpreter& rInterpreter, int nArgs, const char* pArgs[]) ; Int_t List (CTCLInterpreter& rInterp, int nArgs, const char* Args[]); Int_t Delete (CTCLInterpreter& rInterp, int nArgs, const char* pArgs[]) ; Int_t Trace (CTCLInterpreter& rInterp, int nArgs, const char* pArgs[]); ... }; ...
This example shows that the operator()
method will marshall
the objv
array into an argc/argv and pass that to the
subcommand processors. The only modifications needed to the subcommand processors
then become removing the result
from their signatures and
figuring out how, then, to handle returning results.
The next example shows a fragment of code from operator()
that recreates argc and argv from objv
Example 2-6.
Porting CTCLProcessor
to CTCLObjectProcessor
Marshalling arguments.
int CSpectrumCommand::operator()(CTCLInterpreter& rInterpreter, std::vector<CTCLObject>& objv) { std::vector<std::string> words; std::vector<const char*> pWords; // Due to lifetimes and how c_str behaves we need two loops not one: for (auto& word : objv) { words.push_back(std::string(word)); } for (int i =0; i < words.size(); i++) { pWords.push_back(words[i].c_str()); } int nArgs = words.size(); auto pArgs = pWords.data(); ... }
First objv
is converted into a vector of std::string. Next,
this is used to produce a vector of const char*. This has to be done in two loops
due to lifetime issues. Finally, nArgs (argc if you prefer) is just the size of either
of those vectors and pArgs (argv if you prefer) is just the data in the vector of
const char*s.
Each subcommand processor new needs to figure out how to deal with the "missing"
result parameter and, instead use the interpreter's setResult
to set the result. It's simplest to note that std::string has most of the methods that
CTCLResult
has for building up contents.
Let's look at the Usage
for the spectrum command. It appends a
summary of the syntax of the spectrum to the contents of result.
Originally, the result object was passed by reference to this method:
Example 2-7. Porting CTCLProcessor
to CTCLObjectProcessor
substituting std::string for the result
void CSpectrumCommand::Usage(CTCLInterpreter& rInterp, const char* prefix) { std::string rResult = rInterp.GetResultString(); if (prefix) rResult += prefix; rResult += "Usage: \n"; rResult += " spectrum [-new] name type { parameters... } {axisdefs... [datatype]y\n"; rResult += " spectrum -list ?-byid? ?-showgate? [pattern]\n"; rResult += " spectrum -list pattern\n"; rResult += " spectrum -list -id ?-showgate? id\n"; rResult += " spectrum -delete name1 [name2...]\n"; rResult += " spectrum -delete -id id1 [id2...]\n"; rResult += " spectrum -delete -all\n"; rResult += " spectrum -trace add ?script?\n"; rResult += " spectrum -trace delete ?script?\n"; rResult += " In the above, an axsidef has one of the following formats:\n"; rResult += " n - n is the Log(2) the number of channels\n"; rResult += " {low hi n} - Full definition where:\n"; rResult += " low - Parameter value represented by channel 0\n"; rResult += " hi - Parameter value represented by channel n-1\n"; rResult += " n - Number of channels on the axis.\n"; rResult += "\n The spectrum command creates and deletes spectra as well\n"; rResult += " as listing their properties."; rResult += " The -trace switch allows the creation, inspection and removal\n"; rResult += " of traces on adding and deleting spectra\n"; rInterp.setResult(rResult); }
This technique can be used directly in command or subcommand processors:
create a local std::string named the same as the original result parameter,
and just prior to the return statement, invoked
the setResult
method of the interpreter object to
set the command's result. One of the overloads of
CTCLInterpreter
::setResult
sets the result
from an std::string object.