Search                        Top                                  Index
REF EXTERNAL_DATA                                   John Gibson Dec 1994

        COPYRIGHT University of Sussex 1994. All Rights Reserved.

<<<<<<<<<<<<<<<<<<<<<                             >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<  EXTERNAL DATA STRUCTURES   >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<                             >>>>>>>>>>>>>>>>>>>>>>

The term external  data structure  refers to data  maintained in  memory
outside  of  the  Poplog  system  proper  by  'external'  functions  and
procedures , that is, those written in non-Poplog languages (such as  C,
FORTRAN, PASCAL etc); This  file describes the ways  in which such  data
can be represented  and manipulated  inside Poplog. It  also deals  with
issues concerning the  use of  native Poplog structures  in an  external
context (e.g. when passed to external functions).
    The actual  processes of  loading  external data/functions,  and  of
calling  external  functions,  etc,  are   not  dealt  with  here;   see

         CONTENTS - (Use <ENTER> g to access required sections)

  1   Representation of External Data

  2   External Pointer-Class Records

  3   Predicates on External Pointers, Etc

  4   Constructing External Pointers

  5   External Pointer Information

  6   Standard External Access/Conversion Procedures
      6.1   Access
      6.2   Conversion

  7   External Pointer Vectorclass ("exptrvec")

  8   Fixed-Address Poplog Structures for External Use

  9   Poplog Structures as External Data

 10   External Function Closures

 11   Miscellaneous

1  Representation of External Data

External data  is represented  by  structure-pointer values.  These  can
either be directly externally-loaded, or can be returned as results from
external functions. Such pointers can  be represented inside Poplog  (in
the first place) by external pointer records, which contain the  pointer
values, and through which the fields in external data structures may  be

Access through  external  pointers  is done  either  with  in-line  code
generated by the Pop-11 syntax form exacc, or with procedures defined by
the syntax form defexacc (both described in REF * DEFSTRUCT).

Note that  external functions/procedures  are themselves  external  data
structures,  and  are  represented  by  external  pointers.  Calling  an
external function is just a special kind of 'data access'.

Since external  structure fields  may  themselves contain  further  data
pointers, the access  code and procedures  applied to external  pointers
allow a field type "exptr"; this  contains an actual pointer value,  and
either returns an external pointer record with that value when accessed,
or assigns the value from an external pointer record into the field when
updated. Moreover, such fields are allowed in Poplog records and vectors
too (making it possible for externally-used Poplog structures to contain
external data pointers).

Thus an external pointer record has  the properties that it can be  used
with external  data access  code/procedures, and  assigned into  "exptr"
fields (another  property  not mentioned  is  that an  external  pointer
record is replaced by its pointer value when passed as an argument to an
external function -- see REF * EXTERNAL).

However, so as not  to restrict these properties  to one kind of  record
(and to allow other information to  be attached to pointer values),  new
classes of Poplog records can be made 'external pointer-class', that is,
to behave directly  as external  pointers. This  is done  by giving  the
attribute "external_ptr" when a record class is defined, to qualify  for
which it must have an "exptr" field  in a fixed place (which is  assumed
to hold the  pointer value  an instance  of the  class represents).  The
above properties then apply to any external pointer-class record.

As mentioned  above,  the  syntax constructs  exacc  and  defexacc  (see
REF * DEFSTRUCT)  are  available  for  accessing  or  updating   through
external pointer-class records  (these constructs  respectively use  the
Poplog Virtual  Machine  instruction sysFIELD  (REF * VMCODE),  and  the
procedure cons_access (REF * KEYS)). A small number of standard external
access procedures are given below.

2  External Pointer-Class Records

As described above,  an external  pointer-class record is  one that  can
have external access code applied to it, and which can be assigned  into
"exptr" fields. The  basic such  class of record  in the  system is  the
external pointer (dataword "external_ptr").

The class_=  of an  external pointer  gives equality  with any  external
pointer-class  record  with  the  same  pointer  value.  The   procedure
external_ptr_props can  be  used  to  attach an  arbitrary  item  to  an
external pointer; this props value defaults to false, and is used by its
class_print, e.g.


