APPX is the Premier Development and Runtime Environment for Business Application Software
(Answer) (Category) FAQ's - APPX Software, Inc. : (Category) Trash :
628 - Calling Windows API Functions
By using the PASS and CALL statements in APPX, the WIN32 functions can easily be accessed from APPX subroutines.  This paper discusses some of the issues involved in invoking Windows API functions from within an APPX application.  The TAPI functions act as a link between the computer and the phone system.  In our example, we will demonstrate how to place a simple call to an alpha numeric pager using functions exported by the TAPI API.


Documentation for these functions can be found online at:  http://search.microsoft.com/us/dev/default.asp or is available as part of the MSDN library.


Below is a sample of the documentation you can find at Microsoft's MSDN website:


lineClose - The lineClose function closes the specified open line device.


LONG WINAPI lineClose(
HLINEhLine
);

Parameters

hLine


A handle to the open line device to be closed. After the line has been successfully closed, this handle is no longer valid.


Return Values -  Returns zero if the request succeeds or a negative error number if an error occurs. Possible return values are:


LINEERR_INVALLINEHANDLE
LINEERR_RESOURCEUNAVAIL
LINEERR_NOMEM
LINEERR_UNINITIALIZED
LINEERR_OPERATIONFAILED
LINEERR_OPERATIONUNAVAIL
Remarks


If an application calls lineClose while it still has active calls on the opened line, the application's ownership of these calls is revoked. If the application was the sole owner of these calls, the calls are dropped as well. It is good programming practice for an application to dispose of the calls it owns on an opened line by explicitly relinquishing ownership and/or by dropping these calls prior to closing the line.


If the line was closed successfully, a LINE_LINEDEVSTATE message is sent to all applications that are monitoring the line status of open/close changes. Outstanding asynchronous replies are suppressed.


Service providers may find it useful or necessary to forcibly reclaim line devices from an application that has the line open. This can be useful to prevent an application from monopolizing the line device for too long. If this happens, a LINE_CLOSE message is sent to the application, specifying the line handle of the line device that was closed.


The lineOpen function allocates resources to the invoking application, and applications can be prevented from opening a line if resources are unavailable. Therefore, an application that only occasionally uses a line device (such as for making outgoing calls) should close the line to free resources and allow other applications to open the line.


Requirements


 Windows NT/2000: Requires Windows NT 4.0 SP3 or later.
 Windows 95/98: Requires Windows 95 or later.
 Version: Requires TAPI 1.3 or later.
 Header: Declared in Tapi.h.
 Library: Use Tapi32.lib.

See Also


TAPI 2.2 Reference Overview
Basic Telephony Services Reference
LINE_CLOSE
LINE_LINEDEVSTATE
lineOpen


Notice in the Requirements section that there is an entry called "Library".  In this case, the Library is "Tapi32.lib" - if you substitute ".dll" for ".lib", you know where the lineClose function is located - it's in Tap32.dll. If the data required by the function includes a string, the function name will be terminated with an A (ascii) or U (unicode).  To determine if an A or U is required is to right click on the DLL file in %WINDIR%\system32, and use QuickView to view the function names associated with the DLL.  If you see "lineCloseA" and "lineCloseU", use the "lineCloseA" choice.  If you see "lineClose" all by itself, don't include a suffix.  To call the lineClose function, your CALL statement would specify "TAPI32.DLL,lineClose".


Data Types and their Appx Counterparts

Most Windows functions use a small set of data types.  It is convenient to define a few domains which correspond to these data types.  The advantage of using Domains is obvious - should the definition of a DWORD change, is is easier to change the domain definition than to have to change each DWORD that would be defined as a 10 digit Binary Number.  It is also for this reason that we follow the Windows standards and define a seperate Domain for each Data Type even though the definitions may be the same - should one change at a later date, we can update our program easily.  The data types required by each Windows function can be found in the technical documentation for each function.  The most commonly used data types are:


DWORD 10 digit Binary Storage Numeric 
HANDLE 10 digit Binary Storage Numeric 
POINTER 10 digit Binary Storage Numeric 
CSTR Size varies with 
  usage Alpha 
LPCSTR 10 digit Binary Storage Numeric 
LPDWORD 10 digit Binary Storage Numeric 


 The prefix "LP" means a 'pointer to' or 'address of', so a LPCSTR is a 'pointer to a CSTR'.  A CSTR is a null-terminated string, so an LPCSTR is a pointer to a null-terminated string.  An LPDWORD would be a 'pointer to a DWORD'.


