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:

      smg$helloworld output (XP)

      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:

      smg$helloworld output (DECW)

      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