wickensonline.co.uk
Retrochallenge 2009 Winter Warmup Entry
Programming-2
|
Retrochallenge 2009
Mark Wickens
11-Jan-2009 20:41
Programming 2
I seem to be suffering from a recurrance of the age old programmers dilema. I
know I should do 'documentation' (i.e., this web blog) but a part of me just
wants to keep cracking on - now I'm doing some coding.
SMG$ HELLO WORLD APPLICATION
First order of the day was to get through the initial struggle of getting a C
application written that would do the bare minimum to display a message using
the SMG$ Screen Management library.
I found a couple of examples on the internet that helped out with the use of
descriptors - the VMS way of specifying strings and other data structures.
There is a standard #define called $DESCRIPTOR that allows the definition of a
static descriptor from a C null-terminated character string:
static $DESCRIPTOR(hello_message_d, "Hello World!");
The string descriptor is defined by a C struct with the following elements:
DBG> ex hello_message_d
SMG$HELLOWORLD\main\hello_message_d
dsc$w_length: 12
dsc$b_dtype: 14
dsc$b_class: 1
dsc$a_pointer: 1040
This is a debugger display of the hello_message_d static descriptor defined in
SMG$ hello world application source file smg$helloworld.c[1]. The key difference
between C strings and VMS string descriptors is that C strings are null
terminated, but VMS string descriptors explicitly include a length.
Within the debugger if you want to see the contents of a string descriptor you
can use the EXAMINE/ASCID command:
DBG> ex/ascid hello_message_d
SMG$HELLOWORLD\main\hello_message_d: 'Hello World!'
With the help of the RTL Screen Management(SMG$) Manual I was able to get an
application working fairly quickly.
The following calls are made to initialize the SMG$ display, output a line and
retrieve a key press:
rtn = smg$create_pasteboard(&pasteboard_id);
rtn = smg$create_virtual_display(&5, &80, &display_id);
rtn = smg$create_virtual_keyboard(&keyboard_id);
rtn = smg$paste_virtual_display(&display_id, &pasteboard_id, &10, &15);
rtn = smg$put_line(&display_id, &hello_message_d);
rtn = smg$read_keystroke(&keyboard_id, &word_terminator_code);
When run it produces the following output until a key is pressed:
This screen grab was done using gimp on my X60 laptop as I am using it to write
this article, in ALLIN1 on the VAX obviously! The decterm is displayed by the
X-server software Exceed running on top of Windows XP. In order to create the
decterm I have to start up a telnet session (I'm using PowerTerm 525 from
Ericom) and set the display on the VAX remote, then create the terminal
detached, using the following commands:
$ set display/create/node=192.168.1.22/trans=tcpip
$ create/term/detach
The following screen grab is the same decterm when running under DECwindows on
the VAX with a monitor connected directly:
I was happy with the results of my SMG$ test and once I got my head into it I
found it surprisingly easy to get working. The compile, link and run commands
were trivial:
$ cc smg$helloworld
$ link smg$helloworld
$ run smg$helloworld
This method of running the command, however, has the drawback that no command
line arguments can be specified. I want my RCU utility to include the ability to
work from the command line. The 'C' standard way of processing command line
arguments is via the main() method parameters argc and argv. These give the
count of command line parameters and an array of character strings holding the
values (separated by spaces).
In order to run the command so that command line arguments are processed using
the 'C' standard way, you can define a symbol to execute the application, for
example:
$ smg$helloworld :== "$smg$helloworld.exe"
The initial dollar sign at the start of the quoted string instructs VMS to run
the command.
The VMS 'standard' way to specify commands is by using the Command Definition
Utility.
COMMAND DEFINITION FILE
I'd created the Command Definition File for the RCU utility the other day,
although at this stage it might still need some tweaking. The file contains a
description of the command line options that can be specified for the command.
The Command Definition Utility can then be used to add the command to the
command line interpreter. The command line interpreter then takes care of
ensuring that the command is called with correctly-specified arguments and
parameters. It is still the responsibility of the application, however, to
determine what has been specified, via the CLI$ library.
My command definition file, RCU.CLD[2], contains the following:
_______________________________________________________________________________
MODULE RCU_TABLE
IDENT "V1-001"
DEFINE VERB RCU
IMAGE "DKA300:[MSW.RETRO2009WW.RCU]RCU.EXE"
PARAMETER P1, LABEL=MACHINE, PROMPT="Machine Mnemonic"
QUALIFIER ADDRESS, VALUE
QUALIFIER PORT, VALUE(DEFAULT=80,TYPE=$NUMBER)
QUALIFIER SWITCH, VALUE(TYPE=SWITCH_KEYWORDS)
QUALIFIER DIRECTION, VALUE(TYPE=$NUMBER)
QUALIFIER INTERFACE, VALUE(TYPE=INTERFACE_KEYWORDS)
DISALLOW SWITCH AND DIRECTION
DEFINE TYPE SWITCH_KEYWORDS
KEYWORD TOGGLE, DEFAULT
KEYWORD ON
KEYWORD OFF
DEFINE TYPE INTERFACE_KEYWORDS
KEYWORD CLI, DEFAULT
KEYWORD SMG
KEYWORD DECW
_______________________________________________________________________________
The VERB definition includes the location of the executable, the possible
parameters and possible qualifiers. Qualifiers start with the slash '/'
character and parameters are space separated.
An example call might be:
$ RCU/ADDRESS=192.168.1.199/PORT=80/SWITCH=ON/INTERFACE=CLI VAX4K90
In this call, /ADDRESS is a qualifier and VAX4K90 is the first parameter. The
command definition file can include the specification of exclusive parameters,
such as the line DISALLOW SWITCH AND DIRECTION.
So, if I try and call the command like this:
$ RCU/SWITCH=ON/DIRECTION=1 VAX4K90
the command line interpreter will not run the application and returns an error:
%DCL-W-CONFLICT, illegal combination of command elements - check documentation
\SWITCH\
The valid set of keywords for a qualifier can be specified, so if I try and call
the command like this:
$ RCU/SWITCH=MAYBE VAX4K90
then I get the following error because MAYBE is not one of the valid keywords
for the SWITCH qualifier (valid keywords are ON, OFF and TOGGLE):
%DCL-W-IVKEYW, unrecognized keyword - check validity and spelling
\MAYBE\
Each parameter or qualifier can have a type specified, such as the PORT
parameter that can only be a number. Not specifying a number results in an
error:
$ RCU/PORT=ABC
%DCL-W-NUMBER, invalid numeric value - supply an integer
\ABC\
The Command Definition Utility processes the command definition file and makes
it available to the command interpreter:
$ SET COMMAND RCU
With the correct priviliges you can add the command to the system command table
and make it available to all users on the machine.
COMMAND LINE PROCESSING
In order to make the command line options available to the application I created
a C structure to contain the parameter and qualifier values and defined it in
cmdline.h[3]:
/*
* Structure to hold the results of parsing the command line
*/
struct rcu_options {
char machine_value[MAX_MACHINE_NAME_LEN+1];
char address_value[MAX_ADDRESS_LEN+1];
unsigned short port_value;
unsigned short ch_value;
unsigned short directon_value;
unsigned short interface_value;
unsigned machine_flag : 1;
unsigned address_flag : 1;
unsigned port_flag : 1;
unsigned switch_flag : 1;
unsigned direction_flag : 1;
unsigned interface_flag : 1;
};
The function to parse the command line is declared to take a reference to an
instance of this structure:
/* declaration for the defined functions */
extern unsigned long parse_cmdline(struct rcu_options *options);
The command line parser function is defined in cmdline.c[4] and makes use of the
CLI$ library routines CLI$PRESENT and CLI$GET_VALUE. As these conform to the
OpenVMS calling standard they use descriptors to specify strings. A number of
static descriptors specify the name of the command line arguments:
$DESCRIPTOR(cli_address, "ADDRESS");
$DESCRIPTOR(cli_port, "PORT");
$DESCRIPTOR(cli_switch, "SWITCH");
$DESCRIPTOR(cli_direction, "DIRECTION");
$DESCRIPTOR(cli_interface, "INTERFACE");
$DESCRIPTOR(cli_machine, "MACHINE");
A dynamic descriptor is used to hold the values retrieved for each of the
qualifiers and parameters as they are processed:
struct dsc$descriptor_d work_str;
A #define is used to initialize the descriptor to hold a string:
#define init_dyndesc(dsc) {\
dsc.dsc$w_length = 0;\
dsc.dsc$b_dtype = DSC$K_DTYPE_T;\
dsc.dsc$b_class = DSC$K_CLASS_D;\
dsc.dsc$a_pointer = NULL;}
The following include files make the CLI$ routines available that are used to
process the command line arguments:
#include <climsgdef.h>
#include <cli$routines.h>
The following code processes the /PORT qualifier and extracts the value
specified:
/* check for the PORT value, normally: 80 */
status = cli$present(&cli_port);
if (status & 1) {
options->port_flag = 1;
status = cli$get_value(&cli_port, &work_str);
if (status & 1) {
/** 3rd argument '2' specifies a 2-byte word as target */
status = ots$cvt_tu_l(&work_str, &options->port_value, 2);
}
}
The cli$present call determines if the command line argument is present. If it
is then the cli$get_value call returns the value as a string descriptor. In the
case of the PORT qualifier we must then convert the string into an unsigned
short - the call to ots$cvt_tu_l performs this conversion.
ENDNOTES
1. smg$helloworld.c
2. RCU.CLD
3. cmdline.h
4. cmdline.c