<<

Paper AD10 SAS, GNU & Open Source: MinGW Development Tools and Sample Applications

Brian Fairfield-Carter, PRA International, Victoria, BC (Canada) Stephen Hunt, PRA International, Victoria, BC (Canada) Tracy Sherman, PRA International, Victoria, BC (Canada)

ABSTRACT Although many products of the Open Source initiative have been successfully adopted in the SAS world (notably , and the CVS/TortoiseCVS version control system), open source applications written by and for SAS users are somewhat under-represented. Of the 100,000+ applications currently registered on SourceForge (the world’s largest open source clearing house), a search for SAS-related applications currently yields fewer than a dozen projects.

The willingness of SAS users to freely exchange ideas and is evidenced by the popularity of SAS conferences and by the wealth of information distributed on mail lists such as SAS-L, and tends to suggest motivations similar to those driving the development and distribution of open source software (namely the desire to increase the quality of software by allowing fellow to freely read, modify and re-distribute source code). Cooperative development of SAS-specific open source applications would seem a natural extension of these same motivations.

To set the stage for open source development targeted at SAS users, and under the assumption that the majority of readers are running Windows, this paper introduces the very basics of MinGW (Minimalist GNU for Windows), a pared-down collection of Windows-specific header files and libraries combined with various other GNU tools (most importantly GCC (the GNU Collection)). It provides basic information on distribution, set-up and configuration, and illustrates practical usage via two SAS-centric Win32 applications: the first is a simple log-checker, and the second, available on SourceForge under the name “ShellOut”, is a grid control and SAS code-builder for creating table shells.

INTRODUCTION The world of open source/free software has evolved over the last twenty years or so under the central premise that better software and a stronger programming community result when source code can be freely read, modified and re- distributed. The basic idea is that bug fixes can be implemented by a potentially much larger pool of developers than under the traditional closed/proprietary model, and that because the same people often fill the role of developer and end user, features can be tailored precisely to match user requirements.

Central to the Open Source initiative are the GNU and the GNU toolset, cornerstones of the latter being the freely available and freely distributable header files and function libraries combined with the GNU Compiler Collection (GCC). These are what allow developers to build and distribute applications, and allow users to run these applications.

GNU, MINGW, AND GCC GNU The term “GNU” is a recursive acronym standing for “GNU’s Not ”, and refers to a free, open source operating system created to mirror the functionality of Unix in a non-proprietary distribution. Rather than simply being free in a monetary sense, GNU is free in the sense of allowing open access to the source code, which can be modified, studied, borrowed from, and re-distributed. What we know of today as the Linux operating system is more appropriately referred to as GNU/Linux, reflecting its composition as a combination of the GNU system and the Unix- compatible kernel Linux.

GNU/Linux and complete GNU documentation are distributed via http://www.gnu.org/. Large numbers of applications built using GNU tools are also available from http://sourceforge.net/, which is currently the world’s largest development and download repository of open source code and applications. Of particular note are TortoiseCVS, a graphical front-end to CVS (the Concurrent Versions System for version control of source code) (http://www.tortoisecvs.org/ ) (Williams, 2004) and wxWindows/wxWidgets, the class library on which TortoiseCVS is built (http://wxwindows.org/ ). MINGW AND MSYS Minimalist GNU for Windows (MinGW) is, as the name suggests, a minimal subset and Windows-specific ‘version’ of GNU, consisting of Windows-specific header files, binary utilities, and a Win32 version of GCC (the GNU Compiler

1 Collection), and which collectively provides all the functionality necessary to build Windows desktop applications.