LPCSTR and LPSTR are a little tricky - if an LPCSTR is a function parameter, you should pass a (null-terminated) shared alpha field: if an LPCSTR appears within a structure, you will need to put the address of an alpha field in the structure (see below).


Other handy domains to create are FUNCTION NAME and ACCESS TYPE - alpha fields, 75 characters long.  Storing function names and access types in the default value of work fields makes them easy to find and use.


Access Types and Security Codes

Many of the API's in windows require an access type be passed as part of the argument.  An access type tells Windows what you plan on doing.  For example, if you open a file, you must tell Windows whether you are going to read from or write to the file (or both).  This access type can be obtained by searching C header files (included with a C compiler) for the numeric value (usually in hex).  The permission varies depending on the users needs.  If more than one permission is required (ie. both Create and Delete rights), the hex values of each are searched, converted to decimal, and summed to generate the value to be passed.  Specific functions have specific requirements - a handle opened to change a service might not have sufficient permissions to delete a service.  An easy way to organize and pass access types is to create a work field for the access type with a default value equal to the value that must be passed.  Prefacing the field names with a ":" makes them easy to identify in a large program.


Structures

Often a function requires a structure (or usually the address of a structure) bepassed as one of the parameters.  The structure required as a parameter by the lineInitializeEx function is of the type LINEINITIALIZEEXPARAMS (documentation from the MSDN library shown below).


The LINEINITIZALIZEEXPARAMS structure describes parameters supplied when making calls using LINEINITIALIZEEX.


typedef struct lineinitializeexparams_tag {
    DWORD  dwTotalSize;
    DWORD  dwNeededSize;
    DWORD  dwUsedSize;
    DWORD  dwOptions;
    union
   {
            HANDLE  hEvent;
            HANDLE  hCompletionPort;
    } Handles;
    DWORD  dwCompletionKey;
} LINEINITIALIZEEXPARAMS, FAR *LPLINEINITIALIZEEXPARAMS;

 

Members
dwTotalSize


The total size in bytes allocated to this data structure.


dwNeededSize


The size in bytes for this data structure that is needed to hold all the returned information.


dwUsedSize


The size in bytes of the portion of this data structure that contains useful information.


dwOptions


One of the LINEINITIALIZEEXOPTION_ constants. Specifies the event notification mechanism the application desires to use.


hEvent


If dwOptions specifies LINEINITIALIZEEXOPTION_USEEVENT, TAPI returns the event handle in this field.


hCompletionPort


If dwOptions specifies LINEINITIALIZEEXOPTION_USECOMPLETIONPORT, the application must specify in this field the handle of an existing completion port opened using CreateIoCompletionPort.


dwCompletionKey


If dwOptions specifies LINEINITIALIZEEXOPTION_USECOMPLETIONPORT, the application must specify in this field a value that is returned through the lpCompletionKey parameter of GetQueuedCompletionStatus to identify the completion message as a telephony message.


Remarks


See lineInitializeEx for further information on these options.


QuickInfo


Version: Requires TAPI 2.0 or later.
Windows CE: Unsupported.
Header: Declared in tapi.h.


