home *** CD-ROM | disk | FTP | other *** search
/ Der Mediaplex Sampler - Die 6 von Plex / 6_v_plex.zip / 6_v_plex / DISK5 / DOS_42 / XLIB30.ZIP / EASYX.DOC < prev    next >
Text File  |  1993-12-20  |  22KB  |  722 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.                             EASYX PROGRAMMER'S MANUAL
  31.                                    VERSION 1.0
  32.  
  33.                               (DOS Extender Library)
  34.  
  35.                                 TechniLib Company
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.                     Copyright 1993, by TechniLib (TM) Company
  60.                                All Rights Reserved
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.                                 TABLE OF CONTENTS
  68.  
  69.  
  70.   CHAPTERS
  71.                                                                       Page
  72.   1. Introduction                                                        1
  73.   2. Initialization of EASYX                                             2
  74.   3. Memory Management                                                   3
  75.   4. Memory-Mapped Input/Output                                          5
  76.   5. File Management                                                     6
  77.  
  78.  
  79.   EXAMPLES
  80.                                                                       Page
  81.   1.  EASYX Memory Management                                            4
  82.   2.  EASYX File Management                                              9
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.                                  1. Introduction
  90.  
  91.  
  92.        EASYX is a DOS extender library intended for programmers who are
  93.   unfamiliar with assembly language.  EASYX allows real-mode high-level
  94.   languages to perform transfers between extended memory and conventional memory
  95.   or between extended memory and disk files.  Many programmers need a DOS
  96.   extender merely to afford real-mode languages access to extended memory.  In
  97.   such cases, EASYX is a simple and powerful alternative.
  98.        EASYX is actually little more than a real-mode interface to XLIB.  The
  99.   source code for EASYX is supplied in the XLIB archive (EASYX.ASM).  Assembly
  100.   language programmers may be interested in examining this code to learn more
  101.   about working with XLIB.  This code has been assembled and then linked with
  102.   XLIB to create EASYX.LIB.
  103.        The EASYX library is provided in two formats - one for Microsoft
  104.   languages and the other for Borland languages.  Microsoft programmers should
  105.   use EASYX.LIB.  Borland programmers should use EASYXB.LIB.  The respective C
  106.   header files are EASYX.H and EASYXB.H.  Programmers using other languages will
  107.   have to write their own prototypes for EASYX procedures.  These prototypes
  108.   should declare all EASYX procedures as far procedures conforming to the PASCAL
  109.   calling and naming convention.
  110.        Most EASYX procedures can return error codes.  These are always XLIB
  111.   error codes.  The codes are explained in the XLIB documentation.
  112.  
  113.  
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.                                         1
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.                             2. Initialization of EASYX
  154.  
  155.  
  156.        EASYX requires no initialization; however, the XLIB library which has
  157.   been linked with EASYX must be initialized before any EASYX procedures are
  158.   called.  XLIB is initialized by calling INITXLIB.  INITXLIB returns an error
  159.   code in DX:AX; therefore, it should be implemented as a long integer function.
  160.   The following code demonstrates initialization in Microsoft C:
  161.  
  162.  
  163.   extern long __far __pascal INITXLIB(void);
  164.  
  165.   void main(void)
  166.   {
  167.     long errcode;
  168.     errcode = INITXLIB();
  169.     if(errcode != 0)
  170.     {
  171.       printf("Initialization error:  %lX",errcode);
  172.       return;
  173.     }
  174.   .
  175.   .
  176.   .
  177.  
  178.        An explanation of possible error codes is explained in the XLIB
  179.   documentation.  The most common error is caused by insufficient conventional
  180.   memory.  INITXLIB will attempt to allocate a small amount of conventional
  181.   memory through DOS; however, many high-level languages automatically claim all
  182.   available DOS memory, even though only a small percentage of this memory may
  183.   actually be used.  The programmer must therefore release a portion of this
  184.   memory back to DOS before calling INITXLIB.  This process is illustrated for
  185.   the Borland Turbo Assembler in the file EXAMP1B.ASM, and for Microsoft BASIC
  186.   in the XLIB documentation.  C and C++ programmers need not be concerned with
  187.   this matter because these languages allocate DOS memory dynamically.
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206.  
  207.  
  208.  
  209.  
  210.                                         2
  211.  
  212.  
  213.  
  214.  
  215.  
  216.  
  217.                                3. Memory Management
  218.  
  219.  
  220.        High-level languages confined to real mode are incapable of addressing
  221.   extended memory; consequently, such languages must communicate with extended
  222.   memory through buffers in conventional memory.  EASYX provides several
  223.   procedures to facilitate this process.
  224.        Extended memory should always be allocated with the procedure XMALLOC
  225.   before it is addressed.  Attempted transfers to or from unallocated memory may
  226.   lead to page faults (exception 14), or to corruption of resident software.
  227.   XMALLOC returns a long integer error code; therefore, it should be implemented
  228.   as a long integer function.  The following Microsoft C prototype shows the
  229.   structure of XMALLOC:
  230.  
  231.  
  232.   extern long __far __pascal XMALLOC(long nobytes, long __far *address,
  233.      long __far *size, long __far *handle);
  234.  
  235.   where:
  236.   nobytes  = The requested size for the extended memory block.
  237.   *address = A far pointer to a long integer variable which will receive the
  238.              linear address of the allocated block.
  239.   *size    = A far pointer to a long integer variable which will receive the
  240.              actual size of the allocated extended memory block.  The actual
  241.              size will always be at least as large as the requested size.
  242.   *handle  = A far pointer to a long integer variable which will receive a
  243.              handle for the allocated extended memory block.  The handle must
  244.              be used to release the block.
  245.  
  246.  
  247.        An extended memory block can be released with the XFREE procedure.  XFREE
  248.   is also a long integer function which returns an error code.  The C prototype
  249.   of this procedure is:
  250.  
  251.  
  252.   extern long __far __pascal XFREE(long handle);
  253.  
  254.   where:  handle = The handle assigned to the block by XMALLOC.
  255.  
  256.  
  257.        All extended memory is automatically released upon program termination;
  258.   consequently, XFREE will not be necessary for most programs.
  259.        Transfers between conventional memory and extended memory can be
  260.   accomplished with the MOVMEM procedure.  The prototype for MOVMEM is:
  261.  
  262.  
  263.   extern void __far __pascal MOVMEM(long destadr, long sourceadr,long nobytes);
  264.  
  265.   where:
  266.   destadr   = The linear address of the destination memory.
  267.   sourceadr = The linear address of the source memory.
  268.   nobytes   = The number of bytes to be transferred.
  269.  
  270.  
  271.  
  272.  
  273.  
  274.                                         3
  275.  
  276.  
  277.  
  278.  
  279.  
  280.  
  281.        MOVMEM may actually be used to transfer memory between any source and
  282.   destination.  The destination block and source block may also be overlapped.
  283.        MOVMEM transfers are faster than XMS or INT 15H because MOVMEM uses 32-
  284.   bit instructions.  MOVMEM also exposes to less risk of losing an interrupt.
  285.        Real-mode programs use segment addresses instead of linear addresses.
  286.   EASYX includes a procedure called LINADR which computes linear addresses from
  287.   segment addresses.  The prototype for LINADR is:
  288.  
  289.  
  290.   extern long __far __pascal LINADR(void __far *ptr);
  291.  
  292.  
  293.        The following program illustrates the usage of the procedures presented
  294.   in this section:
  295.  
  296.  
  297.   Example 1:  EASYX Memory Management
  298.   _____________________________________________________________________________
  299.   #include <stdio.h>
  300.   #include <easyx.h>
  301.  
  302.   void main(void)
  303.   {
  304.     long errcode, nobytes, xaddress, xsize, xhandle, bufferaddress;
  305.     long buffer[1024];            /*4k buffer*/
  306.  
  307.     errcode = INITXLIB();         /*Initialize XLIB*/
  308.     if(errcode != 0)
  309.     {
  310.       printf("Library initialization error:  %lX\n",errcode);
  311.       return;
  312.     }
  313.  
  314.     nobytes = 0x10000;            /*Allocate 64k of extended memory*/
  315.     errcode = XMALLOC(nobytes, &xaddress, &xsize, &xhandle);
  316.     if(errcode != 0)
  317.     {
  318.       printf("Memory allocation error:  %lX\n",errcode);
  319.       return;
  320.     }
  321.  
  322.     bufferaddress = LINADR(buffer);          /*Get linear address of buffer*/
  323.     MOVMEM(xaddress, bufferaddress, 4096);   /*Transfer buffer to extended*/
  324.     MOVMEM(bufferaddress, xaddress, 4096);   /*Transfer extended to buffer*/
  325.  
  326.     errcode = XFREE(xhandle);     /*Release the extended memory*/
  327.     if(errcode != 0)
  328.       printf("Memory release error:  %lX\n",errcode);
  329.   }
  330.   _____________________________________________________________________________
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338.                                         4
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.                           4. Memory-Mapped Input/Output
  346.  
  347.  
  348.        On 386 and higher machines, memory reads and writes may undergo address
  349.   translation so that the address which is physically accessed may be different
  350.   from the address specified in the read/write instruction.  The physically
  351.   accessed address is the "physical address."  The address specified by the
  352.   read/write instruction is the "logical address."  Normally programmers need
  353.   not be concerned with the difference; however, this is not the case if the
  354.   program must access an IO device which maps to a fixed physical address.  In
  355.   such cases, the programmer must obtain a logical address which corresponds to
  356.   the physical address of the IO device.  EASYX contains a procedure called
  357.   MAPIOMEM which will map a specified physical address into a logical address
  358.   space.  MAPIOMEM is a long integer function which returns an error code.  The
  359.   prototype for MAPIOMEM is:
  360.  
  361.  
  362.   extern long __far __pascal MAPIOMEM(long physaddress, long size,
  363.      long __far *logaddress);
  364.  
  365.   where:
  366.   physaddress = The beginning physical address for the IO device.
  367.   size        = The size of the physical address block in bytes.
  368.   *logaddress = A far pointer to a long integer which is to receive the assigned
  369.                 logical address.  Access the IO device at this address.
  370.  
  371.  
  372.        MAPIOMEM will sometimes return errors upon attempts to map physical
  373.   addresses in the first megabyte.
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393.  
  394.  
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401.  
  402.                                         5
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.                                 5. File Management
  410.  
  411.  
  412.        EASYX includes procedures which can transfer data between extended memory
  413.   and files.  These procedures can load files to extended memory or save
  414.   extended memory to files.  They can read and write files either sequentially
  415.   or randomly.
  416.        All EASYX file management routines will receive and return values in a
  417.   contiguous block of memory called a "file control block" (not to be confused
  418.   with DOS file control blocks).  The file control block must be located in
  419.   conventional memory and must have the following form:
  420.  
  421.  
  422.   Field Name   Field Type        Field Description
  423.   ----------   ----------        -----------------
  424.   CONDCODE     DWORD             Condition code from file operation
  425.   FNAME        BYTE[68]          File path and name (zero terminated string)
  426.   FHANDLE      WORD              File handle assigned by DOS
  427.   FPTRMODE     WORD              File pointer mode
  428.   FPTR         DWORD             File pointer
  429.   BLKADR       DWORD             Memory source/destination address
  430.   BLKSIZE      DWORD             Size of source/destination block in bytes
  431.   BUFADR       DWORD             Buffer address (conventional memory)
  432.   BUFSIZE      WORD              Buffer size in bytes
  433.   CONTROL      WORD              Control word
  434.  
  435.  
  436.        CONDCODE is used to return error codes.  CONDCODE should be situated at
  437.   the starting address of the control block.
  438.        FNAME is a zero-terminated ASCII string defining the file path and name.
  439.   There cannot be more than 68 characters in this string, including the
  440.   termination character.
  441.        BLKADR and BLKSIZE define the source/destination memory block for the
  442.   transfer.  This block may be in either conventional or extended memory.
  443.   BLKADR is a linear address.
  444.        XLIB uses DOS to access the disk.  DOS cannot read or write to extended
  445.   memory; consequently, a conventional memory buffer must be set up for the DOS
  446.   transfers.  File management routines shift to protected mode to perform
  447.   transfers between the buffer and the source/destination memory.  BUFADR and
  448.   BUFSIZE define the conventional memory buffer.  BUFADR is a linear address.
  449.        For fastest transfers, the memory block and the buffer should be DWORD
  450.   aligned and should have sizes equal to an integer multiple of four.
  451.        FPTR and FPTRMODE specify the file pointer setting to be used before
  452.   intrafile transfers to or from the disk.  FPTRMODE specifies how FPTR is to be
  453.   interpreted.  The following values are valid for FPTRMODE:
  454.  
  455.  
  456.   FPTRMODE    FPTR Interpretation
  457.   --------    -------------------
  458.   0           Unsigned offset from the beginning of the file
  459.   1           Signed offset from the current file pointer
  460.   2           Signed offset from the end of the file
  461.   3           FPTR is ignored.  Use current file pointer (sequential mode)
  462.  
  463.  
  464.  
  465.  
  466.                                         6
  467.  
  468.  
  469.  
  470.  
  471.  
  472.  
  473.        CONTROL is not used in the present version of EASYX.  All bits in control
  474.   should be set to zero.
  475.        Since these routines perform disk operations, special precautions should
  476.   be taken to ensure that parameters in the file control block are properly
  477.   defined before performing calls.  In particular, one should always make sure
  478.   that the source/destination memory block and the conventional memory buffer
  479.   are properly defined.  A safe rule is to simply set the buffer size to zero
  480.   because this forces EASYX to supply a buffer when opening or creating the
  481.   file.
  482.        All EASYX file routines recieve a single argument; namely, the far
  483.   address of the control block.  Prototypes for the file routines are:
  484.  
  485.  
  486.   extern void __far __pascal XFCREATE(void __far *controlblock);
  487.   extern void __far __pascal XFOPEN(void __far *controlblock);
  488.   extern void __far __pascal XFCLOSE(void __far *controlblock);
  489.   extern void __far __pascal XFLOAD(void __far *controlblock);
  490.   extern void __far __pascal XFSAVE(void __far *controlblock);
  491.   extern void __far __pascal XFREAD(void __far *controlblock);
  492.   extern void __far __pascal XFWRITE(void __far *controlblock);
  493.  
  494.  
  495.        The following is a detailed explanation of each of these procedures:
  496.  
  497.  
  498.   XFCREATE (Create File)
  499.   Purpose:  Create and open a new file of specified name in specified directory.
  500.   Control Block at Call:  FNAME = file path and name.
  501.   Control Block at Return:  CONDCODE = error code.  If CONDCODE = 0, then
  502.   FHANDLE = file handle assigned by DOS.  If the procedure is called with
  503.   BUFSIZE = 0, then EASYX will set BUFADR and BUFSIZE to its own internal
  504.   buffer.
  505.   Details:
  506.      If the file already exists, then it will be truncated to zero length.
  507.      Files created by this routine will be given both read and write access.
  508.  
  509.   XFOPEN (Open File)
  510.   Purpose:  Open existing file of specified name in specified directory.
  511.   Control Block at Call:  FNAME = file path and name.
  512.   Control Block at Return:  CONDCODE = error code.  If CONDCODE = 0, then
  513.   FHANDLE = file handle assigned by DOS.  If the procedure is called with
  514.   BUFSIZE = 0, then EASYX will set BUFADR and BUFSIZE to its own internal
  515.   buffer.
  516.   Details:  The file is opened for both read and write access.
  517.  
  518.   XFCLOSE (Close File)
  519.   Purpose:  Close previously opened file.
  520.   Control Block at Call:  FHANDLE = file handle.
  521.   Control Block at Return:  CONDCODE = error code.
  522.  
  523.  
  524.  
  525.  
  526.  
  527.  
  528.  
  529.  
  530.                                         7
  531.  
  532.  
  533.  
  534.  
  535.  
  536.  
  537.   XFSAVE (Save File)
  538.   Purpose:  Create file with contents equal to specified memory block.
  539.   Control Block at Call:  FNAME = file path and name.  BLKADR/BLKSIZE = address
  540.   and size of memory block to provide file contents.  BUFADR/BUFSIZE = address
  541.   and size of conventional memory buffer.
  542.   Control Block at Return:  CONDCODE = error code.
  543.   Details:
  544.      The file cannot already be open.  The file is both created and closed by
  545.   this routine.
  546.      This routine will replace any previously existing file named FNAME.
  547.      BLKADR/BLKSIZE may define a conventional memory block provided that this
  548.   block is not overlapped by BUFADR/BUFSIZE.
  549.      If this routine is called with BUFSIZE = 0, then EASYX will automatically
  550.   supply a buffer.
  551.  
  552.   XFLOAD (Load File)
  553.   Purpose:  Load file contents to specified memory block.
  554.   Control Block at Call:  FNAME = file path and name.  BLKADR/BLKSIZE = address
  555.   and size of memory block to receive file contents.  BUFADR/BUFSIZE = address
  556.   and size of conventional memory buffer.
  557.   Control Block at Return:  CONDCODE = error code.  If CONDCODE = 0, then
  558.   BLKSIZE = actual number of bytes transferred.
  559.   Details:
  560.      The file cannot already be open.  The file is both opened and closed by
  561.   this routine.
  562.      The value of BLKSIZE as of call is interpreted as an upper limit on the
  563.   number of bytes to transfer.  The entire file is loaded provided that it does
  564.   not contain more than BLKSIZE bytes.
  565.      BLKADR/BLKSIZE may define a conventional memory block provided that this
  566.   block is not overlapped by BUFADR/BUFSIZE.
  567.      If this routine is called with BUFSIZE = 0, then EASYX will automatically
  568.   supply a buffer.
  569.  
  570.   XFWRITE (Write to File)
  571.   Purpose:  Write specified memory block to specified location in open file.
  572.   Control Block at Call:  FHANDLE = file handle.  FPTR/FPTRMODE = file pointer
  573.   setting for beginning of transfer.  BLKADR/BLKSIZE = address and size of
  574.   memory block to provide file contents.  BUFADR/BUFSIZE = address and size of
  575.   conventional memory buffer.
  576.   Control Block at Return:  CONDCODE = error code.
  577.   Details:
  578.      The file must be opened with XFOPEN or XFCREATE before using this routine.
  579.      BLKADR/BLKSIZE may define a conventional memory block provided that this
  580.   block is not overlapped by BUFADR/BUFSIZE.
  581.      Sequential transfers should set FPTRMODE = 3 for fastest execution.
  582.  
  583.   XFREAD (Read From File)
  584.   Purpose:  Load specified memory block from specified location in open file.
  585.   Control Block at Call:  FHANDLE = file handle.  FPTR/FPTRMODE = file pointer
  586.   setting for beginning of transfer.  BLKADR/BLKSIZE = address and size of
  587.   memory block to receive file contents.  BUFADR/BUFSIZE = address and size of
  588.   conventional memory buffer.
  589.   Control Block at Return:  CONDCODE = error code.  If CONDCODE = 0, then
  590.   BLKSIZE = the actual number of bytes transferred.
  591.  
  592.  
  593.  
  594.                                         8
  595.  
  596.  
  597.  
  598.  
  599.  
  600.  
  601.   Details:
  602.      The file must be opened with XFOPEN or XFCREATE before using this routine.
  603.      BLKADR/BLKSIZE may define a conventional memory block provided that this
  604.   block is not overlapped by BUFADR/BUFSIZE.
  605.      Sequential transfers should set FPTRMODE = 3 for fastest execution.
  606.  
  607.  
  608.        The following C program illustrates the usage of some of the above
  609.   procedures:
  610.  
  611.  
  612.   Example 2:  EASYX File Management
  613.   _____________________________________________________________________________
  614.   #include <stdio.h>
  615.   #include <string.h>
  616.   #include <easyx.h>
  617.  
  618.   void main (void)
  619.   {
  620.     int i;
  621.     long errcode, nobytes, xaddress, xsize, handle, arrayaddress;
  622.     int array[100];
  623.     struct xfile fb;                 /*Declare file control block*/
  624.  
  625.     errcode = INITXLIB();            /*Initialize XLIB*/
  626.     if(errcode != 0)
  627.     {
  628.       printf("Library initialization error:  %lX\n",errcode);
  629.       return;
  630.     }
  631.  
  632.     nobytes = 0x10000;               /*Allocate 64k of extended memory*/
  633.     errcode = XMALLOC(nobytes,&xaddress,&xsize,&handle);
  634.     if(errcode != 0)
  635.     {
  636.       printf("Extended memory allocation error:  %lX\n",errcode);
  637.       return;
  638.     }
  639.  
  640.     for(i = 0; i < 100; i++)         /*Put something in array[]*/
  641.       array[i] = i;
  642.  
  643.     arrayaddress = LINADR(array);    /*Compute linear address of array[]*/
  644.  
  645.  
  646.  
  647.  
  648.  
  649.  
  650.  
  651.  
  652.  
  653.  
  654.  
  655.  
  656.  
  657.  
  658.                                         9
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.     fb.condcode = 0;                 /*Set control block to create file*/
  666.     strcpy(fb.fname,"junk.dat");     /*Specify file name*/
  667.     fb.blkadr = arrayaddress;        /*Will transfer array[] to the file*/
  668.     fb.blksize = 200;                /*There are 200 bytes in array[]*/
  669.     fb.bufsize = 0;                  /*Force XLIB to use its internal buffer*/
  670.     XFSAVE(&fb);                     /*Create file and save array[] to it*/
  671.     if(fb.condcode != 0)
  672.     {
  673.       printf("File save error:  %lX\n",fb.condcode);
  674.       return;
  675.     }
  676.  
  677.     XFOPEN(&fb);                     /*Reopen the file*/
  678.     if(fb.condcode != 0)
  679.     {
  680.       printf("File open error:  %lX\n",fb.condcode);
  681.       return;
  682.     }
  683.  
  684.     fb.blkadr = xaddress;         /*Prepare to transfer the file to extended*/
  685.     fb.blksize = 100;             /*Will transfer only 100 bytes*/
  686.     fb.fptrmode = 0;              /*File pointer is relative to start of file*/
  687.     fb.fptr = 100;                /*Set file pointer to 50th element*/
  688.     XFREAD(&fb);                  /*Read last 50 elements to extended*/
  689.     if(fb.condcode != 0)
  690.     {
  691.       printf("File read error:  %lX\n",fb.condcode);
  692.       return;
  693.     }
  694.  
  695.     MOVMEM(arrayaddress,xaddress,100); /*Transfer file contents back to array[]
  696.  
  697.     XFCLOSE(&fb);                      /*Close the file*/
  698.     if(fb.condcode != 0)
  699.     {
  700.       printf("File close error:  %lX\n",fb.condcode);
  701.       return;
  702.     }
  703.  
  704.     errcode = XFREE(handle);           /*Release extended memory*/
  705.     if(errcode != 0)
  706.     {
  707.       printf("Memory release error:  %lX\n",errcode);
  708.       return;
  709.     }
  710.   }
  711.   _____________________________________________________________________________
  712.  
  713.  
  714.  
  715.  
  716.  
  717.  
  718.  
  719.  
  720.  
  721.  
  722.                                         10