when false, or

        <external_ptr props>

when not, etc.

Other built-in external pointer-class records are the "exptr_mem"  class
(see below), and "XptDescriptor" used by the Poplog X facilities.

User classes may be constructed by defclass or conskey by supplying  the
special attribute "external_ptr" (see REF * DEFSTRUCT, * KEYS); to  have
this attribute, the  class must  have an  "exptr" field  at the  pointer
position  ('pointer  position'  is   explained  under  Format  of   Data
Structures in REF * DATA). For example,

        defclass xptr [external_ptr]
            {     xptr_props :full,
              >-> xptr_ptr   :exptr

gives a record class equivalent to  an ordinary external pointer. If  in
addition, the class  starts with  a "full"  field (as  in the  example),
external_ptr_props can be used to access or update that field.

3  Predicates on External Pointers, Etc

isexternal_ptr(item) -> bool                                 [procedure]
        Returns true if  item is an  (ordinary) external pointer,  false

isexternal_ptr_class(item) -> key                            [procedure]
        Returns datakey(item)  if  item  is  an  external  pointer-class
        structure (as described above), or false otherwise.

is_valid_external_ptr(exptrclass) -> bool                    [procedure]
        Given any external pointer-class record exptrclass, returns true
        if its pointer value is a valid address, or false otherwise.

        Note that  the  main purpose  of  this procedure  is  to  detect
        pointers which have standard error-return values like 0 and  -1.
        It merely  checks  the value  to  be  roughly in  the  range  of
        allowable addresses (and  a true return  doesn't guarantee  that
        the pointer is actually usable).

is_null_external_ptr(exptrclass) -> bool                     [procedure]
        Returns true if exptrclass is  an external pointer class  object
        with an  external  address  field  containing  zero,  and  false
        otherwise. Mishaps  if exptrclass  is  not an  external  pointer
        class record. Many external routines return a NULL as a  pointer
        field address as an error-return value. Also, external  pointers
        can refer  to things  other than  addresses, in  which case  the
        is_valid_external_ptr does not always perform the desired test.

4  Constructing External Pointers

consexternal_ptr() -> exptr                                  [procedure]
        Constructs and returns a new external pointer, with a null (i.e.
        zero) pointer value.

5  External Pointer Information

external_ptr_props(exptrclass) -> props                      [procedure]
props -> external_ptr_props(exptrclass)
        For exptrclass  an  external  pointer, returns  or  updates  its
        associated props item,  which may be  anything (and defaults  to
        false, except that the props of an externally-loaded pointer  is
        initialised to  the  external symbol  name  under which  it  was
        loaded, as a string).

        Otherwise, exptrclass must be  an external pointer-class  record
        that starts with a "full" field (i.e. its spec_list argument  to
        conskey begins

            [full exptr ... ]

        etc); this field  is then  accessed or updated.  (Note that  the
        applicability of external_props_props to  an item can be  tested
        for using class_attribute on its key, i.e.

            class_attribute(datakey(exptrclass), "external_ptr_props")

        returns true if it can be applied and false otherwise.)

        (Note that  "exptr_mem" structures  do  NOT have  an  associated
        props field, and cannot be used with external_props_props.)

6  Standard External Access/Conversion Procedures

Note that the procedures * move_bytes  and * set_bytes may also be  used
with external pointer-class structures.

6.1  Access
exacc_ntstring(exptrclass) -> string                         [procedure]
string -> exacc_ntstring(exptrclass)
        Accesses  as   a  Poplog   string,   or  updates   from   one, a
        Null-Terminated sequence of bytes through (the pointer value of)
        an external pointer-class record exptrclass.

        That is, the base procedure  returns a string consisting of  all
        bytes upto (but not including) the first 0 byte. (N.B. To  allow
        a null-pointer-terminated array of  strings to accessed  easily,
        termin is returned for a null pointer.)

        The updater writes the bytes of the given string into memory  at
        the pointer, and  adds a 0  onto the end.  NEEDLESS TO SAY,  the
        area pointed  to  must be  large  enough to  contain  all  bytes
        written including the 0 (otherwise corruption of external memory
        and/or the Poplog system will result).

6.2  Conversion
exval_to_popobj(exptr1) -> exptr2                            [procedure]
item1 -> exval_to_popobj() -> item2
        This procedure is intended for converting an arbitrary value  in
        and out of an "exval" field.

        The base procedure takes the output from an "exval" field,  i.e.
        an external pointer exptr1, and just returns a copy of it exptr2
        (it must copy it since the field access mechanism will generally
        pass it a constant external pointer record).

        The updater  takes a  value item1  to be  made the  value of  an
        "exval" field, and returns a result item2 which will actually be
        assigned into it. Except in the case where item1 is a  structure
        requiring a fixed-address copy,  it is returned unchanged,  i.e.
        item2 == item1.

        On the other hand,  item2 is a fixed-address  copy of item1  for
        any structure  whose  direct address  will  be the  value,  i.e.
        anything which satisfies

            class_attribute(datakey(item1), "external_noconv")

        and which is  not already fixed-address.  (This basically  means
        anything that  isn't  an  external  pointer  class,  hasn't  the
        "external_deref"  attribute,  and  isn't  a  (big)integer   or a

        If a fixed-address  copy is  made, it  is cached  in a  property
        against item1 so that assigning item1 into the field again  will
        return the cached copy. (This also ensures that providing  item1
        remains non-garbage, then so will the fixed copy.)

exval_to_string(exptr) -> string_or_false                    [procedure]
string1_or_false -> exval_to_string() -> string2_or_false
        This procedure is intended for converting a string value in  and
        out of an "exval" field.

        The base procedure takes the output from an "exval" field,  i.e.
        an external pointer exptr,  and uses exacc_ntstring to  access a
        null-terminated string from it, returning the string or false if
        the  pointer  was  0.  (Note  that  Poplog  strings  are  always

        The updater takes a string or false  to be made the value of  an
        "exval" field, where false means a 0 pointer. If string_or_false
        is false, then false is  returned (since false assigned into  an
        "exval" field will convert to 0).

        Otherwise,   string1   is   returned   unchanged   if    already
        fixed-address,  or  a  fixed-address  copy  is  made;  as   with
        exval_to_popobj, this is cached in a property against string1 so
        that assigning  string1 into  the field  again will  return  the
        cached copy. (And ensures  that if string1 remains  non-garbage,
        so will the fixed copy.)

integer_to_boolean(int) -> bool                              [procedure]
item -> integer_to_boolean() -> int
        Converts between a (simple) integer  field value and a  boolean,
        i.e. any nonzero integer translates to true, and 0 to false. The
        updater translates any true item to 1, and false to 0.

7  External Pointer Vectorclass ("exptrvec")

A standard vectorclass with element  type "exptr" is provided for.  This
has dataword "exptrvec", and is defined simply by

        defclass exptrvec :exptr;

which gives all the usual procedures for a vectorclass.

The  generic   data  structure   procedures  described   in   REF * DATA
(datalength, appdata, explode,  fill, copy, etc)  are all applicable  to
"exptr" vectors, as are the generic vector procedures  (initvectorclass,
move_subvector, sysanyvecons, etc) also described in that file.

isexptrvec(item) -> bool                                     [procedure]
        Returns true if item is an exptr vector, false if not.

consexptrvec(exptrclass1,  ..., exptrclassN, N)              [procedure]
                                                -> exptrvec
        Construct and return  an exptr  vector with  its elements  taken
        from the  next  N external  pointer-class  records on  the  user
        stack, where the first item on the stack will be at the  highest
        subscript value.

initexptrvec(n) -> exptrvec                                  [procedure]
        Constructs and  returns  an  exptr  vector  of  length  n  whose
        elements  are  all   initialised  to  0   pointers.  (See   also
        initvectorclass in REF * DATA.)

destexptrvec(exptrvec) -> (exptr1, ..., exptrN, N)           [procedure]
        Destruct the given exptr vector,  i.e. puts all its elements  on
        the stack, together  with its  length (in other  words does  the
        opposite of  consexptrvec,  except that  what  you get  out  are
        always standard external pointer records, even though the  input
        arguments when constructing  may be  any external  pointer-class

subscrexptrvec(N, exptrvec) -> exptr                         [procedure]
exptrclass -> subscrexptrvec(N, exptrvec)
        Returns  or  updates  the  N-th  element  of  the  exptr  vector
        exptrvec. Since subscrexptrvec is the class_apply of  exptrvecs,
        these can also be called as

                exptrvec(N) -> exptr
                exptrclass -> exptrvec(N)

        (Note that you can assign any external pointer-class record into
        an exptrvec element, but what you  get out is always a  standard
        external pointer record.)

exptrvec_key -> key                                           [constant]
        Hold the key structures for exptr vectors (see REF * KEYS).

8  Fixed-Address Poplog Structures for External Use

The sections above deal with the representation of external data  inside
Poplog; this section considers the use of native Poplog data  structures
externally, i.e,  when  passed  to external  functions  etc.  The  major
problem likely to arise in this context concerns garbage collection, for
the following reasons:

    (1) The Poplog garbage collector will discard (and reuse the  memory
        occupied by)  any  structure  which  has  no  references  to  it
        remaining inside Poplog;

    (2) When  an  (ordinary)  structure  is  retained  by  the   garbage
        collector, it may be moved to  a new location (i.e. its  address
        may change);

    (3) The garbage collector has no  knowledge of references to  Poplog
        structures stored by external functions, and so cannot take them
        into account when deciding  to retain structures  in (1), or  to
        correct the addresses they refer to in (2).

However, since garbage collections can only occur while executing Poplog
code, the  above present  no  problem PROVIDING  the external  use  of a
structure always finishes before control is ever returned to Poplog.

    The problem  occurs only  when  an external  agent  in some  way  or
another retains a structure during a return to Poplog execution  (during
which a garbage collection happens), and then later expects to use  that
structure again (at which time the externally-stored reference may  have
become invalid,  either  through  (1)  or (2)).  That  is,  the  control

        external code  -->  Poplog  -->  external code
                          (GC caused)

must occur  (either  by returning  to  Poplog and  then  re-calling  the
external function,  or  by  calling-back to  Poplog  from  the  external
function and then returning from the callback, etc).

    While this scenario will  not arise in  many programs (e.g.  because
data is always passed anew to an  external function on each call, so  it
never  relies  upon  stored  references),  in  other  programs  it  will
difficult or impossible to avoid.  Poplog therefore provides a  solution
to (2)  by making  possible the  creation of  fixed-address  structures,
whose memory locations do not change;  this is done with the  procedures
described below.  In  all  cases,  they  call  an  ordinary  constructor
procedure, but in such  a way that the  result is allocated memory  in a
separate part  of the  heap reserved  for fixed-address  objects --  and
where  its  address  is  guaranteed  to  remain  unchanged  by   garbage

    However, while  this  solves  (2),  the  potential  problem  of  (1)
remains: that  is, external  code  could have  stored references  to  an
(albeit fixed-address)  structure  for  which  there  are  no  remaining
references inside  Poplog  itself (and  so  a garbage  collection  would
destroy the  object). The  procedures below  therefore also  provide  an
option to automatically hold the structures they create on a global list
(from which,  when  no longer  required,  they  can be  freed  with  the
procedure free_fixed_hold). A  given program only  need use this  option
for structure(s) for which it does not anyway retain suitable references
itself (e.g. in variables or other data structures).

cons_fixed(arg_1, ..., argN, key, hold) -> fixed_struct      [procedure]
cons_fixed(arg_1, ..., argN, key)       -> fixed_struct
        Given a record  or vector  class key key,  calls the  class_cons
        procedure of  key  so  that  the  result  fixed_struct  will  be
        fixed-address. arg_1, ...,  argN are the  arguments required  by
        the class_cons procedure (see REF * KEYS).

        hold is an  optional boolean  argument which  if true  specifies
        that fixed_struct should  be retained  on a  global list  (until
        freed with free_fixed_hold);  if omitted, it  defaults to  false
        (i.e. don't put on hold).

        For example,

             cons_fixed(1, [], pair_key, true) -> fixed_pair;

        will call conspair (and hold the pair), while

             cons_fixed('a', 'b', 'c', 3, vector_key) -> fixed_vector;

        will call consvector (and not hold the vector).

init_fixed(N, vec_key, hold) -> fixed_vec                    [procedure]
init_fixed(N, vec_key)       -> fixed_vec
        Given a vector class key vec_key, calls the class_init procedure
        of vec_key so that the result fixed_vec will be fixed-address; N
        is the  length of  vector  required. hold  is  the same  as  for
        cons_fixed. E.g.

            init_fixed(10, string_key) -> fixed_string;

        will call inits.

copy_fixed(struct, hold) -> fixed_struct                     [procedure]
copy_fixed(struct)       -> fixed_struct
        Calls copy  on struct  so  that the  copy fixed_struct  will  be
        fixed-address. hold is the same as for cons_fixed.

is_fixed(struct) -> bool                                     [procedure]
        Returns true if the address of the structure struct is fixed, or
        false if it can change.

        This will  be true  if either  (a) struct  is an  object in  the
        system (in which  case isinheap(struct) will  be false), or  (b)
        struct was produced by cons_fixed, init_fixed or copy_fixed.

        A typical use  of this  procedure would be  in combination  with
        copy_fixed to  ensure a  fixed-address version  of a  structure,

                unless is_fixed(struct) then
                    copy_fixed(struct) -> struct

        Note that structures locked in by sys_lock_system become part of
        the system,  and  are  therefore  fixed-address,  whereas  those
        locked with sys_lock_heap  are not (since  they can be  unlocked
        with sys_unlock_heap).

free_fixed_hold(item)                                        [procedure]
        For a  item a  fixed-address structure  produced by  one of  the
        above  procedures  with  a  true  hold  argument,  removes   the
        reference to that structure  from the fixed  hold list (for  any
        other item it does nothing).

sys_grbg_fixed(fixed_struct)                                 [procedure]
sys_grbg_fixed(fixed_struct1, ..., fixed_structN, N)
        This procedure frees the memory occupied by the N  fixed-address
        structures given as arguments, where the first form is the  just
        same as

            sys_grbg_fixed(fixed_struct, 1)

        in the second form. free_fixed_hold is also called automatically
        for each structure.

        Freeing the memory occupied  by a fixed-address structure  makes
        it available for re-use  in any later fixed-address  allocation.
        USE THIS PROCEDURE WITH  EXTREME CARE: as soon  as you call  it,
        the structures freed will become internal objects, and cease  to
        be whatever they were previously.

9  Poplog Structures as External Data

While any Poplog structure can be used as external data in the sense  of
passing it  to an  external  function, the  procedure  fill_external_ptr
allows any  fixed-address Poplog  structure to  be used  as an  external
structure within Poplog itself (e.g. with exacc, etc).

A frequent  need in  this  respect is  to (a)  create  a pointer  to  an
uninitialised area  of memory,  (b) pass  the pointer  to some  external
function to fill value(s) into the memory, and (c) on return to  Poplog,
access the  value(s) with  exacc, etc.  One  way of  doing this  is  for

        exptr_init_fixed(nbytes, string_key) -> exptr

i.e. create a fixed-address  string and a  separate external pointer  to

However, the system provides a  more direct method with the  "exptr_mem"
structure. This  is a  fixed-address, external  pointer-class  structure
that combines  both an  area of  memory and  a pointer  to it.  (And  if
desired, the  memory it  occupies can  be freed  directly after  use  by
calling sys_grbg_fixed.)

initexptr_mem(Nbytes, hold) -> exptr_mem                     [procedure]
initexptr_mem(Nbytes)       -> exptr_mem
        Returns an "exptr_mem" structure for  an area of memory of  size
        Nbytes bytes. This  is a  fixed-address, external  pointer-class
        structure, whose pointer points into  itself at the memory  area
        of the required size.  The hold argument  is as for  cons_fixed,

        Note that if Nbytes is greater than the size of a machine  word,
        the  pointer  is  guaranteed  to  be  doubleword  aligned  (i.e.
        suitable for use with machine double floats). datalength applied
        to an exptr_mem returns its size, i.e. Nbytes.

        As described in REF * DEFSTRUCT,  an interface to  initexptr_mem
        for  use   with  typespec   definitions  is   provided  by   the
        EXPTRINITSTR macro, e.g.

                p_typespec bytearray :byte[10];
                vars bytearray = EXPTRINITSTR(:bytearray);
                bytearray =>
                ** <exptr_mem>

        exacc can  then  be used  with  this  to access  or  update  the
        underlying memory:

                `a` -> exacc bytearray[3];
                exacc bytearray[3] =>
                ** 97

        (N.B. For  efficiency's sake,  an exptr_mem  structure does  NOT
        have an external_ptr_props field.)

isexptr_mem(item)                                            [procedure]
        Returns true if item is an "exptr_mem" structure, false if not.

fill_external_ptr(fixed_struct, exptr) -> exptr              [procedure]
        Given any fixed-address structure  fixed_struct (i.e. for  which
        is_fixed is  true),  makes the  pointer  value of  the  external
        pointer exptr be fixed_struct.

        Note that, since the pointer value of an external pointer is  an
        external reference as  far as garbage  collection is  concerned,
        the problem of  (1) in  the section  above potentially  applies,
        i.e. if fixed_struct  has no other  references remaining in  the
        system then a garbage collection will destroy it.

        For  this   reason  fixed_struct   is  also   assigned  to   the
        external_ptr_props of  exptr, which  at  least ensures  that  if
        exptr does not become garbage then neither will fixed_struct.

exptr_cons_fixed(arg1, ..., argN, key, hold) -> exptr        [procedure]
exptr_cons_fixed(arg1, ..., argN, key)       -> exptr
exptr_init_fixed(N, vec_key, hold) -> exptr                  [procedure]
exptr_init_fixed(N, vec_key)       -> exptr
exptr_copy_fixed(struct, hold) -> exptr                      [procedure]
exptr_copy_fixed(struct)       -> exptr
        These three autoloadable procedures call cons_fixed,  init_fixed
        and copy_fixed respectively, and then use fill_external_ptr (see
        above) to return an  external pointer to  the new fixed  address
        structure. For example, exptr_cons_fixed could be defined as:

          define exptr_cons_fixed(/* args on stack*/) -> ptr_to_struct;
              lvars new_struct, new_ptr, ptr_to_struct;

              cons_fixed(/* args on stack */) -> new_struct;
              consexternal_ptr() -> new_ptr;
              fill_external_ptr(new_struct, new_ptr) -> ptr_to_struct;

        Note that  exptr will  have  the new  vector  or record  in  its
        external_ptr_props field.

10  External Function Closures

An external  function closure  (dataword "exfunc_closure")  is a  record
that allows an argument value (an arbitrary Poplog item) to be  `frozen'
into an external function.

    Such a record contains a small  piece of executable code, a  pointer
to a base external function, and a frozen argument value. The code  part
is at the pointer position of the  structure, and so when passed out  as
an external argument  it represents an  executable object; when  called,
its action  is to  deposit the  frozen argument  value in  the  standard
external variable  *pop_exfunc_closure_arg, and  then hand  over to  the
base function (which gets whatever  actual arguments were passed to  the

    The principal use of  these closures is to  surmount the problem  of
externally-defined callbacks that make no allowance for `client' data to
be passed  through  to the  called-back  function. --  see  the  section
External Callback in REF * EXTERNAL for more details.

    Note that external function closures are always fixed-address;  also
note that they are NOT  external pointer-class structures (they  contain
actual code,  not pointers  to code).  Since they  are passed  as  their
structure pointers when  given as arguments  to external functions,  the
net result  is the  same in  that  context; however,  if you  wanted  to
execute a closure with exacc, etc  you would first have to construct  an
external pointer to it (using fill_external_ptr, see above).

make_exfunc_closure(F_exptrclass, arg_item, hold) -> efc     [procedure]
        Given an external pointer-class record F_exptrclass pointing  to
        an external function F, constructs an external function  closure
        efc for that function and the argument arg_item.

        efc is always  a fixed-address structure;  the hold argument  is
        the same as for  the other fixed-address constructors  described
        in the last section,  i.e. true if efc  should be retained  on a
        global list  until freed.  (Unlike those  other procedures,  the
        hold argument is mandatory.)

        The action of efc  when executed as an  external function is  to
        place arg_item in the external variable  *pop_exfunc_closure_arg
        and then call  F with  whatever arguments were  supplied to  the
        call of  the closure  --  for details,  see above  and  External
        Callback in REF * EXTERNAL.

is_exfunc_closure(arg) -> bool                               [procedure]
        Given an arbitrary  object arg, this  procedure returns true  if
        arg is an external function closure, and false otherwise.

exfunc_export(proc, flags, hold) -> efc                      [procedure]
        This procedure takes a Poplog procedure and returns an  external
        function closure which  invokes the Poplog  procedure using  the
        'generic' callback  stub described  in REF * EXTERNAL  (External
        Callback). proc is any valid first argument to pop_call,  namely
        a procedure or an identifier or ref record (which must contain a
        procedure at call time). flags is a bit mask (ie an integer)  of
        PEF flags to  be or-ed  with pop_external_flags  before proc  is
        called. This allows  any of the  user-assignable PEF flags  (see
        REF * EXTERNAL) to be set  for a particular exported  procedure.
        Constants defining  suitable values  for  flags are  defined  in
        INCLUDE * EXTERNAL_FLAGS.  hold  is  the   same  as  for   other
        fixed-address structures.

        When efc  is called  (from  external code),  it ors  flags  with
        pop_external_flags and  then  invokes  proc,  passing  a  single
        external pointer record to it.  This external pointer points  to
        the array of  args passed  to efc,  where each  arg occupies  an
        "exptr" (or  "exval")  -sized  field.  When  proc  returns,  efc
        dereferences the pointer to recover the first arg and returns it
        as result. So if proc wishes to return a result, it can do so by
        modifying the first arg position.

11  Miscellaneous

external_NULL() -> <NULL>                                    [procedure]
        This procedure returns a constant  value of machine zero,  which
        prints as <NULL>. It is provided solely to enable the assignment
        of a null value into  a "full" field of  a data structure to  be
        processed  externally  (e.g.   for  terminating   a  vector   of
        null-terminated strings in Unix, etc).  IT MUST NOT BE USED  FOR
        ANY OTHER PURPOSE: iscompound  is true for  this value, but  any
        procedure in the system which  attempts to inspect its key  will
        cause an access violation.

        In particular, you should  not attempt to  cache the value  this
        procedure returns by assigning it  to a variable or a  constant;
        use a call of external_NULL() every time the value is needed.

        (Note that  null_external_ptr is  a null  pointer for  assigning
        into an "external_ptr" field.)

null_external_ptr -> exptr                                    [constant]
        This variable contains an  external pointer with  a zero in  its
        external address field. ie. a  pointer to <NULL>. Note that  you
        cannot modify this address field. The null_external_ptr constant
        is provided  as  a  convenience  so  that  libraries  can  use a
        standard exptr  when they  want to  pass an  external  routine a
        pointer containing NULL.

external_ptr_key                                              [constant]
exptr_mem_key                                                 [constant]
        Constants holding  the  key  structures  for  ordinary  external
        pointers and "exptr_mem" structures.

--- C.all/ref/external_data
--- Copyright University of Sussex 1994. All rights reserved.