Authors
Important Copyright Information
1. Introduction
The lack of convenient bindings has traditionally been a hindrance to the use of Ada. Microsoft Windows has now become the world's most popular operating system. Ada compilers targeted to Microsoft Windows 95/NT now typically come with a thin binding to the Win32 API [2, 11]. (Win32 is Microsoft's name for the Application Program Interface for Windows NT and Windows 95). At least one vendor offers a thin binding to MFC [11], and there is a binding to Tcl/Tk which is portable across multiple GUIs [9]. We believe there is a need for a compiler independent binding that is friendly to Ada programmers, that uses Ada 95 features to raise the abstraction level above Win32, and yet allows the resulting applications to have the look and feel of the Windows applications expected by the end user.
We have designed and implemented CLAW as a high-level interface for Win32. It uses Ada 95 style and features throughout, and is portable to any Ada 95 compiler. The interface defines tasking safe, object-oriented, interfaces. It can be used to construct a complete GUI application, without referencing any non-Ada code or documentation.
Since CLAW is object-oriented, the CLAW project has been engaged in a large-scale object-oriented design and implementation effort using Ada 95. In general, our experience with object-oriented programming in Ada 95 has been positive, but some problems have arisen. Many of our experiences with CLAW are also applicable to other, non-Windows, development efforts.
2. Bindings
2.1 Thin versus Thick
Most of the existing Ada interfaces for Microsoft Windows are thin bindings. Thin bindings are relatively direct implementations of the underlying C interface. They typically preserve the original C names for types and subprograms, and usually use little or no code to implement the interface.
Thin bindings have some obvious advantages. They are relatively easy to create and easy to modify when Microsoft modifies the Win32 API. Their creators can simply refer users to the mass of existing literature on how to do Windows programming in C, rather than making the expensive effort to document their binding. Thin bindings also can be efficient, as they add the least possible overhead between the application programmer and the Win32 API. Ease of creation has led in the past to multiple, slightly different, compiler dependent, thin bindings. In addition, the implementors of some bindings use nifty features of their own compiler, thus preventing the bindings from being used with another compiler.
Since thin bindings typically include little or no documentation of their own, programmers must reference the original Windows documents or various "Programming Windows" type books, which are very C-oriented. This literature often contains difficult to understand C code, difficult to translate C idioms, code snippets that scale up into very poor programming styles, and task-unsafe techniques. Also, the experienced Ada software engineer is deprived of his tools and forced instead to start not ahead, but behind, the C programmer in understanding the references and examples.
A binding that is thick to the point of being OS-independent also has advantages and disadvantages. It can be easier to learn, hides most of the dangerous and changeable details, and raises the abstraction level to a more coherent, usable, and powerful, set of components. Necessarily, however, it cannot take advantage of Windows-specific characteristics that users, and thus application programmers, want. The Java class libraries exemplify some of the advantages, and disadvantages, of an OS-independent system.
A moderately thick binding has most of the advantages of the platform independent binding, while still allowing the programmer to write applications using all of Windows' capabilities and having the familiar Windows look and feel. It also has the advantage of extensibility to new APIs and provides the application programmer a way to get at the underlying Windows data and API for those occasions when "bare metal" programming is the only way to do the job. CLAW is such a binding.
2.2 Inter-Compiler Portability
One of the main goals for CLAW was portability across Ada compilers targeted to Win32.
This portability reduces project risk for users since they aren't locked into one compiler, allowing them to choose based on the many other dimensions of compiler desirability. Considering the maturity range of compilers, the optional nature of the Annexes, the price range of different compilers and support, and the differences in programming support environments and toolsets, this is a significant consideration.
Such portability requires CLAW to use only standard Ada 95 features. It cannot depend on features of a particular compiler, no matter how useful. This requirement implies that no C++ interfacing be used, as there is no standard interface to C++. That, in turn, determined that CLAW must be a binding directly to Win32, and not to the Microsoft Foundation Classes. Binding directly to Win32 also has the advantage of requiring no additional software beyond the Ada 95 compiler, thus reducing licensing headaches for vendors and users alike.
The only absolutely compiler-dependent features used by CLAW have to do with access to Windows' start-up parameters, and the Convention names for access to Win32 API routines. CLAW requires access to the start-up parameters at elaboration of the program. Compilers provide these parameter values in different ways (typically being some function or functions). We encapsulated the start-up parameter routines in a single low level private child package, where the implementation differs between compilers.
Microsoft has defined several different calling conventions for programs running on Windows NT and Windows 95. The C compiler uses a convention called "Cdecl", which Ada 95 compilers map to convention C. However, the Win32 APIs use a different convention called "Stdcall". Ada 95 compilers could map this to any convention name. To date, all of the compilers we have tested use the name "Stdcall" as the convention. Since the convention in question is not "C", compiler vendors could follow rules other than those mentioned in the Ada Reference Manual when implementing it for details such as parameter passing. So far, all of the compilers tested have followed the Ada 95 "C" convention rules for implementing "Stdcall". CLAW avoids the well-known record by-value versus by-reference problem in Ada 95 by always passing by-value records as separate components. Because of these concerns, all interface routines are declared either in private packages or in package bodies, where they could b changed if necessary.
CLAW uses representation clauses and package Interfaces to ensure the correct data representations. Every type which will be passed to Win32 is either derived from a type in package Interfaces.C, or has (or is derived from a type with) a representation clause. We do not trust compiler layout of record components, for example, because the consequences of an error are likely to be severe and difficult to find.
CLAW internalizes the message loop and hides (major) differences in task/thread mapping from the programmer. Ada compilers can make one of two choices for mapping of Ada tasks to Windows threads. A multi-threaded implementation maps one task to one thread. This makes better use of the processor, but is likely to be slower than a single threaded implementation, and will not work at all on Win32s. (Win32s is a subset of Win32 which allows Win32 programs to run on Windows 3.1. Win32s is similar to Windows NT 3.5, without threading or console support.) A single-threaded implementation maps all Ada tasks to a single thread, which is likely to be faster and work in Win32s, but it is susceptible to blocking. Both kinds of tasking are available in commercial Windows compilers, and both have impacts on CLAW. CLAW is designed to work in both environments. In a single thread implementation an Ada task that goes into a tight loop will consume all system resources. To improve portability, C w provides a "Yield" routine which guarantees to allow other tasks an opportunity to run.
As a result of these decisions and restrictions, CLAW is very portable. For CLAW 1.0, only two package bodies differ between compiler implementations; all other source is identical.
2.3 Other Windows Targets
CLAW was designed and implemented for Win32 systems running on Intel Pentium class processors. Since CLAW is a thick binding, it potentially can support other versions of Windows. We have implemented support for one such version - Win32s. Win32s support in CLAW primarily takes the form of additional "Not_Supported" checks on some functions.
Since the CLAW implementation started, Microsoft has released Windows CE, a version of Windows designed for hand-held computers. We have investigated this version, and believe CLAW could be ported to Windows CE without too severe an impact on CLAW programs, as soon as a Windows CE Ada compiler becomes available.
Finally, CLAW could be used on Windows versions for other processors (for instance, the DEC Alpha). Doing so would require adjusting some of the basic type declarations, but again most CLAW programs would not be affected by the changes.
3. Windows
3.1 Mapping Windows' Organization to CLAW
Windows programs are classically arranged as a set of Interrupt Service Routines called by an event polling loop in the main program. Each ISR should ideally be able to complete its processing in under 1/10 second [7, p. 741] unless the user will naturally expect an operation to take longer (e.g. opening a file). If the ISR will take longer, it displays an hourglass to indicate that interrupts are locked out. In addition to this simple, single-threaded, non-preemptive multitasking, Windows 95/NT adds true preemptive multi-threaded multitasking. Each thread has zero or more associated windows and a single event queue and polling loop dealing with those windows. CLAW has a single internal Ada task that runs the event polling loop and calls (via dispatching) the ISR action routines. Conceptually, then, a CLAW application has one thread dedicated to user input/output and a separate task (simply the main program task except in complex systems) dedicated to time consuming computation
file IO, etc. (An example of a CLAW application program can be found in an appendix to this paper.) This division of labor normally simplifies system design and leads to fewer appearances of the dreaded hourglass.
|
A window may itself contain "child" windows. In fact the standard control windows made available by the system (dialog boxes, edit boxes, etc.) are normally child windows. Child here refers to on-screen position and control characteristics, not to the Object Oriented sense of the word. Children in the sense of OO derived types are also extremely important. Thus a dialog box "is a" window, with behavior partially inherited from, and partially added to, a simpler kind of window. A dialog box on screen is also usually, but not always, contained in the boundaries of an on-screen parent window. CLAW defines an abstract, non-limited, controlled, Root_Window_Type and different CLAW modules define a tree of its more complex descendants.
While a window is a long lasting conceptual entity, it's on-screen presence may change in size, including shrinking to nothing; thus a program cannot actually draw anything in a window. The program draws instead to a "Device Context" which may be the area of the screen inside a window, a non-displayed area of RAM, or a logical sheet of printer paper. These typically take a substantial amount of memory, including, in Windows 95, a significant amount of the limited "system resources' memory, so they are normally created, drawn upon, displayed, and destroyed on the fly. CLAW defines an abstract, limited, controlled Root_Canvas_Type for this drawing object as well as several concrete descendants with appropriate characteristics. Pens, brushes, fonts, bitmap images, regions, and palettes are windows objects used as drawing tools. They are handled similarly, but not identically, by Win32. CLAW defines an abstract, non-limited, controlled Root_Tool_Type, from which different modules derive the specific tools. |
| ||||||||||||||||
Menus are considered objects by Windows. They can be created, modified, attached to one or more windows, and destroyed. There are rules governing when changes actually appear on screen, so the same menu could possibly be used by (and appear differently in) two different on screen windows. Menus that may be in use should not be destroyed. (We considered dispensing with the notion of a stand alone Menu object, considering it instead part of an extension of a window type, but decided instead to follow, as closely but reliably as possible, the Win32 conceptualization.) CLAW defines an abstract, non-limited, controlled Root_Menu_Type with menu bar, submenu, etc. as concrete child types.
The organization of most CLAW tagged types can be seen in the diagrams. Abstract types are denoted by italics; objects cannot be declared of abstract types. Types are connected by derivation as shown. CLAW also contains some types which do not belong to any derivation tree (for example, Icon_Type). These types have been omitted from the diagrams.
3.2 Simplifying Windows through Ada
The Win32 API inherits many of the well-known difficulties of C. In addition, multitasking is provided both by the Windows 3.1 event loop and by the OS level preemptive multitasking in Windows 95 and NT. Win32 was designed by many people over a long period of time, and therefore is very inconsistent in structure. There are multiple ways to implement the same operation. Win32 has many obsolete routines, and its parameters, results, and error handling are inconsistent.
The most important goal of CLAW is to simplify the use of Windows by taking advantage of the many Ada features which help improve reliability and consistency of the interface. We also wanted to take advantage of the new object-oriented features in Ada 95 to make it easier for the programmer to construct reusable custom GUI elements such as windows, tools, and menus.
Another important goal of CLAW is to provide an interface with which an Ada programmer is comfortable. Ada programmers who must use a direct binding to Win32 or another C interface find that many characteristics of these bindings are unfamiliar. They expect that their bindings detect and report all errors automatically, at compile-time if possible, and otherwise by raising exceptions. Ada programmers expect readable, descriptive names with few abbreviations. Ada programmers expect bindings to be organized into sets of related functions. None of these characteristics are found in Win32.
| ||||||||||||||||
3.3 Naming and Terminology
To provide an interface familiar to Ada programmers we needed a consistent naming throughout CLAW. The naming should be natural to an Ada programmer. Entity names should be simple, easily distinguishable, and easy to remember. Because of its importance, we spent considerable effort refining a set of rules to apply to naming in CLAW.
The primary rule is that all names in CLAW will consist of complete words separated by underscores. We do not allow the use of abbreviations or Hungarian notation in CLAW's names. Names are intended to be readable, pronounceable, and mnemonic. In addition, name should be readable whether or not the programmer employs use clauses. We accomplish this by insuring that names of most entities can stand alone.
Names should not contain redundancies, particularly when used as complete expanded names. One important way to accomplish this is to avoid putting the object type into operation names. For example, Windows operations CreateWindowEx and LoadBitmap are called Create and Load, respectively. The object type will still be determinable by (hopefully) well-chosen user objects, complete expanded names (Claw.Frame_Window.Create and Claw.Bitmap.Load for our example), and named parameter notation (a possible call is Create (Window => App);). Users who typically follow these Ada software engineering conventions have plenty of information as to which operations are being called.
We also defined some suffixes for common entities. We determined that all type names should end with "_Type". Exception names should end with "_Error". The rule for type names makes naming packages easier: for example, we have package Claw.Static, and the type within it is Static_Type. We have found that these suffixes quickly become second nature, and names that don't end with them seem incorrect (such as the Ada predefined type "Positive").
We do not consider these rules immutable. We will violate any of them with a sufficiently good reason. For instance, the low-level types generally have the original Win32 names. (For example, HWnd rather than Window_Handle_Type). This was done so that Win32 calls can be constructed as closely as possible to the original Microsoft documentation. Such exceptions should be, and are, rare.
One disappointment in naming was our inability to come up with a satisfactory short name for object-oriented types ("classes" in object-oriented terminology). Our original plan was to provide an alternative short name for all of the concrete classes (those that objects can be declared of), so that programmers who typically use the fully expanded name could avoid the redundancy. However, we were unable to settle on a useful short name. Everyone's first choice, "Type", couldn't be used because it is reserved. "Object" was rejected as implying something other than a type. "Class" was rejected because of potential confusion with Ada 95's classwide attribute (imagine "Class'Class" filling your programs!). The best choice considered was "Object_Type". Ultimately, however, we decided not to use "Object_Type", because it was not significantly shorter than many of the types it was intended to replace. For instance, "Claw.Static.Static_Type" could have been replaced by "Claw.Static.Objec Type", which hardly seems worth the effort. So we reluctantly abandoned the idea of alternative short type names.
CLAW uses Ada 95 child packages to avoid (library) name space pollution. All CLAW packages are children of the root CLAW package "CLAW". One result of this is that the fully expanded name of any CLAW entity starts with "CLAW". This makes it easy to differentiate CLAW defined and user defined entities.
Another way that CLAW simplifies names is by adopting a simpler, consistent terminology. The original Win32 terminology has been changed several times, such that some entities have as many as four names! In addition, some of the names would violate our "no abbreviation" rules. Therefore, CLAW renames some Windows concepts. For instance, in Win32, a "DC (device context)" is what drawing or writing is done on. The term "DC" certainly doesn't imply that, so we have adopted "canvas" as the name of that class of objects. A"GDI object" is used for drawing - we renamed these as "tools". In CLAW you use a tool to draw on a canvas. We hope that these sorts of terminology improvements will make it easier to grasp what is going on when programming for Windows.
The downside, of course, to adopting a new and somewhat unique terminology is more difficulty in mapping between the original Windows terminology and CLAW's terminology. We intend to mitigate this problem simply by doing our best so that users don't need to do that mapping. We hope that the extensive documentation provided with CLAW will mean that users will rarely need to look outside of CLAW for information.
3.4 Functionality and Performance
We decided early on that CLAW would have several layers of functionality. By having several layers, we can avoid the "one-size-fits-all" (which never really does) problem.
The lowest level is the Win32 interface. Since CLAW is not a Win32 binding, we placed this interface into private child packages of CLAW. That makes the Win32 interface available to all of CLAW, but not to the user of CLAW. There are two reasons for making this interface private. First, differences between Ada compilers are most likely to appear in the interfacing code. By keeping the interface private, the differences can be handled without upsetting the CLAW user view. Second, we do not want to create a complete (in any sense) Win32 binding. The Win32 interface packages contain only functions that CLAW calls. Win32 calls outside of CLAW are handled through the usual pragmas and libraries provided with the programmer's compiler.
We considered using a standard Win32 binding for CLAW, but rejected this for two reasons. First, such bindings are considerably larger than the binding CLAW uses, since they are complete. Since some compilers may not do a good job of removing unused bindings, we needed to reduce the risk of carrying along more baggage than necessary. Experiments with CLAW sample programs show as much as a 2.5 times size difference on the same program with different compilers.Second, we wanted to be able to use CLAW interfacing types where necessary. In some cases, the CLAW type (Rectangle_Type, for instance) can be passed directly to Win32. We didn't want the overhead of additional conversions in CLAW.
The next level of functionality is the basic CLAW types. In general, these are relatively close to the underlying Win32 objects. We want these types to provide most of the underlying Win32 functionality, omitting only that which is clearly redundant or not appropriate. Basic CLAW types always provide a way to access the original Win32 object (handle). This capability is provided so that users can pass objects directly to Win32, in order to use some functionality that CLAW doesn't provide, and also so that CLAW can be used in mixed-language programs, passing handles to foreign modules as needed. Finally, all CLAW objects are designed to clean up after themselves when they are finalized. Objects are unhooked from any internal CLAW data structures, and any Windows resources owned by the object are freed. This automated cleanup not only makes CLAW programs easier to write, but also contributes to CLAW's stability. Pointers to objects which no longer exist could cause CLAW to hang crash; proper finalization avoids such problems.
The top level of functionality is high level CLAW types. These generally are constructed out of combinations of basic types. These types provide easy to use abstractions for various purposes. In this case, we only want to provide operations which are clearly useful for the abstraction. Other operations are omitted. A simple example of such a type is the Radio_Button_Set type which provides an abstraction for a set of radio buttons. While it is a powerful abstraction, it supports only the most used subset of the functionality of the basic radio button. For instance, it supports only text radio buttons (bitmaps and owner-drawn buttons aren't supported) and operations are only on the complete set (colors, fonts, and so forth cannot be set individually). We expect many Ada programmers to primarily use these abstractions, as they will not have the need for the more detailed basic types.
The performance of CLAW is an important design goal. However, the speed of individual CLAW operations is unlikely to be important to the user of CLAW because most GUI operations occur at human speeds (for example, responding to a mouse click in a menu). Also, Win32 operations tend to be much slower than the CLAW operations that call them. Therefore, we have put most of our effort into reliability. We have experimented with various CLAW sample programs on machines as slow as a 386/25, and have been unable to detect any problems due to CLAW overhead.
3.5 Exceptions and Reliability
Unreliability is a major problem with many Windows applications. Using Ada should greatly improve this situation, and we designed CLAW to be reliable itself, and to assist the application programmer in building a reliable product. We are also well aware that difficulties, even if caused by user mistakes or Windows strangenesses, are likely to reflect badly on CLAW. We try to take the view that "There are no pilot errors, only difficult to use cockpits".
CLAW tries to avoid the use of nonsense operations by attempting to avoid their existence in the first place. This is accomplished primarily by having multiple types for items which have significantly different operations sets. In this case, the Ada compiler will prevent calling operations on the wrong type. For instance, CLAW (unlike Win32) has separate types for listboxes with strings and without strings. Calling Find_Text on a listbox without strings will cause a compile-time error.
CLAW defines a small set of exceptions for error handling. Six exceptions handle all error conditions. This is true primarily because we have found that the error codes returned from Win32 functions are not reliable. Different implementations of Win32 return different codes, and worse, Windows 95 appears to return random codes in many cases. Therefore, CLAW cannot depend on the error code. Thus, most errors returned by Win32 are handled by raising Windows_Error. Since the CLAW user may find value in the error codes, they are placed into the Exception_Message when Windows_Error is raised.
CLAW checks its arguments for validity before passing them to Windows. These checks raise Not_Valid_Error or Already_Valid_Error as needed. These checks prevent most Win32 failures before Win32 is even called, and provide (depending on the compiler's Exception_Information support) more precise identification of the error.
For operations not supported on all versions of Windows, CLAW includes a version check, raising Not_Supported_Error if the operation is not supported. Win32 is supposed to indicate this. However, since we have found that Win32 error codes are not reliable, CLAW does this check itself.
CLAW contains many internal exception handlers to prevent exceptions from propagating out of CLAW into Windows. CLAW uses Ada.Exceptions to report such exceptions (and any other information that the programmer's compiler provides) to a user in a message box. (This default can of course be overridden if the application programmer prefers another form of error notification or logging.)
Another way that CLAW increases reliability is in the handling of out (return) parameters for user-written, CLAW-called routines. Since Ada 95 has no requirement that such parameters be set, failure to set the parameters would cause CLAW to take actions based on an uninitialized variable. To avoid this, CLAW defines the parameters as in out parameters, and provides an appropriate initial value. This increases CLAW's reliability; rather than having random behavior depending on an uninitialized variable, CLAW provides deterministic behavior.
CLAW's portability goal implies that the CLAW user view must be the same, no matter what compiler is being used. In additional, we must have flexibility for future enhancements to CLAW. In order to help accomplish these goals, most CLAW types are private types. In addition, CLAW takes advantage of the child package relationship to hide operations meant for CLAW's use. Many basic operations are declared in the private part of the root CLAW package. These operations on various root types (particularly Root_Window_Type), prevent the accidental or malicious use of CLAW's management functions. Many of these operations could cause circular or broken chains if misused. By controlling the use of these operations, CLAW's reliability is increased.
3.6 Messages and Tasking
The Windows 3.1 event loop was a form of non-preemptive, shared variable, multitasking. There were opportunities for non-task-safe programming, but mild vigilance would usually save the C programmer from error. Win32 adds on top a preemptive tasking system. This greatly increases the potential for error. Ada 95 also includes tasking, so it is important that tasking issues be considered in the design of CLAW.
A Windows window is tied to the thread that created it. That thread processes Windows messages for any windows it has created. In order to avoid anarchy, we chose to include a single task in CLAW that creates and destroys windows and processes the message loop. Having such a task makes it possible for CLAW to be able to allow the user to create and destroy windows at any time, without tasking implications. The message task is started by the elaboration of the main CLAW package.
If CLAW did not use a single task to process messages, then each task which created windows would need to execute a message loop. This probably would have to be done via some special call or object. If the special item were omitted, the windows would never appear. This would likely be a difficult to find program bug, and one which would bite most new CLAW users.
CLAW itself, in the message loop thread, processes a few messages. For instance, CLAW provides a convenient procedural interface for setting the colors for controls. CLAW processes the control color messages in order to implement the user's requested colors.
Many messages are translated into calls on CLAW action routines. Action routines are overridable primitive operations of the CLAW data types, e.g. "procedure When_Key_Down(Window : in out Root_Window_Type; ...)". The default implementations usually do nothing, but they can be overridden using normal tagged primitive operation overriding. CLAW then delivers the message by simply making a standard Ada 95 dispatching call. In order that action routines be easily distinguishable from regular routines, CLAW action routines always are named "When_<<something>>"
CLAW has a special action routine named "When_Other_Message". CLAW calls When_Other_Message for every message that it does not define an action routine to handle. An advanced Windows programmer can provide a handler for such messages if necessary simply by overriding When_Other_Message.
The message task makes the (dispatching) calls to action routines, so, like any Windows message handling routine, they should be short and fast[5]. They may be thought of as rather like Interrupt Service Routines handling the serial arrival of messages, but leaving heavy computation to other tasks. Blocking in an action routine means that all input to the program is suspended as long as the action routine is blocked. Lengthy or blocking operations must be used with care in action routines and the hourglass should be displayed on the screen to warn the user that input is blocked. It is preferable to assign such big jobs to a different task rather than doing the work in the action routine executing in the CLAW message loop task.
Using the common message task means that the application programmer (normally) knows what task called the action routines. Indeed, it is not one of her tasks. This means that with care, she can have an action routine interact with other tasks.
Windows are commonly created in action routines. For instance, a dialog box may be created in response to the application's user selecting a menu entry. Since CLAW insists that the message task create all windows, a naive implementation of window create would cause deadlock as the message task attempted to rendezvous with itself. CLAW uses the Ada 95 Task_Identification package to determine when window creation is being attempted by the message task itself. Such creations are handled immediately (they are already in the correct thread), while other creations wait for a rendezvous. Thus, window creation is allowed almost anywhere.
Unfortunately, there is a case where this solution fails. If a window is created during a rendezvous between an action routine (thus the message task) and another task, the task identification will identify the user task. Therefore, the user task will end up waiting forever for the message loop task to continue (as the message loop task is waiting for the user task to complete the rendezvous). In order to handle this rare case, all of the rendezvous on the message task use timed entry calls. If the call is not accepted in a reasonable period of time, the call is aborted, and Message_Error is raised. While the window create won't be successful, the programmer will receive an obvious indication that something is wrong. (This case actually happened to one of the authors when writing a CLAW example program. It took two days of work to track down the problem. This probably falls into the category of anything that can go wrong will, eventually.)
Windows delivers many messages to the parent of a window. Such messages are typically notifications of some sort, such as an indication that a button was pushed. Handling such messages in the parent is often easier than providing some sort of handler in the child window, then notifying the parent. However, such a design often limits reusability of custom versions of the child window. A custom version of a child window can therefore require a custom version of it's parent window.
In order to avoid this problem, CLAW redirects messages to the child windows. That means that the child type has action routines for the interesting messages. If the child window does not handle the messages, then the message is sent to the action routine of the parent window. This design allows a customized child window to be created simply by overriding the needed action routines. This customized window can then be plugged in anywhere that allows child windows.
3.7 Windows Objects versus CLAW Objects
The word "object" in Windows usually means "handle" and probably maps most closely to "access to limited private type" in Ada - which is substantially different from its use in "Object Oriented" programming. Pens, brushes, etc. are, however, naturally modeled as controlled objects derived from an abstract Tool object, so CLAW does the necessary work to encapsulate the Windows "object" in a CLAW "Object". Windows has many small inconsistencies. Thus DeleteObject is used for all tools except palettes, but should not be called for a "Stock Object"; bitmaps can only be attached to a "RAM-Canvas". CLAW hides the minor differences and uses compile time checking to catch the important ones. Similarly, CLAW models windows, child control windows, buttons, etc. with a derivation hierarchy from a Root_Window_Type, allowing overriding of primitive operations. Thus the application programmer may override action routines (primitive subprograms) When_Command, When_Key_Down, and so forth to ac
pt menu commands, keystrokes, or whatever. (Unlike MFC, CLAW actually does use tagged type dispatching for this.)
CLAW objects are true OO Objects so extensions can be created. They are Controlled so CLAW can handle finalization, including detaching tools from canvases and menus from windows when appropriate. Most are not Limited and CLAW automatically handles clone copies of the original object. Root types are usually abstract; one kind of object per child package. Generics are used to provide packages which work on any user-defined type. For example, Integer edit controls are packaged in a generic unit (a la Text_IO), so the user can use them on any appropriate type.
CLAW generally uses an abstract base class, which has no create or destroy routines. The abstract classes do have private helper operations which make it easy to implement operations. Claw declares these helper operations in the private part of the Claw parent package. Child units can see and use them, but customers (that is, CLAW programmers) cannot access them.
As a C program, Windows often uses heterogeneous or dynamic types. Many Windows API routines are called with a pointer whose type is implied by another parameter. Some data structures must be laid out in ways that are difficult to specify in Ada. So called "Device Independent Bitmaps" for instance, may contain a variable size array of packed black/white single bit pixels, color index bytes, etc., as well as a possible variable size color translation table. In most cases CLAW is able to offer a convenient set of Ada types, possibly discriminated or tagged, and overloaded routines with behind-the-scenes processing to efficiently and portably pass the data to the Windows API.
4. Ada 95
4.1 Elaboration and Initialization
We wanted CLAW to be immediately available to the application program. That is, once CLAW is withed, objects can be immediately declared and created. Forcing users to wait until after the start of the main program would unnecessarily constrain program structure. In addition, errors caused by using objects too soon would be very difficult to detect. Similarly, we did not want to require application programmers to include elaboration pragmas for CLAW in their code. Again, leaving them out could cause difficult to find bugs.
In order to meet these goals, most CLAW initialization is executed at elaboration of the CLAW packages. In order to insure that elaboration is done immediately, most CLAW packages contain Elaborate_Body pragmas. These pragmas, which are new to Ada 95, insure that any withed package is completely elaborated (including the body) before the current package.
In a few cases, CLAW packages are mutually dependent; that is, the body of A withs B, and the body of B withs A. In such cases, Elaborate_Body pragmas cannot be used. In such cases, we identified a package or packages which must be withed before the mutually dependent package(s); we call these "key packages". We then inserted Elaborate_All pragmas for the mutually dependent packages in the all of the key packages. This insures that all of the needed packages are elaborated before their user can declare objects for any of them.
4.2 Tasking Safety
We considered various approaches to providing task safety in CLAW. The first approach we considered, and the easiest to reject, is "user-beware". The "user-beware" approach says that the user must lock any operation which could happen at the same time as any other potentially conflicting operation. This is the easiest approach to implement (since it essentially ignores tasking issues), and also the most efficient (since it does not have any locking overhead unless it is actually needed). However, this approach has some serious drawbacks. First of all, it is very difficult to explain "potentially conflicting" to the user. Unrelated objects could be linked together by CLAW for memory or resource management purposes. Short of assuming that any two CLAW operations conflict, it is very hard to know when two operations do in fact conflict. Secondly, this approach potentially causes nearly impossible to find bugs. Failing to lock something that does in fact conflict could cause interm
tent, catastrophic failure of CLAW, probably in a portion of CLAW far away from the actual cause. Finally, this approach is very much opposed to the Ada philosophy that CLAW is trying to follow, that is, that operations are safe, independent, and either work as intended or fail gracefully in all circumstances. Therefore, we quickly rejected this approach.
The second approach we considered goes all the way in the other direction. The "always-safe" approach says that CLAW will insure that all operations can be executed by any task at any time. This clearly is the easiest for the user, but it also is going to be very expensive to implement. CLAW would have to lock and/or serialize essentially all operations. We judged this as overkill; users will pay a lot for locking without much corresponding benefit.
Therefore, we settled on the "shared-variable" approach. This approach says that CLAW will lock operations as needed so that different tasks can operate on different CLAW objects simultaneously. On the other hand, if different tasks are to operate on the same object simultaneously the user must apply the usual tools and disciplines to prevent error (typically, wrapping the object or its access in a protected type). This approach costs less to implement and in runtime locking costs than "always-safe". It also allows users to consider CLAW objects as independent entities which can be used in essentially any context.
CLAW uses protected types internally to do locking. These types are essentially counting semaphores, which allow the same task to lock a particular kind of object multiple times. The lock will not be released until an equal number of unlock calls are made. Most locking is completely transparent to the user. However, there are a few cases where the user must invoke locks (most notably, for finalization of assignable types). In these cases, the locks are encapsulated inside of a controlled object, and bundled with another necessary operation. Bundling with another operation ensures that the lock is used as necessary, and the controlled operation insures that the lock is freed when the user operation finishes.
For example, user finalization routines can use the Is_Only_Copy flag to determine whether to destroy any locally allocated data. The user finalization operation must be locked (the "shared-object" model prevents problems when only one object is left, but problems could occur if the last two copies of an object are finalized at the same time. The CLAW finalization operations lock as necessary, of course, but not locking the user code would leave a race condition, which would fail only in very rare cases.) Our solution was to bundle this flag in a controlled record which handles the locking:
type Only_Object_Parent_Type is abstract new
Ada.Finalization.Limited_Controlled with record
Is_Only_Copy : Boolean := False;
end record;
type Only_Object_Type is new Only_Object_Parent_Type
with private;
procedure Is_Only_Copy (Object : in out Only_Object_Type;
Window : in Root_Window_Type'Class);
-- Sets Object.Is_Only_Copy to True if no "clones" of Object
-- exist. Otherwise sets it to False. Do not depend on the
-- value after Object is finalized.
-- Raises:
-- Not_Valid_Error if Window does not have an open
-- (Windows) window or if Is_Only_Copy was already
-- called using Object.
This type is used by declaring the object in the finalize routine or in a enclosing block:
procedure Finalize (Window : in out User_Window_Type) is
-- Finalize Window and all child windows. This destroys the
-- window, but does not raise any exceptions.
Only_Copy : Claw.Only_Object_Type;
begin
-- Check if Window has already been Finalized, return if so.
Claw.Is_Only_Copy (Only_Copy, Window);
-- Get only copy flag, locks tasks.
-- Call parent Finalize routine.
Claw.Frame_Window.Finalize(Claw.Frame_Window.
Frame_Window_Type(Window));
if Only_Copy.Is_Only_Copy then
-- Finalize/Deallocate any extension components here.
else -- Another copy of the window exists.
-- Delete any extension components from this record, but
-- let the other copy destroy them.
end if;
end Finalize;
We found that in a few cases we had to relax "shared-object" to essentially "user-beware". This happens whenever an operation on one object can have an effect on essentially any operation on a second object.
For instance, Windows organizes windows into a parent/child relationship. Among other effects, the destruction of a parent window causes the destruction of the child windows. If a task destroys a parent window while another task is doing any operation on the child window, the child window operation could fail gracelessly. (While virtually all CLAW operations check that an object is valid before preceding, if the child task has already made the check when the parent destroys it, it will not notice and try to complete the operation with the closed handle.) Handling this case unfortunately is as expensive as "always-safe", as any operation on a window would always have to lock its parent. Therefore, we are taking the "user-beware" approach to this case. Unfortunately, this case can be common, as the user of a CLAW application can close it at any time. A programmer using CLAW therefore must insure that any close operation insures that any tasks operating on child windows have been nchronized. Usually this is trivial, since most applications have only a single task executing the GUI portion of the program.
4.3 Termination
Since most Ada programmers are new to Windows, and new to Ada 95 OO style programming, graceful and informative abnormal termination is a priority.
There are at least two tasks in any CLAW program - the message loop task and the main program task. They must both be terminated. The CLAW message loop task has an "others" exception handler that will display an on-screen error dialog, post a Windows Quit message to close any windows, and terminate. The lack of a message loop will become apparent to the main program (or other tasks) when they attempt operations on a closed window or get a Tasking_Error. (Since the end user can click on a window's "close" box at any time, a properly behaved main program will check reasonably often for a closed window as an indication the user wants to terminate the program.)
If the main program stops while the message loop is still running, any on-screen windows will still be somewhat responsive, obscuring the fact that the application is moribund. CLAW's message loop task will sit on a select with "terminate" alternative if there are no windows open, and thus no incoming messages to poll for, so in that case the death of the main program will terminate the message loop task. But if any windows remain open, messages (user key-ins, mouse actions, etc.) will still arrive and the message loop task must continue running. Such windows can be closed by the user of the program, but it would be better if the program could close them itself.
Forgetting to close windows is a common programming error. It can happen because of an oversight, or more commonly because of the propagation of an exception. CLAW uses Ada 95's controlled type finalization in order to insure that windows are closed. However, finalization of library level objects occurs after all tasks have terminated (section 7.6.1(4) [10]). Thus, problems can still occur. Terminating the message loop task when any (library-level) windows remain open has proven to be a tough problem. Most potential solutions do not work because they require a library-level object to be finalized before the message loop task.
Having the message loop task wait on a terminate alternative while processing messages would solve the problem. Then the task would terminate as soon as the main program finished. (A controlled object could be used inside of the message loop task to close any open windows when it is about to terminate.) However, the message loop task must periodically check with Win32 to see if any messages are waiting to be processed. This means that some way to exit the select with terminate alternative is required.
Combining a terminate alternative with a delay alternative (a "timed terminate") would solve the problem. Unfortunately, Ada 95 does not allow the combination of a terminate alternative with a delay or else alternative in a select statement (9.7.1(8-12) [10]). This is prohibited because this combination potentially causes a race condition. If two or more tasks are waiting on "timed terminates", whether or not they terminate, and when they terminate, depends on the exact interaction of the timers. It is possible that they would never terminate, even though both tasks were waiting on a terminate alternative most of the time.
If it were possible to query whether the task's master is waiting for tasks to terminate, the message loop task could exit itself without any race condition. (Once a task starts waiting for dependent tasks to terminate, it will continue to do so until aborted). Unfortunately, that operation is not available in Ada 95.
A legal way to exit a terminate alternative is to have another task call an entry in the select statement. We could define a "kicker" task for this purpose. The kicker task would neatly solve the problem of termination for the message loop task. However, we now would have a new problem - how to terminate the kicker task. The kicker task has to be waiting on a terminate alternative in order to terminate properly. But of course, this is the problem we started with!
We also considered using Win32 timers to solve the problem. Unfortunately, the timer callback functions are only called when the timer message is processed. Of course, since we want to use the timers to control message loop processing, they will not work.
Another solution we investigated was using Ada 95's Asynchronous Transfer of Control. ATC could be used to abort a select alternative. This would look something like:
loop
select
delay 0.1;
then abort
select
-- accepts for the task.
or
terminate;
end select;
end select;
Message.Kick;
end loop;
Since ATC aborts code, it is very difficult to keep a consistent program state when using it. In this case, we wouldn't want any of accepts to be aborted, but they would be if the time-out expired. Compilers don't do a very good job of cleaning up after an abort. Finally, ATC is typically very slow, but this construct has to be used in the inner loop of the CLAW code. Therefore, we have rejected the use of ATC.
An explicit termination routine would, of course, solve the problem. Early in the construction of CLAW, we in fact used an explicit termination routine. This was done more as a pragmatic decision rather than a planned one -- we wanted to defer termination issues until we were convinced that the CLAW model (particularly the message loop task) was workable, portable, and usable. However, experience with the explicit termination routine proved that it is was easy to forget, and it often was. It also was hard to put it on all possible exits to the program, particularly those caused by propagating exceptions. Early CLAW programs often became zombies - dead, and sometimes invisible, programs kept alive by their continuing message loop task - when an error occurred.
We are now convinced that there is no portable Ada 95 solution to the problem of fully automatic termination. (We would be happy to be proved wrong, of course). We are now beginning to investigate compiler-specific solutions. These include the use of compiler-specific task runtime functions (which allow the message loop task to determine if the main program is waiting for it to terminate), and controlled use of ATC (if the compiler does so quickly and cleans up effectively). Proper encapsulation should make the use of such solutions invisible to the user.
4.4 Overridable versus Classwide routines
Making all operations on the tagged types overridable is at first attractive, but it results in a large, cluttered, root type. (The Microsoft Foundation Classes (MFC), for example, has over 350 overridable operations on its window root type, while CLAW has about 30.) CLAW instead makes many operations classwide.
Classwide operations are those which take a class parameter. In CLAW, most of these are Root_Window_Type'Class. Classwide operations are not inherited or overridable, but they still can be used on any type derived directly or indirectly from the root window type.
Unlike overridable operations, classwide operations can be declared in separate packages, resulting in better interface structuring. (Overridable operations all must be declared in the same package as the type that they are primitive for.) Since it is substantially more difficult for compilers to analyze overridable subprograms to see if they are in fact unused and can be removed from the executable module, using classwide routines also tends to result in smaller executables.
The only penalty with the use of classwide routines is that they cannot be overridden. For many operations, this is not an issue. For instance, redefining the position of a window is not likely to be useful, so CLAW defines position routines like Position and Move as classwide.
4.5 Limited versus Non-Limited types
It is very tempting to make objects Limited, but unfortunately that greatly limits their usability and forces the application into heavy use of access pointers to the limited objects. This occurs because of the restrictions on limited types. For instance, limited types cannot be returned from a function or used in an aggregate. In addition, a limited component of a type makes the entire type limited (a result succinctly called "limited poisoning"), forcing those same restrictions on it as well.
To avoid these restrictions, we instead made Menus, Tools, and Windows non-limited Controlled, using Initialize, Adjust, and Finalize to handle what often amount to multiple clones of the same object. Though this is somewhat more work to implement, it simplifies the work of the application programmer, and that, after all, is the whole point of CLAW.
We left Canvases as limited types. Given their intended short lifetime, storing them outside of local blocks should be rare. In addition, their interconnections with other types make supporting assignment on them very difficult. Experience with Claw programs shows that Menus and Tools are frequently copied (returned from functions, and so on), Windows rarely, and Canvases never. One problem with limited Canvases is that they cannot be directly declared as components in a window extension. Ada 95 does not allow limited components in non-limited tagged type extensions. Since Canvases normally have a lifetime much shorter than Windows, this particular case is not a significant problem.
CLAW makes shallow copies, in which each is linked to an original object which in turn is not destroyed until all its clones are destroyed. Deep copies (where the Windows object is actually duplicated) are rarely useful, and expensive at runtime. Such copies also can be meaningless: what would it even mean, on the screen, for an assignment statement to make a deep copy of a visible window?
Shallow copies, however, make more work for user extensions. The user must take care that the local components are either safe to be copied, or are properly cloned. This usually means insuring that all copies of the object share a single copy of the local extension components. Such cloning also needs to protect against multiple tasks assigning objects simultaneously, our solution to this problem was discussed in section 4.2.
Of course, this effort is not needed if the objects of the class will never be assigned. Unfortunately, there is no way to communicate to the compiler that assignment is not desired. Ada 95 does not allow limited extensions of non-limited parents (or vice-versa), so extensions cannot be declared limited. At best, comments can be used to specify that particular extension does not support assignment. (All CLAW classes developed to date, except canvas classes, do support assignment; the issue arises mainly in user classes.)
5. Results to Date
CLAW 1.0 is now available. Its bindings for most GUI interfaces of Win32 comprise 95 public packages and 7 private packages. The whole of CLAW 1.0 has about 67,000 source lines with 26,000 semicolons, while the public specifications are about 1/4 of the total size. CLAW 1.0 also includes a set of example programs illustrating the usage of many CLAW features and showing how to construct different kinds of Windows programs.
CLAW 1.0 has been tested on three Ada 95 compilers. These are Janus/Ada 3.1.1a, GNAT 3.09, and ObjectAda 7.1. As CLAW uses many of the new Ada 95 features, and uses them in combination, it has been a compiler stress test. Each compiler has broken in various ways while compiling CLAW and its examples. In addition, each compiler has detected illegal code in CLAW that the others missed. However, we are happy to say that compiler quality has been improving as the project has continued. We expect that, as compilers mature, using CLAW on other compilers should be less difficult.
CLAW 1.0 has been tested on various Windows versions, including Windows 95, Windows NT 3.51, Windows NT 4.0, and Win32s 1.30c. (The latter requires a compiler which supports single-threaded tasking, which is only available in Janus/Ada so far as we are aware). CLAW masks the differences between these systems, and raises Not_Supported_Error where this is not possible. We expect that CLAW programs should work without modification on future Windows operating systems, but of course this depends on the operating system changes.
We are currently using CLAW to add GUI interfaces to several existing command line programming tools. This is showing the benefits of CLAW in action, as well as helping to shake out bugs and improve CLAW's usability.
6. Related Work
6.1 Earlier work by the authors
One of the authors constructed a medium level Windows binding for Windows 3.1 in 1990 (revised for Win32 and Ada 95 in 1993 [2]). This binding was primarily an Ada 83 restating of the Windows API. It used exceptions to return errors, handled messy string operations, and changed names to a more Ada style. Ada 95 improved this interface substantially. This work greatly influenced our thinking about Windows bindings. It emphasized the value of converting the interface to Ada, as it proved to be much more comfortable working with this medium interface than the raw thin binding that R.R. also provides.
As part of an Ada 9x User/Implementor project, one of us converted an existing text windowing package into a set of Ada 9x object-oriented packages[1]. The resulting packages and sample programs were compiled with the then beta-test version of R.R. Software's Ada 9x compiler. (This project was probably the first Ada 9x object-oriented program to actually run and in fact uncovered problems leading to changes in Ada 9x.) . This work emphasized the value of object-oriented organizations for user interface packages, and helped us understand the best ways to organize such packages.
In 1994, several concerned Ada users (including one of the authors), attempted to construct a "standard" medium Ada Windows binding[3] through SigAda ABWG (Ada bindings working group). Some preliminary specs and design documents were constructed. When the Intermetrics binding[4] became available users felt it was "good enough", which killed the project. This drove home to us that a successful Ada binding would have to be thicker and at a higher level than Win32.
6.2 Other Windows bindings
Both Microsoft's Foundation Classes[13] and Visual Basic[14] are efforts to put a higher level interface on top of the raw C Windows API. Their programming languages are an early C++ and BASIC, both of which lack the power and maintainability of Ada 95. The Visual Basic paradigm is essentially to create derived objects with overridden operations - a limited version of the capabilities of the tagged types in CLAW.
The Intermetrics Win32 binding [4] was constructed by building a tool to convert C header files into Ada 95, and then running the Win32 C header files through the tool. As a result, the binding is very thin.
Aonix took a similar approach constructing their Microsoft Foundation Class (MFC) binding.[11] It also is a thin binding requiring the use of Microsoft's C++ documentation and following C++ idioms. It also requires compiler features which are not part of Ada 95 (interface to C++), tying it to a single compiler.
"Portable" bindings like TASH [9] take a very different approach. They are designed to provide cross-platform support. TASH runs on Microsoft Windows, Apple Macintosh, and various Unix platforms. Such bindings can be at nearly any level, but generally only provide capabilities which are easy to provide on all of the platforms. Platform specific functionality (e.g. Windows Common Dialogs) cannot be used. Since their program organization is often taken from another environment, programs constructed with such bindings do not provide the expected look-and-feel for a Windows application. A "portable" binding can be valuable if an application needs to run on many platforms, but if the primary goal is to produce a good Microsoft Windows application, they fall well short.
6.3 Other Bindings
The IEEE Standard POSIX Ada binding [12] demonstrated the feasibility and usefulness of an Ada style binding between Unix systems and Ada 83. Many of the problems are similar in a Win32/Ada 95 binding, and we were aided by the POSIX Ada document, and in particular by its "Rationale and Notes" appendix
7. Future Directions
CLAW is a working compiler independent binding that is friendly to Ada programmers, that uses Ada 95 features to raise the abstraction level above Win32, and yet allows the resulting applications to have the look and feel of the Windows applications familiar to the end user.
We intend to continue extending CLAW to cover other, non-GUI parts of the Windows programming interface. In addition, an Application Builder tool for CLAW is under construction. Additional tutorial information is being constructed. We also intend to construct additional higher-level types as their utility becomes apparent.
As a "library of components" and not an "application framework", CLAW cannot depend on an explicit termination call or the presence of a "when others" exception handler in each task. We plan future work developing frameworks giving more "safe environment" control, as well as providing more capabilities automatically.
CLAW is structured explicitly to provide for extension via child package and via derived tagged types. To a substantial extent, OO application programming using CLAW consists in fact of extending windows and other objects with new behavior. With a little extra work, these new packages can be made reusable. Since CLAW allows access to the underlying API calls, it is easy for toolmakers to create new child packages exporting convenient use of new API sets. We look forward to extensions of CLAW that we haven't even thought of, created by its users.
Bibliography
1. Brukardt, Randall, et. al., Janus/Ada compiler manual version 5.0 (pages JWN-1 - JWN-38), R.R. Software, Inc., Madison WI, March 1994. (JWindows text windowing packages).
2. Brukardt, Randall, et. al., Janus/Ada compiler manual version 5.1 (pages 16-1 - 16-98), R.R. Software, Inc., Madison WI, November 1995. (Medium Windows bindings).
3. Brukardt, Randall, MAWB Design and Rationale (version 2.1). (Distributed via Internet E-Mail, October 1994). A copy can be requested from the author.
4. Gart, Mitch, "Interfacing Ada to C - solutions to four problems" Tri-Ada '95 conference proceedings, ACM, New York NY, 1995. (Intermetrics Win32 bindings. Additional information can be found on their web site at http://www.intermetrics.com/ and http://www.inmet.com/~mg/win32ada/win32ada.html)
5. Krell, Bruce E., "High-Speed Windows Applications", Bantam Books, 1993. ISBN 0-553-08992-7
6. Leif, R. C, Moran, T., Brukardt, R., "Ada 95, The Language Speaks for Itself", Object Magazine, Implementation Languages, 7 (3) pp. 32-39, May 1997
7. Petzold, Charles, "Programming in Windows 95", Microsoft Press, 1996, ISBN 1-55615-676-6
8. Riehle, Richard, et. al., CLAW user manual, R.R. Software, Inc., Madison WI, June 1997 (Additional CLAW Information can be found at http://www.rrsoftware.com).
9. Westley, Terry "TASH: A Free Platform-Independent Graphical User Interface Development Toolkit for Ada", Tri-Ada '96 conference proceedings 165-178, ACM, New York, NY, 1996
10. --, Ada 95 Reference Manual, ISO/IEC 8652:1995.
11. --, Aonix MFC bindings, ObjectAda OpenPack CD-ROM (reference A1102.71.3) Aonix Corporation, 1997 (Additional information is also available at http://www.jswalker.demon.co.uk/jswtech.htm).
12. --, IEEE Std 1003.5-1992, IEEE Standard for Information Technology, POSIX Ada Language Interfaces
13. --, Microsoft Foundation Class Reference, Microsoft Developer's Network CD-ROM, Microsoft Corporation, Redmond WA, October 1996
14. --, Microsoft Visual Basic Reference, Microsoft Developer's Network CD-ROM, Microsoft Corporation, Redmond WA, October 1996
15. --, Microsoft Win32 SDK Reference, Microsoft Developer's Network CD-ROM, Microsoft Corporation, Redmond WA, October 1996
Acknowledgements
The authors would like to thank Douglas Hadley, Ian Goldberg, Robert Leif, and Richard Riehle, all of whom read drafts of this paper and made valuable comments.
We also would like to thank Do-While Jones, who made valuable comments on early versions of CLAW, and greatly influenced our thinking on naming and organization.
We would like to thank the ATIP/P project, whose financial support made CLAW possible.
Finally, we would like to thank everyone on the CLAW team for their valuable contributions: Timothy Kessler, Wu Wang, Richard Riehle, and Steven Myatt. Our apologies if we left anyone out.
Microsoft Windows, Microsoft Windows NT, Microsoft Windows 95, and Win32 are trademarks of Microsoft Corporation.
Appendix: A CLAW Example
The following is a simple CLAW example. The program calculates prime numbers and displays them.
The program uses two (explicit) tasks, one to do the calculations, and one (the main task) to run the user interface. A protected object is used to synchronize communications between the two tasks. CLAW programs do not require multiple tasks,but the user interface task should not do any lengthy calculations. (Doing so could cause sluggish user response.)
The type Display_Type is derived from Claw.Frame_Window. Frame_Window_Type. The action routine When_Command is overridden in order to handle menu selections. Frame_Window_Type handles the exit condition internally, so When_Command does not need to handle the Exit menu entry.
The object Display is declared in the package body of TriAda_Screen. By doing so, we avoid having to support assignment on Display_Type. A more object-oriented approach would export Display_Type from the package, and declare the object in the main program. We use the simpler approach here.
Menu entries are identified by user-selected integers. CLAW provides a routine to select unique identifiers, and this is used to create the menu.
Display of the results is handled by creating a static control in the window. The calculation task directly calls Set_Text on the control in order to change the text Note that this is not the task that created the control. CLAW supports multiple tasks using the same objects.
In order to keep the example simple and self-contained, a dynamically constructed menu is used. For applications like this, a resource template is recommended by Microsoft. CLAW supports the use of resource templates, although they cause version control problems for the typical Ada development system.
Another CLAW example (using Information Systems Annex features) is available in the Object Magazine article [6].
package TriAda_Screen is
-- Worker is really a separate task.
end TriAda_Worker;
with Claw,
-- Override Display_Type's default "When_Command".
procedure Put (Output : in Positive) is
procedure Run; -- Runs the application.
-- Communication between worker and screen.
function Quit_Requested return Boolean;
function Restart_Requested return Boolean;
procedure Put (Output : in Positive);
end TriAda_Screen;
package TriAda_Worker is
-- Start just gets it going.
procedure Start;
Claw.Frame_Window,
Claw.Canvas,
Claw.Draw,
Claw.Menus.Bar,
Claw.Static,
Claw.Menus.Lists,
TriAda_Worker;
package body TriAda_Screen is
type Display_Type is new
end TriAda_Worker;
Claw.Frame_Window.Frame_Window_Type with record
My_Menu : Claw.Menus.Bar.Menu_Bar_Type;
Status_Box : Claw.Static.Static_Type;
-- Status_Box is the child control window
-- where we actually display the output.
end record;
procedure When_Command (Window : in out Display_Type;
From : in Claw.Command_Source_Type;
Command_Id : in Claw.Menu_Identifier_Type;
Unknown_Command : in out Boolean);
-- Display is the entire on-screen window,
-- including the menu frame.
Display : Display_Type;
type Menu_Options is (Restart, Quit);
MENU_BASE : constant Claw.Menu_Identifier_Type :=
Claw.Menus.Get_Unique_Menu_Identifier (
Claw.Menu_Item_Index_Type(Menu_Options'Pos(Menu_Options'Last)+1));
-- This call ensures that these Identifiers are unique in this run.
-- Communication between worker and screen tasks.
protected Comm is
function Quit_Requested return Boolean;
procedure Set_Quit_Requested;
function Restart_Requested return Boolean;
procedure Put_Restart_Requested(Value : in Boolean);
private
Quit_Request : Boolean := False;
Restart_Request : Boolean := False;
end Comm;
protected body Comm is
function Quit_Requested return Boolean is
end Comm;
begin
return Quit_Request;
end Quit_Requested;
procedure Set_Quit_Requested is
begin
Quit_Request := True;
end Set_Quit_Requested;
function Restart_Requested return Boolean is begin
return Restart_Request;
end Restart_Requested;
procedure Put_Restart_Requested (Value : in Boolean) is
begin
Restart_Request := Value;
end Put_Restart_Requested;
-- We let the worker task write its output directly, leaving
-- CLAW to handle tasking.
begin Claw.Static.Set_Text (Display.Status_Box,
Integer'Image(Output) & " is prime");
delay 0.5; -- Wait a bit so the user can read the result.
exception
-- If we can't display output for some reason, destroy the
-- Display window and let the exception propagate to
-- kill the worker task.
when others =>>
Destroy (Display);
raise;
end Put;
function Restart_Requested return Boolean is
-- A wrappeto publicly display just some of Comm to the
-- worker task This also modifies a protected variable,
-- even though it looks externally like a function.
begin
if Comm.Restart_Requested then
Comm.Put_Restart_Requested (False);
return True;
else
return False;
end if;
end Restart_Requested;
function Quit_Requested return Boolean is
-- Another wrapper routine.
begin
return Comm.Quit_Requested;
end Quit_Requested;
procedure Run is
-- Run the application.
use type Claw.Menu_Identifier_Type;
use Claw.Menus.Lists;
Drop_Down : constant Menu_Item_Type :=
Submenu("&Choices",
(Menu_Item("&Restart", Menu_Options'Pos(
Restart) + MENU_BASE),
Menu_Item("E&xit", Menu_Options'Pos(
Quit) + MENU_BASE)));
begin
Display.My_Menu := Claw.Menus.Lists.Create(
(1 => Drop_Down));
Create (Window => Display,
Window_Name => "TriAda Demonstration",
Menu => Display.My_Menu,
Exit_Id => Claw.Menu_Identifier_Type(
Menu_Options'Pos(Quit) + MENU_BASE));
Show (Window => Display,
How => Claw.Codes.Show_Startup);
-- Set up the display box.
Claw.Static.Create (Display.Status_Box, "99999 is prime",
Parent => Display, Position => (10,10));
-- Let Claw figure out the size for a box that will hold a -- 5 digit number.
Claw.Static.Set_Colors (Display.Status_Box,
Text_Color => Claw.Colors.BLACK,
Background_Color =>
Claw.Colors.Get_System_Color (Claw.Colors.Window));
-- Set the colors to match the window.
TriAda_Worker.Start; -- Start the worker task.
Wait_For_Close (Display);
-- Just sit tight in this task until the window is closed.
Comm.Set_Quit_Requested;
-- Tell the worker to quit, the window is closed.
exception
when others => -- Oops, problems.
-- Tell worker to quit, destroy Display (and its child
-- Status_Box) if it still exists.
Comm.Set_Quit_Requested;
if Is_Valid (Display) then
Destroy (Display);
end if;
raise;
end Run;
procedure When_Command (Window : in out Display_Type;
From : in Claw.Command_Source_Type;
Command_Id : in Claw.Menu_Identifier_Type;
Unknown_Command : in out Boolean) is
use type Claw.Menu_Identifier_Type;
begin
-- Process a menu selection.
if Command_Id in MENU_BASE .. MENU_BASE +
Menu_Options'Pos(Menu_Options'Last) then
case Menu_Options'Val(Command_ID - MENU_BASE) is
when Quit =>
null; -- Should never get here. Quit is processed
-- by the parent, Frame_Window.
Unknown_Command := False;
when Restart =>
Comm.Put_Restart_Requested (True);
Unknown_Command := False;
when others =>
Unknown_Command := True;
end case;
else
Unknown_Command := True;
end if;
end When_Command;
end TriAda_Screen;
with TriAda_Screen;
package body TriAda_Worker is
task Process is
entry Start;
end Process;
task body Process is
function Is_Prime (N : in Positive) return Boolean is
-- A really simplistic prime check function.
D : Natural;
begin
for I in 2 .. N loop
end Is_Prime;
D := N/I;
if I*D = N then
return False;
end if;
exit when D <= I;
end loop;
return True;
begin
accept Start;
end Process;
-- Now do some actual (though just demo) work.
Keep_Going: loop
Search: for I in 2 .. 10000 loop
if Is_Prime(I) then
end loop Search;
TriAda_Screen.Put(I);
-- A good time to check for user control input.
exit Keep_Going when TriAda_Screen.Quit_Requested;
exit Search when TriAda_Screen.Restart_Requested;
end if;
end loop Keep_Going;
procedure Start is
begin
Process.Start;
end Start;
with TriAda_Worker,
TriAda_Screen;
procedure TriAda is begin
TriAda_Screen.Run; -- Run the application.
end TriAda;