Fenestra User's Guide (appendix D) -- contents | previous | next
The runtime system is the part of the library written in C and interfacing the Windows API with the Eiffel part of the library. It consists mainly of `wrapping' functions to convert API calls to Eiffel types and the other way round. A few low level operations are performed in the C runtime when it would have been inconvenient to perform them on the Eiffel side.
The Fenestra C runtime is the one area of the library that has to be ported when using unsupported Eiffel compilers or C compilers. It should be simple in most cases. While the runtime is written in C, it does not imply that the Eiffel compiler has to produce C code: the runtime can be compiled to a DLL for instance. Any Eiffel compiler on the Win32 platform is likely to have a DLL interface.
The runtime library should be fairly portable to other C compilers. There should not be much to do in order to port to another ANSI C compiler for an already supported Eiffel compiler. The runtime library uses only standard Win32 API calls -- from windows.h -- and do not make use of the standard C runtime library. Normally, customising or rewriting the runtime makefile is all that is required. If a quick port is needed, a makefile may not even be needed as the whole of the runtime is contained in a single source file, fenestra.c which can be simply compiled.
The proper symbols described below should be defined. Of course, only one of the Eiffel compiler support should be enabled at a time.
Porting to other Eiffel compilers is a more complicated task than porting the C support, but should not be too difficult provided the compiler provide a usual Eiffel interface to C. C source files from the runtime are commented to indicate the expected portability problems.
Most of what needs to be ported has been moved to the fenestra.h header file, which provides macros for Eiffel types and macros to convert from Win32 or C types to Eiffel and the other way round. Normally porting this file to your compiler will be the main task of the port.
For ports to Eiffel compilers that do not support the external "C" interface from Eiffel, the class WIN_RUNTIME will have to be customised, for instance to a DLL interface with the runtime. This class includes all external calls done by the library.
The other important part is the callback interface. The operating system makes obviously a heavy use of callbacks, and most of the time the program is processing one callback or another. All custom windows handled by the library actually share the same callback procedure, whose C part does nothing more than reformatting the parameters in an Eiffel-friendly way and calling the global (once) instance of the class NOTIFIER. There is a single central entry point to port at this stage! The few remaining entry points into the Eiffel system (also in the NOTIFIER object) are for the subclassing of built-in controls -- which is used for implementing the dialog box keyboard interface (see below) -- and the font enumeration callback. Due to the small numbers of callback functions, it should not be difficult to do, and all but the main callback can be put on hold if a quick port is required.
From a performance viewpoint, because the number of callback functions is reduced, these few callbacks are consequently used very often. Consequently, they should be as direct as possible. If the Eiffel compiler provides runtime resolution of Eiffel feature names from the foreign interface, this feature should be avoided or cached so that the same feature need not be called by name extremely often.
As far as possible, the runtime communicates with Eiffel through basic types to minimise porting efforts. The basic types used are BOOLEAN, CHARACTER, INTEGERThe current implementation requires Eiffel integers to be at least 32 bit wide., POINTER.
All data that cannot go through basic types, including character strings, is interfaced using the library's Eiffel class F_MEMORY which packages a low-level memory block. The Eiffel code then sends pointers to such memory blocks to the low-level code. The low-level runtime does not need to create such objects, as it only reads or modify blocks provided by the Eiffel side. Also, no routine of the low-level runtime keeps a reference to any memory block or objects created in the Eiffel code.
The class NOTIFIER is used to handle all the callback routines from C to the Eiffel system. This class has no descendants in the library, as this may be a requirement for a callable object with some compilers. NOTIFIER is not passed as an argument, except for an initialisation call used by the runtime to know which object to call, and register with the garbage collector that it keeps a static pointer on this object Which is actually probably over cautious given that NOTIFIER is a once object: it should not be collected too frequently..
All library functions used from Eiffel are grouped in the class WIN_RUNTIME (and for a few routines used for finalisation WIN_RUNTIME_FINAL) which contain all the external "C" routines.
Apart from the canonical implementation of the Windows -- or more precisely Win32 -- API by Microsoft under Windows NT and Windows 95, there are many available implementations of the API under various systems, notably Unix/X11 and the Apple Macintosh.
The library makes a fairly standard, and relatively narrow, use of the API, so porting to other implementation should be realistic if not trivial. The dynamic dialog system should help to make actual code more portable as the tricky resource issue becomes simpler to handle.
Section (see section D.3) below documents a number of undocumented or not so orthodox implementation methods which had to be used, and may indicate some source of problems when porting, though the methods used are common -- even recommended by Microsoft in some cases -- and the third-party API implementers may have taken this into account.
As it has been introduced in the previous section, the runtime system is responsible for sending the events received by the application's callback procedures to the Eiffel system for dispatching.
Events are processed by C callback function called by the operating system (in a single process space). The implementation in the C runtime just reformats the parameters to corresponding Eiffel types depending on the event identifier, and then calls the Eiffel system from C.
Events which are not processed by the library are sent back to the operating system for default handling. Therefore, events which are not known by the library are not sent to the Eiffel subsystem.
It is nevertheless trivial to add a new event, in the runtime event callback, if direct processing of an operating system event is required. It should not be much more difficult to send all events to the Eiffel system and decide at this stage whether to process them or not, albeit it would introduce overhead -- as numerous internal events, for instance, should never be processed by an application.
Once the event has been sent to the Eiffel system, it is converted to an internal WIN_EVENT object, which will be processed in the when_event event procedure of a window. All low-level events are associated with windows, even if the link may be indirect for cases such as timers or menu processing.
In a nutshell, processing directly low-level operating system events implies adding packaging of the parameters in the low level runtime, and redefining the when_event procedure of the associated descendant of WINDOW. The new when_event should check the event code and process the new event(s), before calling the old version of when_event Redefining WINDOW's when_event, a function which is frequently called, may make it polymorphic during the compilation of optimised systems, and therefore have an influence on performance..
The operating system sends event in two different manners: some events are queued and processed by the main event loop (GetMessage/DispatchMessage) and others are sent directly by the system which calls the callback procedure directly without queueing. This is the difference between messages sent with SendMessage as opposed to the messages posted to the application queue with PostMessage.
The usual thing to do is understandably to process events when they are received. For an Eiffel implementation on top of C, it means it is necessary to go recursively in and out of the runtime system: an Eiffel function will call through C an API function, which may send a message (callback) to the runtime system, which will call the Eiffel system from C, which then can recursively call an API function, etc. This Eiffel-C-Eiffel-C cycle could create problems with some implementations -- such as restricted debug output or suspended garbage collection.
We normally always return to the first Eiffel level -- the one which was entered at the start of the Eiffel system to process the queued events, if briefly because the event loop implemented in NOTIFIER just calls DispatchMessage which will in turn call do the event processing -- inside a callback from the runtime. The application will practically nearly always be inside an Eiffel callback from the low-level runtime.
An alternative to processing all events when they happen, in order to avoid the deep Eiffel-C-Eiffel-C cycle is to manage our own queue. All events callbacks simply put the event in an Eiffel queue container, which will be processed and emptied after each call DispatchMessage in our event loop. It allows most of the processing to happen in the normal Eiffel world, not inside a callback from C.
Unfortunately, this double queueing introduces problems. Apart from adding some overhead, it may also delay the events: If there are only direct events, the queued direct events will be processed only when the next posted (system queued) event occurs. This particular problem can be solved with a permanent timer event to guarantee, at a small performance cost, regular flushing of the Eiffel event queue.
It can also change the order of events, as no events will be processed during another any more, a resizing event will be always processed after the event, not during the call to window.set_size as in normally happens.
Some events may be marked as requiring immediate processing, but if too many events fall in this category, it defeats the purpose of double queueing. Events that must be marked as such are events which have to send a reply depending on the context back to the sender. Few events fall into this category -- although many system-processed events are like this one, for at least the reason that communicating through events it is the implementation model of all system-provided controls.
Given that most compilers seem to work well enough with the first model, the library does implement immediate processing of all events. It is nevertheless possible (and almost trivial) to modify NOTIFIER for processing of events using its own queue This was implemented in an early version of the library for a compiler that did not support immediate processing, but was later removed when new generations of compiler supported better the direct way. should it be required for a port.
The normal policy is not to use any undocumented methods as far as possible. Unfortunately, even Microsoft documents some ways to do things which are not very elegant and would have been done better by proper support from the API. The few cases where this was required are documented below, along with the compromises which were reached to fit API programming into Eiffel.
In the current implementation, Win32 handles are considered to be 32 bits integers -- they are effectively 32 bits values in the current API -- and stored in an Eiffel INTEGER. Therefore it is assumed that INTEGER is 32 bit wide or more, which is the case in all known Eiffel implementations as of today. This method is simple and straightforward. Meanwhile, the handle code has been encapsulated in the OS interface classes of the WIN_* family. Each object that has an OS handle inherits from WIN_OBJECT which provide the value 'handle', of the same type (currently INTEGER) as the one used by the OS functions in the WIN_RUNTIME class, which groups all calls to the runtime.
Pointers could have been chosen, but there are no practical reason to assume that Windows types are actually pointers more than integers as long as they are 32 bit values. It is also useful to compare them against NULL or other invalid pointer values -- the value for invalid pointers actually varies between function families in the API! -- which may not be possible with all Eiffel implementations of POINTER.
It is also currently assumed that all handles are of the same type, which is the case in reality. The API itself does a similar assumption for some different C types (in the GDI for instance).
The library only allows one common background brush, and one common icon, for all of the application's overlapping windows. The reason is that most overlapping windows created by the library are of the same Win32 class -- not to be confused with an Eiffel class. These particular settings are specific to the window class, while quite a few other parameters are window-specific. It was not deemed useful to add a specific and artificial window class for every single window -- defeating the purpose of a Win32 window class -- just to be able to have specific icons and background brushes. Customised background colour are available for child windows, through CHILD_WINDOW_COLOR, which creates one new class per colour -- the colour has to be chosen at creation time for this reason.
As it has been explained in chapter (see section 4), the library implements dialog boxes dynamically. It does not use dialog boxes built from resources. The library creates all controls using CreateWindow and adds them to a standard window. It is not possible to take easily advantage of the built-in dialog keyboard interface. It is also a problem when implementing modeless dialog boxes in C using the standard API. The only way to do it would be to hook into the message queue with IsDialogMessage, which is not convenient. It would also provide no keyboard interface for custom controls written using the Eiffel library.
So, the keyboard interface has been rewritten. It is implemented in DIALOG and CONTROL, and so every custom control, which is a plain Win32 child window, inherits from it. To do that and still access the standard controls, which have their own event processing procedure in the operating system, it is necessary to subclass all built-in controls to get the keyboard events. Subclassing is a documented method allowing a third party program or module to plug-in a window event procedure to manipulate some events.
The keyboard events generated by this facility are specially managed by the runtime system and are not treated like other windows events, mainly because they should be processed immediately so that, before returning the control to the system or the original window procedures, it is known whether the message has actually been processed. The original window procedure is stored in a Window property -- SetProp and GetProp and RemoveProp API functions -- so that there is no need to call the Eiffel system when no events are captured to avoid extra overhead. The subclassing is automatically cancelled at the runtime level when the subclassed procedure processes the WM_DESTROY message.
The Win32 API does not provide any way to set the size of the client area of a window, while it can be retrieved. The only way to set the size of the window is by setting its external size. This is not a problem for child windows when the external size is the same as the client size -- although the origin of the client and external coordinate system is likely to be different. It is a problem with windows which have a title bar or a slider or a menu or another similar feature.
The API provides a function that converts client size to external size according to the class creation time parameters. Though, this function only takes the creation parameters and can not retrieve simply the client size from a window handle. The primary aim of this function is to be called before a CreateWindow call to create a window of appropriate dimensions given a required client size. The library uses this function to implement the ability to set the client size of the window, it retrieves the given window original parameters thanks to GetWindowLong/Word. Hopefully, a postcondition guarantees that an exception will occur if this method fails.
Another size related tricky issue is the operating system debatable definition of a rectangle. When sizing windows or other non-GDI elements on the desktop, some -- but not all -- API function take or return a rectangle structure -- the API's RECT. This rectangle is defined by its upper left and lower right points. That should be straightforward enough. Nevertheless, these window `rectangles' do not include the lower right point (and therefore the lower and right sides of the rectangle), while they do include the opposite sides. Because this behaviour is very confusing, and inconsistent with the graphics device interface -- so that if a window's size is used to draw a rectangle in its client area the results may be unexpected -- Fenestra corrects this problem and adds the proper offset so that the rectangle defining a window from the Eiffel library is what is will appear the desktop. To this end, the library implementation has to add some add or substract 1 from the actual coordinates in a few selected places.
A related problem is how to define a rectangle width and height. While in this case these values are rarely communicated to the low level interface, the class RECTANGLE define width and height as the width and height in pixels of a rectangle, that is a rectangle defined by the point (2,5) and (5,10) will have a size of (4,6) while the operating system would say (3,5). This is a tricky issue because the former works well with bitmap displays while the latter can be sensible with virtual (for instance metric) coordinates when it can be assumed that a point is like a geometric point which has no width. It may be more secure to use only point coordinates when using virtual coordinates with classes from the `Graphics' cluster.
Fixed-size windows such as dialogs (MODELESS_DIALOG and MODAL_DIALOG) may not be resized or iconised, but even if they do not have the relevant buttons and resizing border, the standard system menu still allows to do it. It is then necessary to be modify the system menu so that these options are removed, this modification is dependent on the current implementation of the system menu, but is the common way to handle this problem -- this is the way the standard dialog box API does it.
Another problem is the subclassing of a COMBOBOX control, an editable combobox -- the only type of combo box in this library, which names non-editable Windows combo boxes ``dropdown list boxes'' because it better reflects their functionality. It has its keyboard flow managed by its child edit control. As explained for control subclassing above, it was necessary to subclass controls to get the keyboard support for the customised dialog boxes. The method documented by Microsoft ([sdk93], 13.2.3) to do this is to get the handle of the edit control with ChildWindowFromPoint at position (1,1)
Despite being a very clumsy method -- the position had to be changed to accommodate the new 3D controls in newer versions of Windows -- this is the way it is implemented.
The operating system sends the same messages -- WM_HSCROLL and WM_VSCROLL -- to the built-in controls on the side of a child window, and control style scrollbars which are inside this window, possibly a dialog box. The library dispatches control message by assuming that the handle of the `scrollbar control' parameter is NULL, which is what it should intuitively be, and happens to be in fact, but this is not unambiguously documented.
Dynamic dialogs are managed like normal windows so the library does not require the ID provided by normal controls used from a resource with the normal dialog procedure. As they are not used -- the normal window handles are used -- and because it would be complicated to find a correct algorithm to generate unique and reusable ID for each control in a given parent window while never using it -- all of our controls have the same ID, which should not cause any problems.
The library does not provide methods to change the Windows `viewport' origin of a graphic device, as the same result can be accomplished by setting the `window' origin. Nevertheless, converting the coordinates to logical coordinates may be necessary in some cases.
It is very common to set a font size in points -- the standard dialog box for font selection uses this unit. Unfortunately, the GDI only allows to set the height of a character (or character cell) in logical units of the device where it will be used. Besides the logical units, the library allows to specify font size in points. Therefore, the library converts the point size into logical units, using the device vertical resolution (from GetDeviceCaps) or the default screen resolution when no device is available -- in the case of built-in controls for example.