Slide A: Outline of Talk Slide A: Outline of Talk Slide A: Outline of Talk ________________________ ________________________ ________________________ ________________________ Slide A: Outline of Talk This purpose of this presentation is to show that it is possible to write DECwindows programs in languages other than "C". This presentation is not an introduction to DECwindows programming, and I will assume that you know a bit about it (but don't let that scare you away). Incidentally, this talk deals exclusively with DECwindows V2, not the newer DECwindows/Motif. The structure of this presentation is as follows: 1. A quick overview of DECwindows 2. Files required to write, compile and link DECwindows programs 3. A simple "Hello World" program 4. A program to calculate Mortgage repayments 5. Summary LEAVE THE SLIDE ON THE PROJECTOR AND COVER ALL BUT ITEM 1 LEAVE THE SLIDE ON THE PROJECTOR AND COVER ALL BUT ITEM 1 LEAVE THE SLIDE ON THE PROJECTOR AND COVER ALL BUT ITEM 1 LEAVE THE SLIDE ON THE PROJECTOR AND COVER ALL BUT ITEM 1 NO SLIDE CHANGE NO SLIDE CHANGE NO SLIDE CHANGE NO SLIDE CHANGE DECwindows is Digital's supported version of the X11 Window System, as developed at MIT over the last several years. It is available for VMS and ULTRIX systems and a X11 Server is available for MS-DOS with the PATHWORKS product. Many computer vendors supply only the standard MIT distribution of X, if they supply one at all, and programs on those systems are restricted to writing their X11 programs in "C" only. Under VMS and ULTRIX, programmers have the option of using many of the languages which Digital supports on these operating systems. My talk concerns the VMS version of DECwindows; Richard Sharpe has already talked about DECwindows under ULTRIX earlier today. Programmers writing for VAX/VMS have the choice of the standard MIT "C" interface to X11 or can use the so-called VMS Bindings, routines written to VAX procedure calling standards which interface to the standard X11 library calls. The DECwindows programming documentation describes each DECwindows routine in two ways, one for the standard MIT "C" binding and one for the VMS binding: DISPLAY SLIDE B - EXAMPLE ROUTINE - XT$PENDING DISPLAY SLIDE B - EXAMPLE ROUTINE - XT$PENDING DISPLAY SLIDE B - EXAMPLE ROUTINE - XT$PENDING DISPLAY SLIDE B - EXAMPLE ROUTINE - XT$PENDING Slide C: Files Used in DECwindows Programming Slide C: Files Used in DECwindows Programming Slide C: Files Used in DECwindows Programming _____________________________________________ _____________________________________________ _____________________________________________ _____________________________________________ Slide C: Files Used in DECwindows Programming Most files needed when writing DECwindows programs are in either DECW$INCLUDE: or SYS$LIBRARY: DECW$INCLUDE: is where you will find .H files (for "C" programming using the MIT binding) and a selection of .UIL files, which are the same no matter what language your DECwindows program is written in. In particular, the file DWTAPPL.UIL contains definitions of widget arguments etc used within UIL files. SYS$LIBRARY: is where definition files for all languages can be found. Also, the shareable images which contain the DECwindows routines are found here. Choose the files from SYS$LIBRARY: according to the application. DISPLAY SLIDE D - FILEVIEW LISTING DISPLAY SLIDE D - FILEVIEW LISTING DISPLAY SLIDE D - FILEVIEW LISTING DISPLAY SLIDE D - FILEVIEW LISTING For programs in all languages except ADA, which use the DECwindows For programs in all languages except ADA, which use the DECwindows For programs in all languages except ADA, which use the DECwindows For programs in all languages except ADA, which use the DECwindows Toolkit and don't make XLib calls: Toolkit and don't make XLib calls: Toolkit and don't make XLib calls: Toolkit and don't make XLib calls: - DECW$DWTDEF - DECW$DWTDEF - DECW$DWTDEF - DECW$DWTDEF Structures and routines for using the DECwindows Toolkit DECW$DWTENTRY DECW$DWTENTRY DECW$DWTENTRY - DECW$DWTENTRY Routines in the DECwindows Toolkit DECW$DWTSTRUCT DECW$DWTSTRUCT DECW$DWTSTRUCT - DECW$DWTSTRUCT Structures for using the DECwindows Toolkit DECW$DWTWIDGETDEF DECW$DWTWIDGETDEF DECW$DWTWIDGETDEF - DECW$DWTWIDGETDEF Structures and routines for using the DECwindows Toolkit plus extra definitions for dealing with widget internals DECW$DWTWIDGETSTRUCT DECW$DWTWIDGETSTRUCT DECW$DWTWIDGETSTRUCT - DECW$DWTWIDGETSTRUCT Structures used in dealing with widget internals DECW$DWTMSG DECW$DWTMSG DECW$DWTMSG - DECW$DWTMSG VMS error message code definitions For ADA programs: DECW$DWT_.ADA DECW$DWT_.ADA DECW$DWT_.ADA - DECW$DWT_.ADA all all all Contains all DECwindows Toolkit definitions For programs which use Xlib directly: DECW$XLIBDEF, DECW$XLIBMSG DECW$XLIBDEF, DECW$XLIBMSG DECW$XLIBDEF, DECW$XLIBMSG - DECW$XLIBDEF, DECW$XLIBMSG All languages except ADA DECW$X_.ADA DECW$X_.ADA DECW$X_.ADA - DECW$X_.ADA For ADA programs. For BASIC, BLISS, C, FORTRAN and PL/1, the DECW$DWTDEF files contain only two statements, to include the DECW$DWTENTRY and DECW$DWTSTRUCT files. Similarly, the DECW$DWTWIDGETDEF files include these two files plus the DECW$DWTWIDGETSTRUCT file. Most programmers will use DECW$DWTDEF or DECW$DWTWIDGETDEF. Slide E: Linking a DECwindows Application Slide E: Linking a DECwindows Application Slide E: Linking a DECwindows Application _________________________________________ _________________________________________ _________________________________________ _________________________________________ Slide E: Linking a DECwindows Application You will need a linker options file to link your program, and this file includes the same statements whether you are using the MIT "C" binding or the VMS binding. Your file will contain either or both of the following statements: SYS$SHARE:DWTLIBSHR/SHARE SYS$SHARE:DWTLIBSHR/SHARE SYS$SHARE:DWTLIBSHR/SHARE SYS$SHARE:DWTLIBSHR/SHARE ! To link the DECwindows Toolkit SYS$SHARE:XLIBSHR/SHARE SYS$SHARE:XLIBSHR/SHARE SYS$SHARE:XLIBSHR/SHARE SYS$SHARE:XLIBSHR/SHARE ! To link the Xlib routines There are other DECwindows shareable images in SYS$SHARE: but the above two are the most commonly used. DO NOT DISPLAY THE NEXT SLIDE YET DO NOT DISPLAY THE NEXT SLIDE YET DO NOT DISPLAY THE NEXT SLIDE YET DO NOT DISPLAY THE NEXT SLIDE YET A "Hello World" Program A "Hello World" Program A "Hello World" Program _______________________ _______________________ _______________________ _______________________ A "Hello World" Program The first program anyone writes is traditionally "Hello World" and here's a version in MIT "C": DISPLAY SLIDE F - XHELLO.C DISPLAY SLIDE F - XHELLO.C DISPLAY SLIDE F - XHELLO.C DISPLAY SLIDE F - XHELLO.C And here's a version in Pascal using the VMS binding: DISPLAY SLIDE G - XHELLO.PAS AS WELL DISPLAY SLIDE G - XHELLO.PAS AS WELL DISPLAY SLIDE G - XHELLO.PAS AS WELL DISPLAY SLIDE G - XHELLO.PAS AS WELL In "C", argv[0] contains the program name but Pascal does not provide a mechanism for this, so we have to pass it explicitly. Similarly, "C" provides its own way of extracting command line arguments, which Xt$Initialize Xt$Initialize Xt$Initialize X11 supports, but Pascal does not so those parameters to Xt$Initialize are ignored. Xt$Create_Managed_Widget Xt$Create_Managed_Widget Xt$Create_Managed_Widget The first hint of problems to come is in Xt$Create_Managed_Widget where I have had to type-cast the widget class parameter in the Pascal version of the program. This is because the definitions in SYS$LIBRARY:DECW$DWTWIDGETDEF.PAS are not quite compatible: labelwidgetclass Label_Class labelwidgetclass Label_Class labelwidgetclass Label_Class labelwidgetclass is variable of type Label_Class which is not the same Widget_Class Widget_Class Widget_Class as type Widget_Class. The last two parameters are used to specify non-default attributes for the widget. The default text for a label widget is the name of the widget. Here's what you get on the screen: REVEAL THE "HELLO" SCREEN DUMP REVEAL THE "HELLO" SCREEN DUMP REVEAL THE "HELLO" SCREEN DUMP REVEAL THE "HELLO" SCREEN DUMP The LOAN Program The LOAN Program The LOAN Program ___ ____ _______ ___ ____ _______ ___ ____ _______ ___ ____ _______ The LOAN Program This is a program I wrote as an exercise in DECwindows programming. It functions as a home loan mortgage repayments predictor. Here's what it looks like on the screen: DISPLAY SLIDE H - "LOAN" SCREEN DUMP DISPLAY SLIDE H - "LOAN" SCREEN DUMP DISPLAY SLIDE H - "LOAN" SCREEN DUMP DISPLAY SLIDE H - "LOAN" SCREEN DUMP There is no slide I. Slide J: Initializing the X Environment Slide J: Initializing the X Environment Slide J: Initializing the X Environment _______________________________________ _______________________________________ _______________________________________ _______________________________________ Slide J: Initializing the X Environment 1. A program label provides an easy way of aborting the program when a DECwindows call fails. You can put statements after the label to clean up. An alternative is to call SYS$EXIT and use an exit handler to clean up, but exit handlers sometimes have problems in themselves (eg have files opened by the language's RTL been closed?). 2. Purely a convenience for calling Xt$Initialize (item 6). A trailing NUL is not required as the VMS call binding performs string conversion in this case. 3. There are many cases where numeric definitions have to be made to interface with similar definitions in the UIL file. We could either define a few numeric constants, or define an enumerated type, which is what I chose to do. The use of an enumerated type simplifies debugging in PASCAL because when you display enumerated values the Pascal RTL converts them to text strings. Another advantage of enumerated types, in any language, is that the compiler automatically sets the numeric value of new elements. However, be sure of the base value of the type, ie is the first element ZERO? Finally, note the LONG alignment which ensures type compatibility with the UIL definitions. 4. In this program there are three text widgets used for getting numeric input. I used an array of widgets to make callbacks simpler, which is quite feasible when all the widgets are the same field_type field_type field_type type and used in a similar way. The field_type is passed in the callback when the widget is created or referenced. In addition, each input field has an associated name for use in error messages and tracewrites. 5. When using DRM we begin by initializing it. This call is exactly the same as in the standard "C" binding (but note the slightly different routine name). 6. Following DRM, we initialize the X Toolkit, which is done by calling Xt$Initialize. This routine's parameters are as follows: P1 Application Name P2 Class name P3 (and remaining) are used in parsing the command line The command line parsing parameters are required, except for the last which would be filled in with the actual program parameter values. These parameters are usually used only by "C" programs. The class name is used by the X Toolkit routines to locate the resource file for the application. Resources for the application named in parameter 1 are prefixed by the application name in the resource file. In practice, many programs make the application name and the resource name the same. DISPLAY SLIDE K - LOAN.DAT DISPLAY SLIDE K - LOAN.DAT DISPLAY SLIDE K - LOAN.DAT DISPLAY SLIDE K - LOAN.DAT Slide L: Opening the UIL File(s) Slide L: Opening the UIL File(s) Slide L: Opening the UIL File(s) ________________________________ ________________________________ ________________________________ ________________________________ Slide L: Opening the UIL File(s) This is the first case I encountered in this program where the VMS binding proved to be less than simple! 1. After initializing the DECwindows Resource Manager and the DECwindows Toolkit, the UIl file is opened by calling Dwt$Open_Hierarchy Dwt$Open_Hierarchy Dwt$Open_Hierarchy Dwt$Open_Hierarchy. DISPLAY SLIDE M - ROUTINE DEFINITION, SO THAT BOTH SLIDES ARE DISPLAY SLIDE M - ROUTINE DEFINITION, SO THAT BOTH SLIDES ARE DISPLAY SLIDE M - ROUTINE DEFINITION, SO THAT BOTH SLIDES ARE DISPLAY SLIDE M - ROUTINE DEFINITION, SO THAT BOTH SLIDES ARE VISIBLE VISIBLE VISIBLE VISIBLE This routine can open as many UIL files as you like, eg to cater for site or language variants of your application. Each file is indicated by an array element which points to a string descriptor for the filename. This is different from the "C" binding where each array element points to the string itself. In the degenerate case of one file it is necessary to declare a single-element array. It is not sufficient to pass the filename by descriptor because the routine is expecting the address of a pointer to a descriptor, not the address of the descriptor itself. I'll describe how to the set up this array in a moment. The third parameter is O/S dependent. In the VMS environment it can be used to supply default filespecs for the UIL files listed in the first parameter. The final parameter is a context variable which will be used in later calls to UIL-related routines. 2. When setting up the filenames list we must be careful not to create temporary copies of strings which will be destroyed before the list is used. The address of the filename can be obtained by iaddress iaddress iaddress the iaddress function in Pascal but this routine will not accept a UIL_filename UIL_filename UIL_filename constant for the actual parameter, hence UIL_filename is declared [readonly] [readonly] [readonly] as a variable. The [readonly] attribute tells the compiler that there is no need to make a temporary copy of the string, so that the value returned by the iaddress function will still be correct Dwt$Open_Hierarchy Dwt$Open_Hierarchy Dwt$Open_Hierarchy when the file list is passed to Dwt$Open_Hierarchy. Finally, note the trailing NULL on the filespec. The next slide shows some more examples of argument list construction. Calling DRM REGISTER NAMES Calling DRM REGISTER NAMES Calling DRM REGISTER NAMES __________________________ __________________________ __________________________ __________________________ Calling DRM REGISTER NAMES 1. Now that the UIL file has been opened we must link the callback routines in LOAN.PAS with the declarations in the UIL file. This Dwt$Register_DRM_Names Dwt$Register_DRM_Names Dwt$Register_DRM_Names is done by calling the routine Dwt$Register_DRM_Names which has only two paramaters: a list of routine name and address pairs, and a count of the number of entries in this list. DISPLAY SLIDE N DISPLAY SLIDE N DISPLAY SLIDE N DISPLAY SLIDE N The construction of the list took a while to work out, involving much consultation of the definition file (DECW$DWTDEF.PAS). One way would be to use the convenience routine VMS$Set_Arg but this is comparatively inefficient as it takes copies of all string parameters and you must remember to call VMS$Free_ArgNames after the list is used, to release memory held by these duplicate strings. It would be nice if Pascal could do the necessary initialization in a VALUE clause (like "C" can), especially of addresses, but it can't so I devised the scheme shown here. register_name register_name register_name 2. The register_name routine does the job of building the argument list, but contrary to its name, does not actually register them! rname rname rname In its formal parameter list, note how the rname parameter is var [readonly] var [readonly] var [readonly] declared. The combination of the var keyword and [readonly] attribute ensure that no temporary copy of the actual parameter is made. You will remember from the previous slide that this is required, otherwise the the argument list will be invalid by the time it is used. 3. The variant record is required because Pascal does not allow a iaddress iaddress iaddress function result (in this case iaddress) to be type-cast. An alternative would be to typecast a temporary copy of the iaddress result, which is what I did in another part of the program. 4. The names to be registered must be NUL-terminated strings. The VMS$Set_Arg routine allows strings without NULs to be registered, but only by making copies of them and appending the NULs itself. My technique avoids this overhead. The technique shown here works very well for "static" declarations, eg initializations, but might prove less useful when the name is not known in advance. I haven't tried it in that situation. Slide O: Last Stage of UIL Link-up Slide O: Last Stage of UIL Link-up Slide O: Last Stage of UIL Link-up __________________________________ __________________________________ __________________________________ __________________________________ Slide O: Last Stage of UIL Link-up The last step in setting up the linkages between the UIL file and the application is to "fetch" the widgets from the UIL file, ie to create them, which is what is happening here. Dwt$Open_Hierarchy Dwt$Open_Hierarchy Dwt$Open_Hierarchy Given the UIL hierarchy as returned by Dwt$Open_Hierarchy, the name of the widget to fetch and the parent of that widget, the Dwt$Fetch_Widget Dwt$Fetch_Widget Dwt$Fetch_Widget Dwt$Fetch_Widget routine returns the ID of the requested widget and the class to which that widget belongs. It also creates all child widgets of the fetched widget. This routine is one of those that poses no problems when using the VMS bindings. Note that the widget name must be specified in uppercase names = case_insensitive names = case_insensitive names = case_insensitive when names = case_insensitive has been specified in the UIL file. Slide P: Some Simple Callback Routines Slide P: Some Simple Callback Routines Slide P: Some Simple Callback Routines ______________________________________ ______________________________________ ______________________________________ ______________________________________ Slide P: Some Simple Callback Routines Starting at the bottom... 1. The UIL file describes three text widgets which are used in this application to enter numeric values, so the program needs to know the widget IDs of these widgets. We could have had a separate creation callback for each one, but it is simpler to share a common routine and have the UIL file identify each one as the callback is made. 2. The list box is declared seperately in the program so we have a separate callback for it. In this case the UIL does not pass any value in the callback (except the widget ID). Set_Interval Text_Value_Changed Set_Interval Text_Value_Changed Set_Interval Text_Value_Changed 3. These two routines (Set_Interval and Text_Value_Changed) demonstrate different types of callbacks, but the principle is the same. Quit Quit Quit 4. The Quit button causes an immediate program exit The program doesn't need to know what its widget ID is, only when it is activated. [global] [global] [global] The callback routines do not need to be declated with the [global] attribute because the VMS linker does not have to link them to other object modules. The REGISTER DRM NAMES routine (described earlier) does this at run time. However, the callback routines should probably be declared [asynchronous] [asynchronous] [asynchronous] [asynchronous] because they are called outside of program control, but it seems to work without. SHOW AND EXPLAIN SLIDES Q,R - UIL DECLARATIONS FOR ABOVE SHOW AND EXPLAIN SLIDES Q,R - UIL DECLARATIONS FOR ABOVE SHOW AND EXPLAIN SLIDES Q,R - UIL DECLARATIONS FOR ABOVE SHOW AND EXPLAIN SLIDES Q,R - UIL DECLARATIONS FOR ABOVE Slide S: Miscellaneous Techniques 1 Slide S: Miscellaneous Techniques 1 Slide S: Miscellaneous Techniques 1 ___________________________________ ___________________________________ ___________________________________ ___________________________________ Slide S: Miscellaneous Techniques 1 list list list 1. Here is another example of setting up an argument list. A list box items itemsCount box items itemsCount box items itemsCount box widget is erased by setting the items and itemsCount Xt$Set_Values string_address Xt$Set_Values string_address Xt$Set_Values string_address attributes to zero using Xt$Set_Values. The string_address routine used here to build the argument list returns the address of the string passed to it, using the same techniques described var [readonly] var [readonly] var [readonly] earlier (ie var and [readonly]). This is necessary because iaddress iaddress iaddress iaddress by itself won't do the job. It would be more correct to use the pre-defined constant strings Dwt$C_Nitems Dwt$C_NitemsCount Dwt$C_Nitems Dwt$C_NitemsCount Dwt$C_Nitems Dwt$C_NitemsCount Dwt$C_Nitems and Dwt$C_NitemsCount but unfortunately the definitions provided do not include a trailing NUL so it would be necessary to make a copy of them when building the argument list. In which case we might as well use VMS$Set_Arg. writev writev writev 2. Processing simple strings is very easy. The writev statement converts the numeric value to an ASCII string, which is then passed to Dwt$S_Text_Set_String to set the value for display on the screen. The whole point of these three lines of code is to Calculate Calculate Calculate make the screen look nice after the Calculate button has been pressed. 3. In this application, the actual calculation of the loan repayments is done in a work procedure because it is iterative in nature and takes a long time. Using a work procedure allows X events to be processed while calculating successive interest payments. Setting up the work procedure is messy but not difficult. In this client_data client_data client_data case I have made the client_data the same as the address of the procedure, but you would not normally do this. Xt$Add_Work_Proc Xt$Add_Work_Proc Xt$Add_Work_Proc This would have been much easier if the Xt$Add_Work_Proc routine had a formal parameter list which allowed the routine to be passed directly, like is done for AST routines when calling VMS System Services such as $QIO. Slide T: Miscellaneous Techniques 2 Slide T: Miscellaneous Techniques 2 Slide T: Miscellaneous Techniques 2 ___________________________________ ___________________________________ ___________________________________ ___________________________________ Slide T: Miscellaneous Techniques 2 The work procedure performs the calculations for one repayment interval, then updates the list box to show the latest results. A list box displays compound strings which are generated quite easily using the VMS binding. writev writev writev a) writev produces an ASCII string for display b) The ASCII string is converted to a Latin1 compound string (the most commonly used type of compound string). c) The compound string is placed at the end of the list. The "0" means "end of list"; "1" means head of list, and any other value references a specific position in the list. d) The display is updated to bring the new list entry into view. e) The list box has its own copy of the string, so we delete our copy Xt$Free Xt$Free Xt$Free using Xt$Free. Finally, the work procedure indicates whether it should be re-invoked by returning a true or false value. Slide U: General Observations and Summary Slide U: General Observations and Summary Slide U: General Observations and Summary _________________________________________ _________________________________________ _________________________________________ _________________________________________ Slide U: General Observations and Summary Many routines and structures can be used and declared in the same was in both the VMS binding and the MIT "C" binding. If this were not the case, there would not be any point at all in trying to write DECwindows programs in languages other than "C". The VMS binding generally handles string parameters quite well but is not so good when a routine has a list (ie array) of strings as a parameter. The VMS binding is just that -- an interface to the X11 Window System which does some parameter type conversion and little else. VMS$Set_Arg, VMS$Set_Callback_Arg VMS$Set_Arg, VMS$Set_Callback_Arg VMS$Set_Arg, VMS$Set_Callback_Arg The convenience routines VMS$Set_Arg, VMS$Set_Callback_Arg and VMS$Set_Desc_Arg VMS$Set_Desc_Arg VMS$Set_Desc_Arg VMS$Set_Desc_Arg work OK but suffer from two deficiencies compared to XtSetArg XtSetArg XtSetArg the MIT "C" binding XtSetArg macro: - There is a different routine for each type of argument list, XtSetArg XtSetArg XtSetArg whereas XtSetArg handles different argument types better. - The program must call VMS$Free_Argnames after the argument list has been used. DECwindows programs, like all X11 programs, are asynchronous in nature and so it is possible that compiler optimizations might cause strange bugs to appear of appropriate steps are not taken, eg declaring routines to be asynchronous and variables to be volatile, as appropriate. However, I did not find this necessary in the few simple programs I have written. BIGGEST PROBLEM BIGGEST PROBLEM BIGGEST PROBLEM The BIGGEST PROBLEM with using the VMS binding concerns the declaration of formal parameters for DECwindows routines. Some seem Xt$Add_Work_Proc Xt$Add_Work_Proc Xt$Add_Work_Proc particularly silly, eg Xt$Add_Work_Proc, but no doubt this is due in many cases to the nature of SDL (the means by which the language-specific definition files are produced). On the other hand, "C" is relatively lax about type checking and most #include #include #include DECwindows routine definitions in the #include files do not even define the formal parameters. This makes calling the routines much easier to code (but may also lead to more bugs?). Things might be different in Fortran or BASIC which are typically less stringent about formal and actual parameters. In summary: yes, it can be done. If you want to write DECwindows programs and you don't like "C" or don't have a "C" compiler you can still do the job.