This structure is defined by creating an Appx file - LIPARAMS.  It is of a One-Rec, Working Storage Type (as we'll only be passing one occurence of the data), and contains a field for each member in the TAPI structure.


LIPARAM         ONE-REC      WORKING-STORAGE      LIPARAMS DWTOTALSIZE       DOMAIN      DWORD          We set the default value of the field at 24 - the size of the   structure.      LIPARAMS DWNEEDEDSIZE      DOMAIN      DWORD      LIPARAMS DWUSEDSIZE        DOMAIN      DWORD      LIPARAMS DWOPTIONS         DOMAIN      DWORD (Default Value - 2)

We set the default value of the field to 2 - One of the 

LINEINITIALIZAEXOPTIONS_ constants.  Using the Microsoft Documentation, look up LINEINITIALIZEEXOPTIONS_ constants - our choice is to use the option, LINEINITIALIZEEXOPTION_USEEVENT.  If you have a C compiler, the numeric value for this option is easy to find (just search) - otherwise, a web search is probably your best option.


      LIPARAMS HEVENT               DOMAIN      HANDLE          Note - this field will return a handle for our use, as we have  specified in the field above to _USEEVENT.      LIPARAMS HCOMPLETIONPT        SYNONYM     UNION      

        Note - as this is a Union, it is represented in Appx by a data  type of Synonym.  If included in a structure, the synonym does  not add to the total length of the structure - often a parameter        required by a function.      

LIPARAMS DWCOMPLETIONK        DOMAIN      DWORD
 

In our example, it is convenient to assign default values to the fields which pass data to the function. As some of the fields will return values to us, we will just leave them blank.  Not all fields will contain a value, but all fields must be included in the structure.


In Summary...


You can build a struct by defining a matching File in the Appx data dictionary.
If you only need one struct, make it a one-record, working storage file.
If you need more than one, make it an indexed file.
Passing a field shared in a Struct file passes the address of the value rather than the value of the value.


The file must contain a field for each member of the Structure, even though some of the fields in the struct may not be used.


A struct may need to contain a null terminated field - this must be done prior to PASSing the record to the CALL statement.


If the length of the struct is to be passed as an additional argument, that length can be determined (easily) by accessing Print Technical Documentation, in Utilities.


PASSing, Share?ing and CALLing

Most of the Windows functions require that parameters be PASSed, either to provide or return data.   If a parameter is a pointer, you must PASS it shared.  If a parameter is not a pointer, you pass it non-shared.  Usually, Microsoft will require a pointer if a parameter is used to give data back to us.  The Microsoft documentation should be referred to when deciding which fields to pass shared.  Strings are always passed shared (because a string is represented by the address of the first character).  Structures are PASSed as RECORD with the Shared? flag set to Y.


Sometimes a parameter in a function is reserved for later use, or may not be of value to us in using the function.  We can set these parameters as NULL.  Refer to the documentation - usually these fields are noted as "can be passed as Null if...".  For these parameters it is handy to have a work field to use as a place marker - TST WORK NULL.  In functions, as in structures, all parameters must be included - even if we don't use them.


CSTR's are strings - defined in Appx as an Alpha field.  Strings are almost always Null Terminated (in other words a NULL (0) character marks the end of the string).  Simple subroutines to add and remove Null Terminators are included at the end of this document.


If you've stored the function name as the default value in a work field, it is easy to assemble a series of PASS and CALL statements to use that function:

 

PASS        ---TEMP 32         FIELD      SHARE? YGOSUB      CAP NULL TERMINATE*PASS      CAP LINE HANDLE      FIELD      SHARE? NPASS      CAP CALL HANDLE      FIELD      SHARE? YPASS      --- TEMP 32          FIELD      SHARE? YPASS      CAP NULL             FIELD      SHARE? NPASS      CAP NULL             FIELD      SHARE? NCALL      CAP *LINE MAKE CALL


In the above example, the field ---TEMP 32 is a string, and must be passed Null Terminated.  Before PASSing it to the CALL statement, we pass it to a Subroutine to be null terminated.  Pass it shared - the value of the string must change to accomodate the Null Terminator.


The default value of CAP *LINE MAKE CALL is:  TAPI32.DLL,lineMakeCall


How results are returned.

Results for a function are returned in either a shared field, or the ---RETURN CODE pre-defined field.  Use MSDN documentation for each function to determine what kind of information is to be found in return code.


Sometimes, the *Get Last Error function must be used to return a specific error code.  To use GetLastError:                                   


       PASS     *Your fields here
       CALL     *A function
       *
       IF       --- RETURN CODE      EQ      (Error code)
T      CALL     CAP *GetLastError
T      TRAP
T      SET      --- RETURN CODE      EQ       --- RETURN CODE


If you are in the debugger, prior to reaching the CALL statement, you must opt to continue to the next valid TRAP.  This is to ensure that the Return Code is for the function called rather than for the Appx keystroke.  The function that you called will affect GetLastError - you want to make sure that Appx itself doesn't call any Windows functions between your CALL statement and the call to GetLastError - otherwise, GetLastError will reflect the result of Appx work, not your CALL statement.  It's also a good idea to call GetLastError before you do anything else (and ignore the result) - Appx must call a few Windows functions in order to load GetLastError into memory - you don't want that to happen later when you really need an error code.


Handles

Many Windows functions make use of handles.  A handle is just a number that Windows gives to you to represent an object.  For example, if you intend to read data from a file, you first open the file - Windows gives you back a file handle.  When you need to call the ReadFile function, you give the file handle back to Windows.  When you request a handle, you tell Windows what type of access you need (see access types above).  If you don't have permissions to access the object in the way you requested, you won't get a handle.


Closing Handles

If your function opens a handle, the handles must be closed when you reach the end of your application.  If you must use a handle multiple times, open it once, use it and then close the handle rather than repeatedly opening and closing the handle to ease error handling.  If you forget to close your handles, they will close upon exiting Appx.  Sometimes, an operation won't appear to complete until the handle is closed - if you delete an entry from the registry, you won't see it disappear until you close your registry handle.


Sample

Our sample application will call a pager, wait 15 seconds, and relay a number.  It cheats a bit - it doesn't actually verify that the phone has been answered - it just assumes.  It also doesn't include error handling, or other features that a real application probably should - it's just a sample...


The first function we will call in our sample application is lineInitializeEx.  This function initializes a line and returns a handle for a TAPI call, as well as a count of the devices that are suitable for use with TAPI functions.  The Microsoft Documentation (shown) defines the function call - also included in the documentation is detailed information about each field, error/success codes and other relevant functions. 

LONG lineInitializeEx(  LPHLINEAPPlphLineApp,  HINSTANCEhInstance,  LINECALLBACKlpfnCallback,  LPCSTRlpszFriendlyAppName,  LPDWORDlpdwNumDevs,  LPDWORDlpdwAPIVersion,  LPLINEINITIALIZEEXPARAMSlpLineInitializeExParams);


We need to define Work Fields which are passed to the function either to carry or return information.  From the documentation we know we need:


CAP SYSTEM HANDLE    HANDLE   This will return a valid usage handle for TAPI. 
CAP NULL             DWORD      The 2nd & 3rd parameters can beNULL (for our purposes).
CAP APPLICATION NAME LPCSTR   This is used for call logging purposes.
CAP DEVICE COUNT     LPDWORD This will return the number of valid devices.
CAP :API VERSION     LPDWORD This has a default value of 131072 for Version 2.0 (hex 20000)


The last parameter we pass is a pointer to a structure (remember, a structure is passed as a shared record).


The structure is of the type LINEINITIALIZEEXPARAMS, and is defined by creating an Appx file - LIPARAMS.  It is of a One-Rec, Working Storage Type (as we'll only be passing one occurence of the data), and contains a field for each member in the TAPI structure.


LIPARAM                 ONE-REC      WORKING-STORAGE  LIPARAMS DWTOTALSIZE    DOMAIN      DWORD (Default Value - 24)  LIPARAMS DWNEEDEDSIZE   DOMAIN      DWORD  LIPARAMS DWUSEDSIZE     DOMAIN      DWORD  LIPARAMS DWOPTIONS      DOMAIN      DWORD (Default Value - 2)  LIPARAMS HEVENT         DOMAIN      HANDLE  LIPARAMS HCOMPLETIONPT  SYNONYM     UNION  LIPARAMS DWCOMPLETIONK  DOMAIN      DWORD


In our example, it is convenient to assign default values to the fields that must pass data to the function.  As some of the fields will return values to us, we will just leave them blank.  The field CAP LIPARAMS HCOMPLETIONPT is a synonym, and as such doesn't add to the size of the structure.


We want to be sure that our handle and device counts don't have any leftover data, and we will hard code in an application name - just a friendly identifier.


      *      Subroutine - Initialize Line      SET      CAP SYSTEM HANDLE           =      SET      CAP DEVICE COUNT            =      SET      CAP APPLICATION NAME        =    APPX Pager Application


The member, CAP APPLICATION NAME is of a data type, LPCSTR, an address to a string.  As strings are passed Null Terminated, we first pass it to a subroutine  to add the Null Terminator.


      PASS     CAP APPLICATION NAME        FIELD      SHARE?  Y      GOSUB    CAP NULL TERMINATE


The parameters for the call are passed shared if so noted in the documentation for the call - usually when data is to be returned, or changed.   Some parameters are passed as NULL as that parameter may not be required in our use of that function, but the space must be reserved.  The structure that we created earlier is passed as a RECORD, and is shared.


      PASS      CAP SYSTEM HANDLE                  FIELD      SHARE? Y      PASS      CAP NULL                    FIELD      SHARE? N      PASS      CAP NULL                    FIELD      SHARE? N      PASS      CAP APPLICATION NAME        FIELD      SHARE? Y      PASS      CAP DEVICE COUNT            FIELD      SHARE? Y      PASS      CAP :API VERSION            FIELD      SHARE? Y       (default value 131072)      PASS      CAP LIPARAMS                RECORD      SHARE? Y      CALL      CAP *LINE INITIALIZE EX     RESIDENT? N  END? N  FAIL 0

(the work field *LINE INITIALIZE EX contains default value: TAPI32.DLL,lineInitializeEx)


If the function call succeeds, a value of 0 is returned in ---RETURN CODE.  The meaning of the return value is documented for each function - sometimes a '0' might indicate failure, other times it would indicate success.  We test for this, and decide how to continue.


      IF    ---RETURN CODE              =      0T     PASS  CAP SYSTEM HANDLE                 FIELD      SHARE? NT     PASS      DEVICE COUNT                  FIELD      SHARE? NT     GOSUB CAP NEGOTIATE TAPI VERSIONSF     ERROR      An Available Device Has Not Been Found!


For the next subroutine, we need a structure, of the LINEEXTENSIONID type.  We create an Appx file, LINEXTEN.  We aren't setting any values in this structure - it just needs to exist.


LINEXTEN          ONE-REC      WORKING-STORAGE   LINEXTEN EXTENSION ID0   DOMAIN      (DWORD)   LINEXTEN EXTENSION ID1   DOMAIN      (DWORD)   LINEXTEN EXTENSION ID2   DOMAIN      (DWORD)   LINEXTEN EXTENSION ID3   DOMAIN      (DWORD)


Assuming that we have a handle and that we have found devices, we continue on to the subroutine CAP NEGOTIATE TAPI VERSIONS.  This subroutine searches the machine to find a device which supports the TAPI version we require. 


      *      Subroutine - Negotiate TAPI Versions      RECEIVE     CAP  SYSTEM HANDLE            FIELD      RECEIVE     CAP  DEVICE COUNT             FIELD        SET      ---BI                        =  CAP DEVICE COUNT    
The valid Device ID will be a number equal to or greater than 0, and less than the Device Count.


      BEG LOOP  AI = 000 TO BI      STEP 001      SET      CAP  DEVICE ID                =  --- AI      SET      CAP  SYSTEM TAPI VERSION      =    


We pass the low version (131071) and a high version (131072) of TAPI that our application will work with.  These values are derived from the hex values for the version - '131071' is 1FFFF in hex (Version 1.0) while the value '131072' is 20000 in hex (Version 2.0).  TAPI returns the CAP SYSTEM TAPI VERSION to tell us which version is installed on our machine.


      PASS      CAP  SYSTEM HANDLE           FIELD      SHARE?  N      PASS      CAP  DEVICE ID               FIELD      SHARE?  N      PASS      CAP  :API LOW VERSION        FIELD      SHARE?  N      PASS      CAP  :API HIGH VERSION       FIELD      SHARE?  N      PASS      CAP  SYSTEM TAPI VERSION     FIELD      SHARE?  Y      PASS      CAP  LINEXTEN                RECORD     SHARE?  Y      CALL      CAP  *LINE NEGOTIATE API     RESIDENT? N  END?  N  FAIL  0

(the work field *LINE NEGOTIATE API contains the default value: PI32.DLL,lineNegotiateAPIVersion)


If a DEVICE ID is useable, end the loop and use the current device.  If not, return and try the next device.


      IF        --- RETURN CODE            EQ          0T     PASS      CAP SYSTEM HANDLE           FIELD      SHARE? NT     PASS      CAP DEVICE ID               FIELD      SHARE? NT     PASS      CAP SYSTEM TAPI VERSION     FIELD      SHARE? NT     GOSUB     CAP LINE OPENT     RETURN
If we get through all the devices and none are found to be suitable, display an error message.


      END LOOP AI      ERROR      No valid devices have been found!      RETURN
Assuming we have at least one usable device:


The next subroutine opens the available device.


      *      Subroutine - Line Open      RECEIVE     CAP SYSTEM HANDLE            FIELD      FAIL N      RECEIVE     CAP DEVICE ID                FIELD      FAIL N      RECEIVE     CAP SYSTEM TAPI VERSION      FIELD      FAIL N

The sixth parameter to lineOpen is a number that might be used in other applications, but we don't need it.  We just pass an arbitrary value.


      SET      --- AI                       =        43      PASS      CAP SYSTEM HANDLE           FIELD      SHARE? N      PASS      CAP DEVICE ID               FIELD      SHARE? N      PASS      CAP LINE HANDLE             FIELD      SHARE? Y      PASS      CAP SYSTEM TAPI VERSION     FIELD      SHARE? N      PASS      CAP NULL                    FIELD      SHARE? N      PASS      --- AI                      FIELD      SHARE? N      PASS      CAP :LINECALLPRIVILEGENONE  FIELD      SHARE? N        (default value - 1)      PASS      CAP NULL                    FIELD      SHARE? N      PASS      CAP NULL                    FIELD      SHARE? N      CALL  CAP *OPEN LINE        RESIDENT? N        END? N       FAIL 0

            (the default value of CAP *OPEN LINE is: TAPI32.DLL,lineOpen)


If the return code is 0, we have a valid line handle, and will continue on to place our call.  If not, display an error.


      IF      --- RETURN CODE              EQ      0T     PASS    CAP LINE HANDLE                  FIELD      SHARE? NT     GOSUB   CAP DIAL PHONE            F     ERROR      The line is not available.

Assuming we have a handle for our line, we continue:

      *      Subroutine - Dial Phone      RECEIVE  CAP LINE HANDLE            FIELD      FAIL N

The phone number to dial is followed by ",,,", indicating a pause, then the number to relay.

      SET         ---TEMP 32           =  5551234,,,5554321

The phone number is passed Null Terminated - in the documentation, we can see it is of the data type LPCSTR - a null-terminated string.

      PASS      --- TEMP 32           FIELD      SHARE? Y      GOSUB     CAP NULL TERMINATE      PASS      CAP LINE HANDLE       FIELD      SHARE? N      PASS      CAP CALL HANDLE       FIELD      SHARE? Y      PASS      --- TEMP 32           FIELD      SHARE? Y      PASS      CAP NULL              FIELD      SHARE? N      PASS      CAP NULL              FIELD      SHARE? N      CALL      CAP *LINE MAKE CALL
            (the default value of CAP *LINE MAKE CALL is: TAPI32.DLL,lineMakeCall)


At this point a pop-up should appear as the modem dials.  The lineMakeCall function has many variables, and can be configured to handle international calls, and to relay call tracking information.  This example is the most basic use of the function.


Don't forget to close your handles...


      PASS      CAP LINE HANDLE      FIELD      SHARE? N      CALL      CAP *LINE CLOSE                  *      PASS      CAP SYSTEM HANDLE    FIELD      SHARE? N      CALL      CAP *CLOSE HANDLE      *      PASS      CAP CALL HANDLE      FIELD      SHARE? N      CALL      CAP *CLOSE HANDLE
            (the default value of CAP *LINE CLOSE IS: TAPI32.DLL,lineClose)

            (the default value of CAP *CLOSE HANDLE IS: KERNEL32.DLL,CloseHandle)


Null Terminate

It is generally noted in the Microsoft documentation if a field is expected to be Null Terminated before being passed to the function.  Pass this subroutine the field to be null terminated (Shared? = Y).  The value is then updated to include the null terminator.


      RECEIVE     --- TEMP 32K          FIELD            FAIL N      CNV BIN     --- TEMP 1                  =      0      APPEND      --- TEMP 32K                0      --- TEMP 1      RETURN

Remove Null Terminator

Strings returned by the API may be null terminated.  Pass this subroutine a field (Shared? = Y), and it will clean up the value.


      *    Subroutine - API Remove Null Terminator      *          *    This subroutine will remove the null terminator from a field.      *    It will also put spaces in the 998 characters following the      *    null terminator, just in case there is garbage in there.      *          RECEIVE  --- TEMP 2K                   FIELD              FAIL N      *             CNV BIN  --- TEMP 1                     =      0       SET      --- TEMP 8K                    =         IF       --- TEMP 2K                    IN --- TEMP 1 T     SET      --- NI                         =  --- TEXT AT POSITIONT     SET TEMP 2K    AT NI  FOR 999 FROM 001 OF  --- TEMP 8K       RETURN  
[Append to This Answer]
2010-May-20 12:29am
Previous: (Answer) 182 - Copying Files Between Databases
Next: (Answer) 647 - Accessing Query Parameters
This document is: http://board.appx.com/cgi-bin/fom.cgi?file=107
[Search] [Appearance]
This is a Faq-O-Matic 2.719.
Copyright 2003 by APPX Software, Inc. All rights reserved.