REF XptDescriptor R.Evans Jan 1991
Revised Adrian Howard Jun 1993
COPYRIGHT University of Sussex 1990. All Rights Reserved.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< XptDescriptors >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
This ref file describes the XptDescriptor data type used by the X
Toolkit interface, the notion of preferred representation for X data
structures, and the weak type-checking mechanism used by the interface
procedures.
CONTENTS - (Use <ENTER> g to access required sections)
1 Introduction
2 Memory Management
3 'Weak' Type-Checking
4 Preferred Representations
5 XptDescriptors
6 Class Apply Of Descriptors
7 Destruction Of XptDescriptors
8 XptProcedures
9 Type-checking By X Toolkit Interface Routines
10 Access To Preferred Representations
11 System Management Of XptDescriptors
12 Programming With Preferred Representations
---------------
1 Introduction
---------------
Note:
This REF file will be mostly of interest to library writers
providing higher level interfaces to the X Toolkit routines.
Generally, speaking, ordinary users should not need to know about
the internal details of descriptor representation and type-checking.
Management of data structures is probably one of the areas of greatest
difference between C and Poplog. In C, the user assumes full
responsibility for allocating and freeing global data structure memory
dynamically (and for this reason often avoids global dynamic memory
use), and run-time type-checking is not possible, all the checking
taking place at compile-time. In Poplog, memory allocation and freeing
is automatic, and almost all type checking (if any) occurs at run-time,
there being very little compile-time checking possibilities (except in
PML). These differences have important consequences for the X Toolkit
interface (indeed for any interface between Poplog and a substantial
'external' package). Poplog needs to provide support for the automatic
management of externally created data structures. It also needs to
provide support for run-time type-checking of procedure arguments. And
it is desirable to do these things in a way that does not prejudice the
organisation of higher-level libraries.
--------------------
2 Memory Management
--------------------
If Poplog's automatic memory management (in particular the garbage
collector) is to deal properly with data structures created by the C
Toolkit routines then the following requirements have to be met:
¤ there should be a SINGLE Poplog representation for the
externally created data structure which the garbage collector
can take to be the canonical representative: when this record
becomes garbage, the external data can be freed.
¤ there should be a reliable way of mapping from an external
reference to a data structure (ie a 'raw' pointer) to its
canonical representative, to be used, for example, when
'importing' an external reference returned by a Toolkit routine.
¤ suitable deallocation routines must be available to be
registered as destroy actions (see REF*PROPS) so that the
garbage collector can free externally created structures when it
decides they are no longer needed.
¤ any dependencies among externally created data structures (or
between an externally created structure and an ordinary Poplog
object) which the garbage collector needs to be aware of must be
paralleled by ordinary Poplog references (which the garbage
collector can detect) between their canonical Poplog
representations.
Together these points ensure coherent management of externally created
data. For example, the external Toolkit routines create widget
structures, which to Poplog are opaque handles, that is, they are simply
pointers to data structures whose internal contents are private to the
Toolkit. Poplog associates canonical representatives, (Poplog records
called XptDescriptors, see below), with widgets, and it is generally
these that the user manipulates. Such widget descriptors have the
XtDestroyWidget procedure as their destroy action, so if one becomes
garbage, the associated external structure will be automatically
destroyed be the garbage collector. In addition, a widget descriptor
contains references to other XptDescriptors it depends on (for example,
its parent widget's XptDescriptor, and its widget class's
XptDescriptor), and other Poplog objects which it depends upon (for
example, Pop-11 procedures registered as 'callback' procedures). This
ensures that as long as the widget descriptor itself is non-garbage,
everything it depends on will be too.
-----------------------
3 'Weak' Type-Checking
-----------------------
Compiled C procedures generally perform no run-time type-checking of
their arguments. Furthermore, passing data of incorrect type to them can
often lead to disastrous results. Therefore the ability for Poplog to
perform type-checking on X Toolkit procedure arguments is of
considerable importance. Poplog's standard type-checking is based on
keys: different data types are implemented as different classes with
different keys. However, this approach to type-checking has a number of
disadvantages for the X Toolkit interface:
¤ it entails introducing a large number of new keys into Poplog,
including perhaps 10 or more into the core system. But in fact,
aside from their use for type-checking, many of these keys would
be largely the same - basically just handles on opaque external
data. (Types are very cheap in C, but less so in Poplog).
¤ it forces users who want to take advantage of the type-checking
facilities to use the specific data classes provided, while
there are actually many different ways of representing the
required data, each with its own advantages and disadvantages.
¤ it forces the design of the lowest level X Toolkit interface to
'second guess' the requirements of higher level support in the
future, or commit such facilities to clumsy indirect access to
the Toolkit facilities.
For example, suppose we introduced a widget class with its own key.
Clearly we would have to decide what user-accessible fields that class
should have. Obvious candidates include a field for the parent, the
children, the widget class. Less obvious candidates are the resource
names and types, the callback list names, the actions, actual resource
values, callback procedures etc.. And even less obvious are fields
required by some as yet unwritten higher-level package. It is difficult
to see how to support all these possibilities in a general and
perspicuous fashion. But if we choose not to provide support for fields
some package requires, we commit that package to defining its own class
of widget records which have the extra fields. This in turn may entail
providing its own versions of all the Toolkit routines, versions which
map its own widget records onto the system ones to exploit the
type-checking.
To avoid these problems, the type-checking done by the Toolkit routines
is not based on keys. Instead it assumes that all data structures to be
type-checked (aside from a few simple cases: strings, booleans and
numbers, as discussed in REF*XTOOLKIT) are 'external-class' records
(see REF*EXTERNAL_DATA) and uses their 'external_ptr_props' fields to
hold simple type-checking information (typically just a word, such as
"Widget"). XptDescriptors as created by the system include appropriate
type information to satisfy the checks (so both widgets and widget
classes, for example, are represented by XptDescriptors, but with
different type information so that checking routines can distinguish
them). In addition, library packages may construct their own
external-class data structures which also satisfy the tests, and so can
be used directly with the core Toolkit routines, without sacrificing the
checks.
This notion of type-checking is weak in the sense that the type of a
data structure is in fact user-assignable (unlike its key!). However in
normal use the types will be assigned (typically by libraries rather
than user code) and then not changed.
See 'Programming with Preferred Representations', below, for further
discussion of how these facilities are intended to be used.
----------------------------
4 Preferred Representations
----------------------------
Above we introduced XptDescriptors as canonical Poplog representatives
for externally created data structures. However we also suggested that
library packages might want to use their own representations, for
example including any private 'bookkeeping' information the package
might need. This creates a potential problem: such a package may well
wish to operate in its own 'structure space', that is, supply and expect
returned its own representations for data structures, have sensible
destroy-action behaviour on its own representations etc.: essentially it
wants to view its own representations as the canonical ones, rather than
the XptDescriptors created by the system.
One way to achieve this would be for the notion of canonical
representative to be user-assignable, so that a package can simply
specify its own representation in place of the XptDescriptor
representation. This approach has its disadvantages, however: such a
package would have to be aware of and maintain all the information
(references to dependents etc.) otherwise maintained by the system code
in the XptDescriptor records. In addition it would have to provide
various hooks to allow the system to modify that information (eg a hook
for adding a child to a widget, a hook for adding system bookkeeping
records when a callback is registered, another for accessing such
records).
Basically, since the system itself needs to maintain a certain amount of
structure in its canonical representations, any library that takes them
over has to duplicate that maintenance. Furthermore if we allow (as we
would wish to) several such packages to be in use simultaneously,
coordination of this support would become cumbersome at the very least.
The approach adopted avoids these problems by always using
XptDescriptors as the canonical representatives, with the system
maintaining the integrity of the descriptors automatically. But every
XptDescriptor can have associated with it a 'preferred representation',
that is, an alternative Poplog representation of the same external data
structure which the user wishes to manipulate in preference to the
XptDescriptor representation. This is achieved with the XptRegister
procedure (see below). Such preferred representations can be used in
place of XptDescriptors as arguments, and will be returned in place of
XptDescriptors as results of Toolkit interface procedures. This allows a
library to operate in its own structure space by registering its own
structures as the preferred representations of the system-generated
XptDescriptors. For further discussion of how this can be achieved, see
'Programming with Preferred Representations' below.
-----------------
5 XptDescriptors
-----------------
XptDescriptors are the canonical Poplog representations for data
structures (of many types) created by the external Toolkit code. They
contain the barest minimum of information needed to support their role
as canonical representations as discussed in the preceding sections;
packages requiring additional bookkeeping information are expected to
create their own representations for that purpose. Thus XptDescriptor
records contain:
¤ a pointer to the actual external data structure
¤ type information for the weak type-checking
¤ a user props facility
¤ references to dependent Poplog structures
¤ support for preferred representations
XptDescriptors are external_ptr records, with external_ptr_props fields
(see REF*EXTERNAL_DATA). The external_ptr field contains the pointer
to the external structure, so that they can be passed directly to
external (eg externally loaded) procedures expecting the external
structure as argument. The external_ptr_props field is used for the weak
type-checking and user props facility, as discussed below. The
references to dependent structures are also discussed below, and are not
directly accessible.
In addition, the system maintains a mapping from external references (ie
raw addresses) to XptDescriptor records, so that references to already
existing structures can be correctly resolved. The process of turning a
raw external reference into a Poplog record is called 'importing' the
reference, and is used whenever an external reference (of known type) is
to be returned as a result, or passed as an argument to a callback
procedure. A number of user routines for importing references
(XptImportWidget, XptImportDisplayPtr etc.) are also provided.
The precise procedure for importing references is as follows. First the
reference is looked up in the system mapping. If there is an
XptDescriptor associated with the reference, and it has the expected
type for the reference, it is taken as the canonical representative. If
it has a preferred representation, that is returned, otherwise the
descriptor itself is returned. Otherwise, a new XptDescriptor for the
reference is created and returned (and associated with the reference for
future use). Creation may involve linking to other XptDescriptor
records, importing other references (eg a widget will attempt to import
its parent, and its widget class), adding destroy actions and possibly
other bookkeeping activities. Once a valid XptDescriptor has been
looked-up or created.
Note:
if an XptDescriptor with a different type is found, it is taken to
be the spurious result of re-use of externally managed memory, and
so discarded.
Thus XptDescriptors can be passed directly to all Toolkit routines (both
checking and non-checking), and they, or their preferred representation
if any, will be returned by Toolkit routines as results or callback
arguments.
XptDescriptor_key -> key [constant]
This identifier holds the value of the key for XptDescriptor
records.
consXptDescriptor(eptr, type) -> desc [procedure]
This procedure creates and returns an XptDescriptor from an
external_ptr eptr, setting its type to type (an arbitrary Poplog
item, generally a word). desc will be associated with the
external reference in eptr and have no preferred representation
associated with it (NB: overriding any previous descriptor and
preferred representation for the reference).
isXptDescriptor(arg) -> bool [procedure]
This procedure is a recogniser for XptDescriptors, returning
true if arg is an XptDescriptor, false otherwise.
XptDescriptors have a default class_print procedure which uses the type
and user props information to construct the print form. If there is no
user props information, a descriptor will print as
<TYPE>
If there is user props information it will print as
<TYPE PROPS>
For this reason, TYPE is generally a simple word (eg "Widget").
-----------------------------
6 Class Apply Of Descriptors
-----------------------------
XptDescriptor_apply(desc) [procedure]
VALUE -> XptDescriptor_apply(desc) [procedure]
The class apply for a descriptor (also held in the identifier
xptdescriptor_apply) concatenates the XptDataType of the
descriptor with the prefix "Xpt" and the suffix "Apply", and
then calls the procedure (after autoloading it if necessary)
associated with the resulting word.
The apply action will mishap if there is no procedure associated
with the word and autoloading is unsuccessful.
In update mode, the updater of the apply procedure associated
with the same identifier is called. A mishap is generated if the
update mode is used but there is no updater procedure.
For example, "Widget" descriptors have a class apply procedure
called "XptWidgetApply", which is defined in the (autoloadable)
library LIB * XptWidgetApply.
See REF*XPT_CLASSAPPLY for details of the library-supplied
class apply procedures in Poplog.
Libraries which register a preferred representation with a
descriptor might choose to assign xptdescriptor_apply to be the
class_apply of the new representation.
--------------------------------
7 Destruction Of XptDescriptors
--------------------------------
XptDescriptors can become invalid without becoming garbage. For example
if a program explicitly calls XtDestroyWidget on a descriptor, the
external widget is destroyed and the descriptor's reference to it is no
longer valid, although the descriptor itself still exists. To prevent
this causing problems, a descriptor is 'tidied' when its external
reference is destroy in this way (note: only in cases where the system
knows that destruction is taking place). This tidying does the following
things:
1) sets the XptDescriptor's external_ptr field to NULL
2) sets the preferred representation's external_ptr field to NULL
3) clears the link between the descriptor and its preferred
representation (???)
Such 'dead' descriptors print as live ones but with '(NULL)' inserted
before the TYPE (eg <(NULL) Widget foo>).
----------------
8 XptProcedures
----------------
One class of XptDescriptors which need special consideration is
descriptors referring to external procedures. The new external interface
facilities makes the distinction between external procedures and other
kinds of external value redundant. So in principle a Toolkit routine
expecting an external procedure (eg XtAddCallback) could just be given
an XptDescriptor just like any other, pointing to the procedure and
appropriately typed.
However, there is a complication to this picture. From V14, Poplog is
capable of servicing interrupts (CTRL C, timer, SIGIO etc.) which arise
while executing external Toolkit code. If such interrupt routines
attempt to process the Toolkit event queue (perfectly acceptable if
interrupted while in normal Poplog processing), the effect is a
recursive invocation of Toolkit event handlers. Unfortunately the X
Toolkit is not re-entrant in this fashion and the queue of incoming
events can get corrupted. So this must not be allowed to happen.
To prevent it, all the routines which process the event queue respect an
internal flag which causes them to do nothing if set. This flag is set
by all the Toolkit interface routines built into the system. But it also
needs to be set by Toolkit routines externally loaded in libraries, and
any other external procedures which might be called by the Toolkit and
might generate Poplog interrupt handling. (To be safe, the best advice
is that it should be set by ANY external routine invoking, or
potentially invoked by a Toolkit routine).
The setting of this flag is handled by a special import routine for
external procedures. Rather than returning just an XptDescriptor
'wrapper' for an external procedure pointer, XptImportProcedure first
augments the procedure it is given with a piece of code to set this
'disable-re-entrancy' flag (using a variant of the exfunc_closure
mechanism - see REF*EXTERNAL). So the descriptors it returns will be
'safe' with respect to Toolkit re-entrancy.
XptImportProcedure(eptr) -> desc [procedure]
This procedure takes an external_class record whose external_ptr
field should reference an executable external procedure, and
returns an XptDescriptor for it. The descriptor includes code to
set the disable-re-entrancy flag before the procedure is called.
Notes:
(1) the disable-re-entrancy flag is localised to the Poplog
procedures which apply external procedures, so the global
context is protected from assignments made by XptProcedure
descriptors.
(2) this flag completely blocks nested handling of Toolkit events,
so programs which remains in Poplog callbacks from the Toolkit
for extended periods of time will be unable to service its user
interface properly. Programs should therefore be designed to
remain in callback routines for only very short periods of time,
e.g. to set global flags.
------------------------------------------------
9 Type-checking By X Toolkit Interface Routines
------------------------------------------------
As mentioned above, the Toolkit's weak type-checking is implemented
using the external_ptr_props field of the data structures being checked.
For a data-structure DATA to be of type TYPE it must satisfy the
following criteria:
¤ DATA must be an external_ptr_props record (ie an external_ptr
record with the 'external_ptr_props' flag set and thus a props
field accessible by external_ptr_props).
¤ The external_ptr_props field of DATA must be TYPE, or a pair
whose front is TYPE (this latter option is to retain the ability
for arbitrary user props to be stored in the
external_ptr_props).
The following procedures provide the supported interface to this
type-checking convention, and should be used to protect user code from
any future alterations to the mechanism.
XptDataType(eptr) -> type [procedure]
type -> XptDataType(eptr)
fast_XptDataType(eptr) -> type [procedure]
type -> fast_XptDataType(eptr)
These procedures access/update the weak type of eptr, which
should be an external_ptr_props record. The checking version
mishap if eptr is not external_ptr_props, the fast version
performs no check.
XptDataProps(eptr) -> props [procedure]
props -> XptDataProps(eptr)
fast_XptDataProps(eptr) -> props [procedure]
props -> fast_XptDataProps(eptr)
These procedures access/update the props field of eptr, which
should be an external_ptr_props record. The checking version
mishap if eptr is not external_ptr_props, the fast version
performs no check. If props is non-false the external_ptr_props
of eptr will be a pair. If props is <false> the
external_ptr_props will be simply the type.
In principle, TYPE can be any Poplog data item except a pair. However
all the pre-defined types are words, and constant identifiers for them
(which should be used to protect user code from future changes) are
defined in INCLUDE * XPT_CONSTANTS.PH.
It might seem at first sight that the constraint that all Toolkit
arguments are external-class objects is over-restrictive. However it
should be borne in mind that, as discussed in REF*XTOOLKIT, strings,
numbers and booleans can be used directly, and descriptors created by
the Toolkit (widgets etc.) are in any case external-class. Although in
some cases there are straightforward non-external ways of representing
other kinds of arguments, the majority are more robustly handled as
external-class (for example, many require 'raw' NULLs for list
termination, others use embedded structures etc.). The approach taken,
therefore, is to provide support for easy creation of appropriate
external-class data (primarily LIB * SHADOWCLASS - see
REF*SHADOWCLASS), and then to require its use. Programmers wishing to
avoid the overheads of this approach might consider using the
non-checking versions of the interface routines, which can be used with
any correctly structured data representation.
---------------------------------------
10 Access To Preferred Representations
---------------------------------------
XptRegister(eptr1) -> eptr2 [procedure]
eptr2 -> XptRegister(eptr1)
This procedure accesses the preferred representation of an
external reference. eptr1 is an external-class record. eptr2 is
either <false> or and external-class record with the same
external_ptr field AND type as eptr1. The base form returns the
preferred representation for (the external reference of) EPTR
(or <false> if there isn't one). The updater assigns eptr2 as
the preferred representation for (the external reference of) (or
clears the preferred representation if eptr2 is <false>)
Note that registering a preferred representation will fail if
there is no XptDescriptor associated with the external
reference. However, eptr1 does not have to be that descriptor -
any external_ptr record with the right external reference and
type will suffice (indeed eptr1 and eptr2 can be the same!).
Note also that registering an XptDescriptor as a preferred
representation is effectively equivalent to assigning <false>.
XptDescriptor(eptr1, type) -> eptr2 [procedure]
This procedure returns the XptDescriptor associated with eptr2
(ie with the same external reference and type) if any, or
<false> otherwise. Once a preferred representation has been
registered, the underlying descriptor will not normally be seen
(or needed) by the user; this procedure makes it accessible if
needed.
---------------------------------------
11 System Management Of XptDescriptors
---------------------------------------
This section briefly describes aspects of the system's management of
XptDescriptors which may be of significance to the programmer. These
aspects fall into three main areas: dependencies maintained between
descriptors (and other data items), destroy actions attached to
descriptors, and any other 'unexpected' processing done at descriptor
creation time.
The following lists the XptDescriptor types created and managed by core
Poplog Toolkit routines. For each type, the 'weak' type is given (both
the constant defined in INCLUDE * XPT_CONSTANTS and its actual value),
plus the dependencies (the other objects it maintains references to),
the destroy action and any additional creation processing. A few other
XptDescriptor types are created by libraries, for example the library
LIB * XptImportXEventPtr. These have no dependents, no destroy action
and no additional creation processing. Many of the common types are
defined in INCLUDE * XPT_CONSTANTS.
All 'live' XptDescriptors maintain a reference to their preferred
representation (if any).
Action Hooks
Weak type: XDT_ACTIONHOOKID = "ActionHookId"
Dependents: callback procedure and client data, application context
Destroy action: NONE (they are destroy automatically when there
associated application context is destroyed.)
Application Contexts
Weak type: XDT_APPCONTEXT = "AppContext"
Dependents: Attached displays, Input, TimeOut, Action and
ActionHook procedures (and client data).
Destroy action: fast_XtDestroyApplicationContext
Creation processing: Standard error handlers are registered. Default
values for XptGarbageFeedback and XptWMProtocol actions
are registered. An event handler for WM_PROTOCOL events
is registered.
Displays
Weak type: XDT_DISPLAYPTR = "DisplayPtr"
Dependents: Associated application context
Destroy action: fast_XtCloseDisplay
Input Ids
Weak type: XDT_INPUTID = "InputId"
Dependents: source device record, condition, callback procedure and
client data, application context
Destroy action: NONE (they are destroy automatically when there
associated application context is destroyed.)
Interval Ids
Weak type: XDT_INTERVALID = "IntervalId"
Dependents: callback procedure and client data, application context
Destroy action: NONE (they are destroy automatically when there
associated application context is destroyed.)
Procedures
Weak type: XDT_PROCEDURE = "Procedure"
Dependents: external procedure record
Creation processing: wrapped in an exfunc_closure structure to set
the disable-re-entrancy flag.
Widgets
Weak type: XDT_WIDGET = "Widget"
Dependents: child widgets (both normal and pop-up), parent widget or
application context (for shell widgets), callback
procedures and data, widget class, event handlers
Destroy action: fast_XtDestroyWidget (for shell widgets only)
Creation processing: a destroy callback to tidy up Poplog
representation is added (note: XtDestroyWidget does not
itself 'kill' the Poplog representation, but it causes
this callback to be run soon afterwards). Shell widgets
register the WM_DELETE_ME WM_PROTOCOL when REALIZED.
Widget Classes
Weak type: XDT_WIDGETCLASS = "WidgetClass"
Dependents: superclass
Notes:
(1) The dependencies between descriptors are maintained in order that
garbage collection behaves correctly, rather than for the
convenience of the user. For this reason this aspect of descriptors
is not user-accessible. So for example although a widget descriptor
retains a reference to its widget class's descriptor, the user
cannot obtain the latter directly from the former: instead the
procedure XtClass (in LIB * XT_WIDGETINFO) should be used. (More
direct access to some of the dependencies maintained may become
available in future releases.)
(2) Destroy actions are attached to some of the descriptor records when
they are created. However this is only done for descriptors which
reference data actually created by Poplog Toolkit interface
routines. References which are being imported (and so were created
outside of Poplog's direct control, eg created by external procedure
calls) do not have destroy actions attached to them automatically
The assumption being that whatever package or utility created them
will handle their destruction. So for example if you create a
scrollbar widget it will probably create two or three sub-widgets
typically buttons at either end and the slider portion. The
scrollbar itself, being created by Poplog, will have a destroy
action. The sub-widgets will initially have no Poplog representation
at all (being created externally by the (C) scrollbar creation
routine), but if you choose to import them (using XptImportWidget)
their descriptors will not have destroy actions attached. However
all the destroy actions are publicly accessible procedures, so the
user may attach the destroy actions explicitly if desired.
NOTE FOR UNIX USERS: All the destroy actions are process specific
i.e, they are stored in sys_process_destroy_action. They will
only be called in the same process they were installed. This allows
Poplog processes which use X facilities to be safely forked using
sysfork.
(3) References above to callback procedures and client arguments
generally refer to the external-class procedures and their
arguments. However when Poplog procedures and their arguments are
coerced into their external form, the new client argument maintains
references to the original procedure and client, so these will also
be safely retained by the descriptors.
----------------------------------------------
12 Programming With Preferred Representations
----------------------------------------------
(This section is unfortunately not available for V14 Poplog, but will be
present in V15)
--- C.x/x/pop/ref/XptDescriptor
--- Copyright University of Sussex 1992. All rights reserved.