MSYS (‘Minimal SYStem’) is essentially a command-line interface, providing a convenient portable-operating-system- styled (POSIX) environment in which to run GCC and other MinGW utilities. MinGW and MSYS can be acquired from the download page at www.mingw.org as single-archive distribution packages, in the familiar form of Win32 installation binaries. This not only streamlines installation, but also ensures compatibility between different components of the distribution. Components can also be downloaded and updated individually. GCC Apart from the kernel, which actually runs applications, probably the most important part of any development environment is the compiler – the application that translates higher-level ‘human-readable’ code into low-level executable code. In MinGW, this functionality is provided by GCC. GCC is very versatile in that it includes front ends for , C++, Objective-C, , Java and Ada, as well as standard libraries for these languages. Complete documentation to GCC is available at http://gcc.gnu.org/. PROGRAMMING LANGUAGE AND APPLICATION INTERFACE: C++/WIN32 API The two applications presented in this paper are both written in C++, and use the Windows 32-bit Application Interface (Win32 API). The Win32 API is the facility in Windows which controls user interaction with the operating system. It is what you see when you launch a desk-top application, and is responsible for drawing windows and other graphical elements on the screen, and for handling events that arise from actions in the user interface. MinGW provides access to the Win32 API by means of the ‘windows.h’ header file and associated library. These components supply a large number of functions for creating and controlling windows and the Windows Graphics Device Interface (GDI).

While C++ (essentially an object-oriented ‘extension’ of C) might seem an archaic choice, there are things to be said for it. For a start, huge numbers of applications are and continue to be written in C++; documentation and sample code are extensive. Learning C++ is a worthwhile exercise if for no other reason than that it will learning things like Perl, Java and Python much easier. If you’ve never worked with C++ and find the prospect intimidating, don’t worry – the authors were in exactly the same position a year ago! MINGW/MSYS USAGE ILLUSTRATED To illustrate the very basics of compiling and running an application built on MinGW, we’ll start with the inevitable ‘hello world’ program in c (source code assumed to have been saved as the file ‘hello.c’):

#include main (){ printf("Hello\n");}

The first line of the program ‘#include’s a header file (stdio.h) (much like the “%include” directive in SAS), which contains function ‘prototypes’ for processing standard input and output. This header file resides in the “include” subdirectory under the MinGW parent directory; the function definitions themselves exist in compiled object files (libraries) under the “lib” subdirectory. While the source code behind this function implementation is available, it is not included in the MinGW distribution since it would very rarely be necessary to re-compile these libraries from source. The only part of the ‘hello’ program that actually does anything is the call to the ‘printf’ function, which writes “Hello” (followed by a newline character) to standard output (the command interface, in this case).

To compile and run this program, we’d (1) launch MSYS and navigate to the directory containing the source file, (2) invoke GCC, passing it the name of the source file (hello.c) and specifying the name of the compiled output file (hello.exe), and (3) type the name of the compiled executable:

Figure 1: Compiling and running a simple program using MSYS/MinGW

2

In reality, an entire application is seldom compiled from a single source file. A typical Windows application will consist of at least a header file (containing function prototypes and global declarations, and which is #included by a source file) and a resource script containing menu and dialog definitions. To build such an application, the source file and resource script are compiled into object files, and linked into the executable file. This would be carried out by something like the following:

g++ -c myapp.cpp windres -o resource.o resource.rc g++ -o myapp myapp.o resource.o -mwindows

Note that the C++ compiler is invoked via ‘g++’, and that the resource script is compiled by a resource compiler (‘windres’). The third line takes the compiled object files from the previous two commands and ‘links’ them into the ‘myapp’ application. THE GNU ‘MAKE’ FACILITY The preceding example shows that with even a small number of source files, compiling each file separately and linking into an executable would serve as a serious impediment to the development process. Consider what would happen as an application grew to 50 or more source files.

Fortunately the MinGW distribution includes the GNU Make facility, an application that takes as input a user-defined ‘makefile’ (essentially a script describing how to build the application), and passes all the instructions required to build the application to the command interface. The Make facility allows the user to specify dependencies between source files, so that components are compiled and linked in the appropriate order, and can also recurs through directory trees so that neither source files nor sub-directories need to be explicitly referenced. One of the central features of Make is that it compares the timestamp on source files against that of compiled object files, and only re-compiles where the source files have changed since the last build before re-building the application.

To illustrate the very basics of the Make facility, assume we have a couple of test programs called ‘hello.c’ and ‘binary.c’. When we modify any of the source code we want to be able to recompile just the modified file using a simple command, and we also want to be able to clean out all the object and executable files via a simple command. To do this, we’d create a Makefile like this:

all : hello binary hello : hello.o gcc -o hello hello.o hello.o: hello.c gcc -c hello.c binary : binary.o gcc -o binary binary.o binary.o: binary.c gcc -c binary.c clean: rm hello.o hello.exe binary.o binary.exe

Each set of statements is referred to as a ‘target’. In effect, the ‘all :’ target says “build ‘hello’ and ‘binary’”; the ‘hello :’ target in turn says “build the object file ‘hello.o’, and then use gcc to link it into an executable file ‘hello.exe’”; the ‘hello.o :’ target says “compile the source file ‘hello.c’ into an object file, using gcc”. The ‘clean :’ target simply removes all the specified object and executable files. Commands such as “gcc –c hello.c” are identified to the ‘make’ facility by means of a preceding tab character.

This example is not programmed particularly efficiently, since it hard-codes each file reference rather than making use of wild-cards or other devices, but it does allow you to re-build all modified source code simply by running the command ‘make’ in MSYS, or to call specific targets by running ‘make hello’, ‘make clean’, etc.

Complete documentation to GNU Make is available from http://www.gnu.org/software/make/. As you might imagine, writing a ‘makefile’ for a large and complicated project can be a serious undertaking in of itself. Fortunately there are utilities available to generate makefiles (for example, see ‘Bakefile’ at http://bakefile.sourceforge.net/).

SAMPLE APPLICATIONS

‘CHECKLOG’: A VISUAL LOG-CHECKER This application is not particularly novel or sophisticated, meaning it serves as a good learning tool and as a starting point for further modification. As the name implies, this application simply parses SAS log files for ‘error’ and 3 ‘warning’ messages, and displays these in color-coded windows (figure 2). To provide further illustrative value, it also functions as a ‘console’ application if run from MSYS, since it also writes summary information to ‘standard output’.

Figure 2: A simple Win32 application for parsing SAS log files, built using MinGW BUILDING AND RUNNING THE APPLICATION Source code is provided as part of the distribution package for “ShellOut” (described below) from the ShellOut SourceForge project website http://sourceforge.net/projects/shellout. The source file is called “checklog.cpp”, and can be compiled in MSYS/MinGW via the statement: g++ -mwindows checklog.cpp -o checklog.exe The application can be called by typing the name of the executable followed by the target log file into the MSYS command interface: Checklog test.log This will create a display such as that in figure 2, and will also write summary log information back to the command interface.

The application is more conveniently called is as a Windows shell extension, where a context menu in Windows Explorer associated with a particular file extension (i.e. “.log”) submits the command string (the name of the executable plus the argument list).

Figure 3: CheckLog called as a Windows shell extension

Registry keys for the ‘Checklog’ context menu and associated command can be added to the system registry by means of a registry script:

[HKEY_CLASSES_ROOT\SAS.OutputLog.701\shell\CheckLog] [HKEY_CLASSES_ROOT\SAS.OutputLog.701\shell\CheckLog\command] @="c:\\sandbox\\sasapps\\checklog\\checklog.exe \"%1\""

‘SHELLOUT’: A GRID CONTROL AND CODE-BUILDER FOR TABLE SHELLS Dynamic code generation is based on the generalized rules or abstractions that can be distilled from virtually any piece of code. In clinical/analysis programming, one of the more predictable (and therefore tedious) tasks is that of creating SAS code to generate table shells, and it is the predictable and repetitive nature of this task that makes it an ideal candidate for dynamic code generation.

4

Dynamic code generation is a process commonly driven through a graphical user interface: events in the user interface are captured, and translated through an underlying set of rules to produce source code. This is precisely what happens when you record a macro in Word or Excel.

ShellOut is a ‘visual code-builder’ for creating table shells: it is ‘visual’ in that it uses a graphical interface (once again, implemented in C++ on the Win32 API), and it is a ‘code-builder’ in that it dynamically generates SAS code. In essence the application consists of a simple grid control which allows the user to create a table mock-up through a point-and-click interface. A code-writing function then takes this table mock-up and translates the grid parameters (rows, columns, relative column widths, column header definitions and dummy data, all of which are stored as XML) into SAS/ODS code, which in turn produces a table shell as an rtf document. ANALYSIS TABLES AND TABLE SHELLS A ‘table’ is fairly easy to describe in abstract terms. It consists of rows and columns, column headings, and data (content). When you program a table using PROC REPORT, you tend to see some key elements: a DATA statement specifying the content source, a COLUMN statement specifying columns to display, and DEFINE statements, specifying column headings and column widths. It is a fairly simple task to take the data defining a table and translate it into table-generating instructions to be consumed by another application. This is what happens when an Excel file is stored as XML which is then rendered in a web browser. Extending this concept to SAS, it is easy to see how the same XML file might be translated in a set of PROC REPORT statements which would in turn (via ODS) produce an rtf table (of course, the rtf code is nothing more than another set of instructions to be consumed by an application such as Word).

A statistical analysis plan in a clinical trial usually contains table shells, or mock-ups, of the planned analysis tables. Using SAS to create these shells is useful in two regards: first, it prevents table design from including elements that can not be programmed in SAS, and second, a modest amount of efficiency may be gained by re-using the table- generating code in the actual analysis programs.

Table shell programming consists of three steps: 1. Generation of ‘dummy data’

data temp; col_1="N"; col_2="xx"; output; col_1="Mean"; col_2="xx.xx"; output; ... run;

2. Generation of tabular output

proc report data=temp nowd; column ...; define ...; run;

3. Conversion of output to word-processor-ready document, either directly, through ODS/rtf (2, below), or indirectly, by re-directing SAS listing output using PROC PRINTTO (1, below), and then using a ‘helper’ application (for example, using VBScript/Windows Scripting Host (3, below; see Hunt et al, 2005) or VBA/Word) to automate File/Open, formatting, and File/Save operations.

proc printto file="c:\listing_output.lst" new; *** (1); run; ods rtf file="c:\ods_output.rtf" style=mystyle; *** (2);

proc report data=temp nowd; column ...; define ...; run;

ods rtf close; proc printto; run;

x "c:\lst_to_rtf.vbs c:\listing_output.lst"; *** (3);

5 The objective is ultimately to produce an rtf table that looks something like this:

Group 1 Group 2 Active treatment Placebo Active Treatment Placebo N xx Xx Xx Xx Mean xx.xx xx.xx xx.xx xx.xx Min xx.x xx.x xx.x xx.x Max xx.x xx.x xx.x xx.x

The ODS style “mystyle” (2, above) that you create (using PROC TEMPLATE) will depend on the style requirements for the analysis tables; the same is true of the functionality you build into a script for SAS listing –> rtf conversion (3, above). Once one or the other of these is created, the code to create the table shell is based on three things: the number and relative widths of columns, the required column headings, and the table content. The application presented here provides the first of these by means of a ‘grid control’ (essentially a very simple spread-sheet), where the number and relative widths of columns can be retrieved by enumerating and retrieving screen coordinates of ‘child windows’ that make up the grid. Column headings and table content are treated identically, with the assumption that anything in the first row in the grid control will be used to define column headings in the SAS output (including simple rules for defining spanning column headers), and anything in subsequent rows will be table ‘content’. To minimize typing, the grid control includes a set of ‘context menus’ to automatically populate rows with dummy data. THE APPLICATION As is typical of Windows applications, this application consists of a ‘main’ window, a number of ‘child’ windows (edit controls, buttons, etc.), and some ‘resources’ (drop-down menus and dialogs). Events such as button clicks and menu selections are handled by means of window and dialog procedures. THE GRID CONTROL The central component of the application is the grid control. This is the point-and-click interface where the table is mocked up, and from which information on column widths and table content is extracted, providing all the information necessary to dynamically generate SAS code for the table shell.

The grid control provides some basic spread-sheet functionality: it allows the user to create a blank grid with a specified number of rows and columns, and to insert and delete rows and columns; it allows the user to adjust column widths, to scroll the pane containing the grid when the grid exceeds the dimensions of the screen, to toggle between cells via a hot-key, to enter data, both directly into the cells, and by using context menus to auto-fill sections of the grid with pre-set types of dummy data. The application allows the user to save and re-open data files in order to preserve the exact characteristics and content of a specific table mock-up.

Since XML is rapidly becoming the universal standard for data interchange, it was adopted as the ‘native data type’ for the grid component of the application. The XML document is modeled after that used by Excel, since in common with Excel it is necessary to store table content in terms of rows and cells:

Mean xx.xx xx.xx

, and it is also necessary to store information on column widths:

...(etc.)...

Adoption of this format has the additional advantage that data created in Excel can be opened in the grid control, and

6 vice versa.

The grid is implemented as an array of re-sizeable, multi-line edit controls (‘EDIT’ is a Windows-defined class possessing basic text-editing features such as are found in the Windows Notepad application), each of which is a child window:

int CreateGrid(int rows, int cols) { for(int row=0;row

So that the grid scrolls inside a pane or ‘clipping region’, cells are actually child windows to a static window which is itself a child window to the ‘main’ application window:

hWnd_ = CreateWindow("STATIC", "", WS_CHILD | WS_VISIBLE, 20, 24, 969, 335, hWnd, (HMENU) 9, hInstance, NULL); SCROLLING AND COLUMN RE-SIZING IN THE GRID CONTROL: ENUMERATING CHILD WINDOWS In order to mock up a table in the grid control, two simple features need to be supported. It must be possible to adjust column widths, and in the event that a grid exceeds the dimensions of the static ‘clipping’ window, it must be possible to scroll the grid.

The first of these features takes advantage of the WS_SIZEBOX attribute assigned to all grid cells, which allows a window to be re-sized (vertically and horizontally) by means of a click-and-drag action with the mouse:

Figure 4: Re-sizing a column by clicking and dragging the right edge of a cell.

When the mouse button is released, cells in the same column as the one just re-sized are themselves re-sized to the same width, and cells in columns to the right are shifted by the amount of the re-size.

The extent to which the rest of the cells are re-sized or repositioned is calculated based on the difference between the starting and ending dimensions of the re-sized cell. These dimensions are captured by handling the WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE messages in a window ‘subclass’ procedure (in effect, messages that originate in a child window are sent to the main window procedure (‘WndProc’) and are then re-directed to the subclass procedure).

7

This re-size/move action is carried out across all cells in a grid by means of the Win32 EnumChildWindows function, which iteratively passes a handle to each child window within a specified ‘client area’ (in this case the rectangular area bounding the static window) to an application-defined function (in this case EnumChildProcMove).

EnumChildWindows(hWnd_, EnumChildProcMove, (LPARAM) &rcClient_);

EnumChildProcMove calls the Win32 SetWindowPos function to re-size or move the window according to its relationship to the ‘focal’ cell as determined from the window’s unique ID. SetWindowPos essentially re-draws the target window using starting coordinates specified as ‘client coordinates’ (as in, treating the top left corner of the static window as point (0,0)), and window dimensions specified in pixels.

BOOL CALLBACK EnumChildProcMove(HWND hwndChild, LPARAM lParam) { ...(etc.)... SetWindowPos(hwndChild,HWND_BOTTOM, rcNudged.left -22 - lpPointhWndUL.x/2 +iShiftRight, ((iRow+1)*60 - 60) + iScrollYCumulative, rcNudged.right-(rcNudged.left-20)-20,60, SWP_NOACTIVATE); ...(etc.)... ShowWindow(hwndChild, SW_SHOW); return TRUE; }

A similar technique is used to scroll the grid. Clicking a scroll bar sends a WM_HSCROLL message, which is handled by passing the name of the appropriate application-defined function to EnumChildWindows. CONTEXT MENUS The last spreadsheet-like feature that the grid control needs is the ability to insert and delete rows and columns. For convenience, and to keep the interface as -free as possible, this feature is implemented via context menus (pop-up menus displayed when you right-click):

Figure 5: Selecting ‘Insert’ in the top row inserts a column

By convention, when the right-click occurs in the first row of the grid, a column is inserted (to the right of the current column) or deleted (Figure 5); when the right-click occurs in any other row, a row is inserted (above the current row) or deleted (Figure 6).

Figure 6: Selecting ‘Insert’ in any row other than the top row inserts a row

8

Similar to cell re-sizing, context menus are displayed by handling a message (in this case WM_CONTEXTMENU) passed to the window sub-class procedure:

case WM_CONTEXTMENU: AppendMenu(hgridmenu, MF_STRING, ID_POP_INSERT, "Insert"); AppendMenu(hgridmenu, MF_STRING, ID_POP_DELETE, "Delete");

When a menu item is selected, a WM_COMMAND message is passed to the sub-class procedure, and an ‘insert’ or ‘delete’ action is carried out based on the ID of the menu item:

case WM_COMMAND: switch(LOWORD(wParam)) { case ID_POP_INSERT:

Context menus also provide a convenient way of populating grid cells with dummy data. As with ‘insert’ and ‘delete’, dummy data types are displayed in the context menu via the Win32 AppendMenu function:

AppendMenu(hgridmenu, MF_STRING, ID_POP_DATA1, "xx"); AppendMenu(hgridmenu, MF_STRING, ID_POP_DATA2, "xx (xx.x)");

The WM_COMMAND ‘case’ statement is then extended to handle these new menu items; in the case of ID_POP_DATA1, the function fills the target cells, and all cells to the right of it sharing the same row, with the text “xx”, and for ID_POP_DATA2 does the same with the text “xx (xx.x)”:

Figure 7: Context menus for entering dummy data

Dummy data is displayed by means of a small application-defined function ‘DummyData’, which loops through all cells including and to the right of the target cell, creates a handle to each cell in turn via the Win32 GetDlgItem function, and passes this handle, along with a WM_SETTEXT message and a pointer to the text string to display, to the Win32 SendMessage function. The WM_SETTEXT message is essentially an instruction to the application interface to display the specified text in the specified edit control. THE HEADER-DEFINITION ‘TABLET’ Column headers can be defined simply by typing into the top row of the grid, but in many cases this will result in a lot of repetitive typing. One way to cope with this is simply to save multiple copies of a grid under different file names. As an additional short-cut, the application includes a header-definition ‘tablet’, which is just an edit control where column headings can be mocked up, but which causes the context menu to display an additional menu item when the tablet contains text (this menu item does not appear if the tablet is blank):

9

Figure 8: Content in the header ‘tablet’ activates a context menu which inserts header definitions

When the ‘Header’ menu item is selected, the contents of the tablet are transferred to the corresponding grid cells, and eventually become column header definitions in the PROC REPORT statements. If spanned headers have been mocked up (as is the case in the example above), a simple short-hand is used to represent the two levels within a single grid row. The flag ‘sp’ indicates the start of a spanning header; ‘sp2’ indicates that the header spans 2 columns. ‘Header 1’ is the text of the spanning header, and ‘Column 2’ is the text of the first spanned column heading. This is ultimately translated into the following SAS code:

column v1 ("Header 1" v2 v3) v4 ("Header 2" v5 v6) ; define v1 / "Column 1" center display style(column)=[cellwidth=225]; ...(etc.)...

To store and manage data created in the tablet, a few controls have been added:

Figure 9: Header tablet controls

The ‘S’ button saves the contents of the tablet to a binary file. Binary format is used since multiple column header mock-ups can be stored as separate records in the same file, and binary provides more rapid access to the content of individual records. Records can be scrolled through by means of the arrow buttons, or via the mouse wheel. The ‘O’ button opens the first record, and the ‘C’ button clears the tablet. THE SAS WINDOW Apart from the drop-down menus, the last major component of the interface is the edit control to display generated SAS code (data step code to create dummy data, and PROC REPORT code):

Figure 10: Edit control displaying dynamically generated PROC REPORT code

This window also has a few controls associated with it: the ‘B’ button builds the SAS code to generate dummy data and create report output, writes this code to a file and also displays it in the edit control; the ‘R’ button runs the generated SAS code, and the ‘V’ button opens the output from the SAS run.

The SAS code is written by parsing the XML file containing the grid data, and from this determining data values, column widths, and column headings. By convention, the SAS file is created in the same location as the XML file,

10 and using the same file name but with a ‘.sas’ extension. The ‘V’ (view) button assumes that the SAS output is also directed to the same location and uses the same file name. DROP-DOWN MENUS AND DIALOGS Drop-down menus give the interface the look and feel of a typical Windows application. ‘File/Open’ and ‘File/Save’ menus were created to call the standard Windows ‘Open’ and ‘Save As’ dialogs (via the Win32 GetOpenFileName and GetSaveFileName functions) (figure 11A,B). Shellout is also built to allow an XML file to be opened from Windows Explorer by right-clicking and selecting “Open With/Shellout”. A File/New menu was created to allow the user to specify the number of rows and columns when creating a new blank grid (figure 11C). Menus were also added corresponding to the ‘B’, ‘R’ and ‘V’ buttons associated with the SAS window (figure 11D). Finally, menus and dialogs were created to allow greater convenience in setting and displaying column widths. The ‘Set Column Width’ dialog (figure 11E) indicates the column number and width of the last cell selected, and allows the user to set the width equal to the left-adjacent column or to a specified column, or to a specific width represented in pixels. The ‘Relative Column Widths’ dialog (figure 11F) simply gives the absolute widths in pixels and the relative widths as percentages for each column.

A B

C D

E F

Figure 11: Drop-down menus and dialogs

Drop-down menus and dialogs are defined in a resource script (this script is compiled using the GCC resource compiler): #include #include "resource.h" IDR_MAINMENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&Open", ID_FILE_OPEN MENUITEM "&Save", ID_FILE_SAVE MENUITEM "&New", ID_FILE_NEW END ...(etc.)... END

Dialogs are launched when a menu item is selected, causing a WM_COMMAND message to be passed to the main window procedure. A parameter passed along with the message is parsed to determine which dialog box to launch,

11 and actions taken in the dialog box are interpreted based on the return value from a call to the Win32 ‘DialogBox’ function. ACQUIRING AND INSTALLING THE APPLICATION, AND BUILDING FROM SOURCE Source code and application executable can both be acquired in a simple Zip archive from the ShellOut project website on SourceForge: http://sourceforge.net/projects/shellout. The executable file can be placed basically anywhere (i.e. “c:\program files\shellout”); no install scripts are currently provided, though this may change. The distribution archive also contains a SAS file called ‘mtemplate.sas’, which defines a SAS/ODS template called ‘mystyle’. Shellout currently assumes that this file is stored in the “c:\” root directory, since the following statements are provided in the generated SAS code:

%include "c:\mtemplate.sas";%mtemplate; ods rtf file='C:\_sandbox\SASApps\Grid\test.rtf' style=mystyle;

The simplest way to build the application from source is by using the makefile provided (in MSYS, simply navigate to the directory containing the source code, and type “make”). The source file and resource script can of course also be compiled individually and then linked.

CONCLUSION While this paper barely scratched the surface of GNU tools, the potential these tools offer for writing useful SAS applications should be apparent. From the modest beginnings presented here, hopefully the reader will be motivated to further investigate the possibilities offered by GNU and open source, and perhaps to contribute to a larger body of SAS-specific applications for distribution on SourceForge.

REFERENCES Hunt, Stephen, Sherman, Tracy, and Fairfield-Carter, Brian (2005), ‘An Introduction to SAS® Applications of the Windows Scripting Host’, Proceedings of the 30th Annual SAS Users Group International Conference.

Williams, Tim (2004), ‘Version Control on the Cheap: A User-Friendly, Cost-Effective Revision Control System for SAS®’, Proceedings of the 2004 Pharmaceutical Industry SAS Users Group Conference.

ACKNOWLEDGMENTS The authors would like to thank Tim Williams for introducing us to the world of open source, and for his continual support of creativity and innovation.

CONTACT INFORMATION Your comments and questions are valued and encouraged. Contact the authors at: Brian Fairfield-Carter, Stephen Hunt, Tracy Sherman PRA International 300-730 View Street Victoria, BC, Canada V8W 3Y7

Email: [email protected], [email protected], [email protected]

Web: www.prainternational.com

SAS and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA registration. Other brand and product names are trademarks of their respective companies.

12