home *** CD-ROM | disk | FTP | other *** search
- BTP.DOC
- (C) 1993 John C. Leon
- Documentation for BTP V2.0, 6/10/93
- BTP - The Btrieve Unit for Turbo Pascal 7.0
- (* ------------------------------------------------------------------------ *)
-
- This product is in no way intended to teach you object-oriented programming or
- Btrieve programming. It assumes you are comfortable with OOPs, using dynamic
- variables, and Btrieve. Nonetheless, someone just learning OOP or Btrieve can
- benefit by studying the various source code modules in the product.
-
- The best way to learn this product is to read this documentation, then
- examine the example programs thoroughly. They are all fairly short, but
- will do far more to introduce you to using BTP than any amount of studying
- the unit's source code. The example programs and unit source code are
- heavily commented.
-
- INTRODUCTION TO BTP
- -------------------
-
- Btrieve is a record manager sold by Novell. Btrieve has been around a long
- time. It takes many forms today...with versions for Dos, Windows, OS/2,
- and a "client/server" form when used with Novell Netware. This unit has
- been tested only with Btrieve for Dos, Version 5.10a, with all patches
- available thru date of this file applied.
-
- Btrieve files have no inherent structure to differentiate fields. Your own
- code provides that functionality. The BTP product is a programmer's toolkit
- at a ridiculous price that provides the Turbo/Borland Pascal programmer with
- an extra-ordinarily simple means of Btrieve file management, thanks to a
- great language (TP, of course) and object-oriented programming.
-
- The BTP product is built on comprehensive data structures and provides a
- complete guide (through the example programs) to deal with Btrieve files as
- objects. I suspect that the most intriguing capability for many will be the
- ability to use the extended calls with much greater ease; the nasty details
- of structuring and interpreting buffers is made much simpler with BTP (see
- CRUNCH2.PAS and EXAMPLE2.PAS).
-
- The "universal" Btrieve call is made by calling a TP function supplied by
- Novell with the Btrieve product. It takes six parameters. Without a tool
- like BTP, Btrieve programming can be extremely verbose. Keeping track of
- multiple Btrieve files and the variables for all six parameters for each
- file in a complex application using traditional programming techniques takes
- discipline and a masochistic attention to detail; I consider myself
- somewhat disciplined but I'm NOT masochistic. Btrieve programming screams
- out for the simplification possible with object-oriented programming.
-
- BTP includes a number of sound data structures/objects, including:
- TRecMgr, BFile, BFixed, BFileExt, and BSized. TRecMgr and BFile are base
- objects; they are direct descendants of TP7's TObject. BFixed, BFileExt and
- BSized are each direct descendants of BFile.
-
- TRECMGR is of limited usefulness (see the unit source code and VERSION.PAS),
- ------- but it does provide the ability to make non-file oriented calls
- (stop, reset, version, et al).
-
- BFILE is what you'll use for virtually all standard, fixed length Btrieve
- ----- files when not using extended calls; it will probably be your
- workhorse. Requires only that your descendant add a "FIELDS" member
- to provide field definitions and data/key buffers. This is easier
- than it sounds ... look at EXAMPLE1.PAS and EXAMPLE2.PAS. Much
- useful work can be done by using an object of type BFile directly,
- without deriving a descendant ... look at STATS.PAS.
-
- BFIXED has structures to support ANY standard, fixed length Btrieve file.
- ------ BFixed is used in the CRUNCHx.PAS programs provided, which can 'clone
- and squish' (remove dead space) from any standard, fixed length
- Btrieve file. BFixed is useful for working on standard fixed length
- files of an unknown nature when you need to perform *record* oriented
- functions with no care about field definitions.
-
- BFILEEXT is used for any Btrieve file for which you'll be using extended calls
- -------- (note there is no specific support for the extended insert call, but
- CRUNCH2.PAS is an example of how to make such calls). BFileExt is
- used in CRUNCH2.PAS and EXAMPLE2.PAS.
-
- BSIZED Can be useful at times for record-oriented operations where you need
- ------ to specify a data buffer length of your choosing, without having to
- add data members to BFile. Can be useful for variable length files.
- See CRUNCH1.PAS and CRUNCH2.PAS.
-
- Quite simply, your TYPE declarations define your Btrieve files as descendants
- of one of the four file-oriented objects provided in BTP and described above.
- Those declarations can include definitions of your file's field structures,
- and can provide the required data and key buffers. These programmer-defined
- additions to the BTP objects are encapsulated in the descendant object, along
- with the data fields inherited, which include a description of the file's
- structure and the file's position block. Immediately upon instantiation of a
- BTP descendant object, you have access to any file's structure and stats. You
- also have an 'isolated' position block and the required buffers for Btrieve
- calls.
-
- In the case of a BFile descendant, you override (replace) a single object
- method, and can make your Btrieve calls with just two parameters: the Btrieve
- op code and key number. Use of the 4 get/step extended calls is only
- marginally more difficult. In order to use extended calls, your object must
- be a descendant of BFileExt, and must override two methods: one for standard
- calls, and one for the extended calls. BFileExt includes pointers to two
- collections, which are automatically initialized when the object's
- constructor is called. The first collection is for the filter logic terms,
- the second is for the field extractor specs. Your program simply inserts
- items into these collections. The rest, including determining buffer lengths
- and structuring the outgoing buffer, is handled internally by the object's
- methods.
-
-
- USING THE BASE OBJECT - BFILE
- -----------------------------
-
- This unit gives you a technique, a shorthand way to get into Btrieve files
- and manipulate them. For example, to open a file and encapsulate its stats
- into your Btrieve file object, you need only do:
-
- ObjectName^.Init(BDosFileName, Normal, '')
-
- (BDosFileName is a valid Btrieve file name. 'Normal' is the open
- mode. The null string you see as the third parameter means the file
- takes no owner name. See BTP.PAS for the various constants defined.
- Assuming no errors, the file is open.)
-
- Similary, to close a file, you simply do:
-
- BStatus := ObJectName^.Close; OR simply
- ObjectName^.Close;
-
-
- The BFile object defined herein is the heart of this unit. BFile is NOT an
- abstract object!! It can be used as is, for example, if all you want to do
- is to get stats (see STATS.PAS). However, if you wish to do any normal
- field-oriented functions, you will do two things in a descendant:
-
- 1. Add one data element, a variant record. This record will contain your
- field definitions and provide your buffers.
- 2. You are *required* to override the abstract BT function.
-
- Here's an example. This example will compile and run. It introduces the
- CreateFile function "before it's time" just to get you going.
-
- ..............................................................................
- PROGRAM BTPIntro;
-
- {$X+,N+,E+}
-
- USES BTP;
-
- TYPE
-
- MyFields = record
- case integer of
- 1: (Field1 : string[9]; {obviously use size and type}
- Field2 : double; {of your fields here! }
- {etc}
- KeyBuf : array[1..10] of char); {size to largest key length}
- 2: (DBuffer: array[1..18] of char); {size to record length }
- 3: (Position: array[1..2] of word); {high word returned first! }
- end; {useful after a GET POSITION}
-
- PMyObject = ^MyObject;
- MyObject = object(BFile)
- Fields : MyFields;
- function BT(OpCode, Key: integer): integer; virtual;
- end;
-
- VAR
-
- MyFile : PMyObject;
- MyFileSpec: PFileSpecObj;
-
- {This is the required override of the BT function for all standard, fixed
- length files. As the base object has no field structures, it cannot include
- this function...you must override it by REPLACING it as shown here.}
-
- function MyObject.BT(OpCode, Key:integer): integer;
- begin {DBufferLen is reset here as it may need to}
- DBufferLen := Specs.RecLen; {be changed on return from some ops. }
- BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
- end;
-
-
- BEGIN
-
- MyFileSpec := new(PFileSpecObj, Init(18, 512, 2, 0, 0,
- NewKeySpec(1, 10, Duplicates or Modifiable or ExtType,
- BLString,
- NewKeySpec(11, 8, Duplicates or Modifiable or ExtType,
- BFloat,
- nil))));
-
- BStatus := CreateFile('TestFile', MyFileSpec^.Specs, '', '', 0);
- dispose(MyFileSpec, Done);
-
- MyFile := new(PMyObject, Init('TestFile', Normal, ''));
- writeln('WOW ... TestFile has ', MyFile^.NumSegs, ' segments!');
-
- with MyFile^ do
- begin
- Fields.Field1 := 'Lisa';
- Fields.Field2 := 10000;
- BT(BInsert, 0); {Key number of 0 provided only for positioning info}
- Fields.Field1 := 'John';
- Fields.Field2 := 20000;
- BT(BInsert, 0);
- Fields.Field1 := ''; {Zap existing member data to prove the point.}
- Fields.Field2 := 0;
- BT(BGetFirst, 0);
- writeln('Field1 of after Get First w/key 0: ', Fields.Field1);
- writeln('Field2 of after Get First w/key 0: ', Fields.Field2:5:2);
- MyFile^.Close;
- end;
-
- dispose(MyFile, Done);
-
- END.
-
- ..............................................................................
-
- The record variable itself should be used as the Btrieve data buffer
- parameter. In the override of the BT function, the data buffer length is
- reset to the file's record length before making each call. The KeyBuf field
- of the MyFields record should be used in all Btrieve calls as the key buffer.
-
- As you can see, your BFile descendant will incorporate the universal Btrieve
- function call in a compact, elegant format. Thanks to OOP and encapsulation,
- you are guaranteed that each descendant's own key and data buffers are used
- since each instantiation references its own data members.
-
- As an aside, the .BT function cannot be fully incorporated into the base BFile
- object since the base object has no data fields corresponding to your file's
- fields, and thus cannot contain a properly sized key buffer or data buffer.
- The override is necessary because neither the BFile object nor Btrieve itself
- know anything about fields or the appropriate size for your buffers.
-
- In fact, to assure you don't call the .BT function of BFile directly, BFile.BT
- includes a call to TP7's Abstract method, which will crash your program with a
- runtime error 211 unless you replace it, presumably with an override very
- similar to that used above in BTPIntro.
-
- Examining the BTPIntro program, let's look at the object's instantiation.
-
- MyFile := new(PMyObject, Init('TestFile', Normal, ''));
-
- The BFile constructor/initialization method will do a Btrieve open operation,
- using the open mode you provide as the second parameter to the Init call.
- After the open operation, a stat operation is performed. The Btrieve
- filespec and key specs, retrieved by the stat call, are incorporated into
- BFile's data fields. Those stats can then be read out at will simply by
- referencing the object's data fields in the following manner:
-
- MyFile^.Specs.PageSize, or MyFile^.NumRecs, etc.
-
- The stat fields defined in the object include the number of keys, the total
- number of key segments, the page size, the number of records in the file
- when the INIT was performed, the file flags...in short, the complete Btrieve
- filespec. All other stats can be derived from the object's data fields if
- needed. If you ever need to refresh the stat data to refresh the record
- count, for example, you could simply:
-
- BStatus := MyFile^.Close;
- dispose(MyFile, Done);
- MyFile := new(PMyObject, Init('TestFile', Normal, ''));
- HowMany := MyFile^.NumRecs;
-
- Of course, you could always do a Stat call directly and deal with the
- results, but the BTP way is so EASY!! MyFile^.NumRecs, a longint, is
- available immediately after the Init call.
-
- Btrieve operations with BTP are performed by calling the BT function with JUST
- TWO PARAMETERS. Here's a simple step next call:
-
- BStatus := MyFile^.BT(BStepNext, 2);
-
- (BStatus is a public integer var from this unit. BStepNext is a public
- constant. A number of public constants are defined in this unit that can
- help make your code more readable.)
-
- There is some nominal overhead in the BFile object. It allocates space for
- the maximum of 24 keys/segments, and for an alternate collating sequence,
- whether one is used in the file or not. This simply means a maximum of 633
- bytes per open file could be wasted. This maximum would apply to a standard
- Btrieve file that had just one unsegmented key. The figure of 633 is derived
- as follows:
-
- 665 maximum bytes if the Btrieve max of 24 keys/
- segments and an alternate collating sequence is used
- - 16 bytes to hold the Btrieve file specs (stats)
- - 16 bytes for the minimum required 1 Btrieve key spec for
- a standard (not data-only and not key-only) file
- ---
- 633 bytes maximum possible waste
-
- In addition, my convention of supplying the BFile descendant with a built-in
- key-buffer, arbitrarily sized to the length of the largest size key, could be
- overhead. I don't mind that a bit, and I don't think you should. In my
- opinion, the code/data overhead introduced of necessity with this unit
- is more than offset by the benefits.
-
- Your app's code is responsible for stuffing key values into the key buffer,
- getting or setting the data buffer, et al, as usual. However, you gain the
- ease of accessing all Btrieve ops in a distinct shorthand, accessing those
- buffers and fields by name, and can rest assured that all parameters are
- passed and filled. See EXAMPLE1.PAS for a full working model of this
- technique.
-
- You will note that the examples sometimes break one of OOP's rules by
- accessing data fields directly. In my own apps I will typically have at
- least a couple of additional methods in my BFile descendants for getting and
- setting the key buffers and data buffers. How you handle your own code
- is up to you.
-
-
- USING THE BFILEEXT OBJECT
- -------------------------
-
- There were several assumptions coded into the BTP unit to make a working
- structure for the get/step extended calls (see the declaration of data types
- for BFileExt in the unit's source code):
-
- 1. That the required data buffer will never be more than 32767 bytes.
- 2. That values used for a filter's logic terms will never be longer
- than 255 bytes.
-
- Beyond these assumptions, which you can change if necessary by changing the
- unit's source or by defining your own descendants, BTP enables you to make any
- of the four get/step extended calls with ease. Examples of doing so are in
- example programs CRUNCH2.PAS and EXAMPLE2.PAS.
-
- Admittedly, constructing outgoing buffers for Btrieve's extended calls can
- be frustrating. Btrieve is hostile in this regard. BTP reduces this
- drudgery to a couple of additional setup statements!
-
- Recall that the outgoing buffer for these extended calls requires several
- data structures occupying contiguous bytes in the buffer: a header, a
- filter, optional filter logic terms, an extractor, and at least one field
- extractor spec. After initializing the data buffer, you must calculate
- the buffer length for the Btrieve call to be the larger of the outgoing or
- incoming buffers! SHEESH! The BTP way of dealing with this is as follows:
-
- 1: HEADER is handled internally. Don't mess with it. Yes, even setting
- the buffer length in the header is handled internally.
- 2: FILTER's fields MUST be assigned by your program.
- 3: FILTERSPEC is a TP collection that may or may not hold any objects,
- depending on your program's needs.
- 4: EXTRACTOR's fields MUST be assigned by your program.
- 5: EXTRACTORSPEC is a TP collection that must hold at least one object.
- 6: EXTDBUFFER, the data buffer for these 4 extended calls, is handled
- internally. Don't mess with it, except to use it as shown below and in
- EXAMPLE2.PAS when you override BFileExt.BTExt.
- 7. Buffer structuring is totally transparent to your program. Simply
- override the BFileExt.BTExt function as shown in the examples, and
- you're there! Believe me, this took some work! The standard TP7
- 'ForEach' iterator is used to take every item in the two collections
- and account for them in structuring the outgoing data buffer.
-
- The BFileExt.BTExt method must be overridden, with the override calling the
- ancestor; i.e. your override must be of a standard form, and must *first* call
- BFileExt.BTExt. This is because BFileExt.BTExt is what sets the buffer length
- and constructs the buffer. This is in contrast to BFile.BT or BFileExt.BT,
- which must be overridden by REPLACING them.
-
- Since Btrieve's get/step extended calls permit use of filters based on field
- values or on a user (program) supplied value, there are two constructors for
- the logic term object: INITF (initialize to compare with field) and,
- INITV (initialize to compare with value).
- Refer to the BTP source code for a list of the parameters required by these
- two constructors.
-
- I strongly urge you to review EXAMPLE2.PAS for a full working model of using
- extended calls with BTP. It's a much easier task to review the example than
- to peruse the source code and this documentation. The following is a skeletal
- example, which will compile and run as is ... it assumes the file from
- program BTPIntro above exists in the current directory. This example is
- silly in the sense that the file you're working on has only two records and
- doesn't warrant use of extended calls, but we're just illustrating technique.
-
- .............................................................................
- PROGRAM BTPExtIntro;
-
- {$X+}
-
- USES BTP;
-
- TYPE
- MyFields = record
- case integer of
- 1: (Field1 : string[9]; {obviously use size and type}
- Field2 : double; {of your fields here! }
- {etc}
- KeyBuf : array[1..10] of char); {size to largest key length}
- 2: (DBuffer: array[1..18] of char); {size to record length }
- 3: (Position: array[1..2] of word); {high word returned first! }
- end; {useful after a GET POSITION}
-
- PMyObject = ^MyObject;
- MyObject = object(BFileExt)
- Fields : MyFields;
- function BT(OpCode, Key: integer): integer; virtual;
- function BTExt(OpCode, Key: integer): integer; virtual;
- end;
-
- VAR
-
- MyFile : PMyObject;
- Counter : integer;
- X : string;
- Value : TByteArray; {TByteArray is a data type in BTP...an array of
- 255 bytes.}
-
- function MyObject.BT(OpCode, Key: integer): integer;
- begin
- DBufferLen := Specs.RecLen;
- BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
- end;
-
- function MyObject.BTExt(OpCode, Key: integer): integer;
- begin
- BStatus := BFileExt.BTExt(OpCode, Key); {MUST call ancestor method!!!}
- BTExt := Btrv(OpCode, PosBlk, ExtDBuffer^.Entire, DBufferLen,
- Fields.KeyBuf, Key);
- end;
-
- BEGIN
-
- MyFile := new(PMyObject, Init('TestFile', ReadOnly, ''));
-
- {Set up some data members: maximum number of records to skip, number of logic
- terms, number of records to return on each call, number of fields to return.
- This one call sets up the filter and extractor.}
-
- MyFile^.SetTerms(50, 1, 5, 1);
-
- {Now specify filter logic terms. We'll setup filter to use 'Lisa' as a value
- for filtering. Tell Btrieve to look for it in positions 1 thru 10. Add 1
- pointer to a FilterSpec object to the FilterSpec collection, using the value
- placed in the required array of byte.}
-
- X := 'Lisa';
- move(X, Value, sizeof(X)+1);
- with MyFile^.FilterSpec^ do
- insert(new(PFilterSpec, InitV(BLString, 1, 10, Equal, LastTerm, Value)));
-
- {Let's set up to extract an entire record, not just particular fields. This
- implies placing just one pointer to an extractor spec object in the
- ExtractorSpec collection. The first parameter to that object's constructor
- is the length of the item to extract (here the entire record length). The
- second parameter is the zero-based offset from which to begin extraction.
- This is 0, of course, if extracting the entire record.}
-
- with MyFile^.ExtractorSpec^ do
- insert(new(PExtSpec, Init(MyFile^.Specs.RecLen, 0)));
-
- {Oh, yea...need to establish a position before using these calls, so let's do
- a Get First before the extended call!}
-
- BStatus := MyFile^.BT(BGetFirst, Zero);
-
- {Now let's get on with it and make the extended call...}
-
- BStatus := MyFile^.BTExt(BGetNextExt, 0);
-
- {Take advantage of the data types provided in BTP to see how many records were
- returned in this call.}
-
- writeln(#13,#10,'Number of records returned with Field1 of ''Lisa'' is ',
- MyFile^.ExtDBuffer^.NumRecs);
-
- {Don't forget to close the file and dispose of the dynamic object. The
- object's Done destructor will dispose of the collections for you.}
-
- BStatus := MyFile^.Close;
- dispose(MyFile, Done);
-
- END.
-
- .............................................................................
-
-
- USING THE CLONEFILE FUNCTION
- ----------------------------
-
- While the standard BTP style of making Btrieve calls and defining your data
- types has hopefully been made clear, a few words about the invaluable
- CloneFile function may be helpful.
-
- With this single function call you can clone any standard Btrieve file. It
- is not intended for data only or key only files, and has not been tested with
- them, SO BE WARNED.
-
- The syntax for this call is:
-
- CloneFile(sourcefile, newfile, 'supplemental index handling', ownername),
- where 'supplemental index handling' is one of: Drop, Retain, None
-
- For example, an existing standard Btrieve file having no owner name, with
- 1 permanent index and two supplemental indexes (regardless of # segments
- in each) is named ORIGINAL. You wish to create a clone named DUPE, with
- only the permanent indexes retained. The call would be:
-
- BStatus := CloneFile('Original', 'Dupe', Drop, '');
-
- CloneFile readily handles supplemental indexes. Its syntax permits you to
- specify whether or not you wish to retain such supplemental indexes in your
- cloned file, to drop them and keep only the permanent indexes, or to specify
- 'None' if there are none to begin with.
-
- If you specify 'Drop' or 'Retain', and there are in fact no supplemental
- indexes, no harm is done...the file is properly cloned anyway.
-
- HOWEVER, if you specify 'None' and there are indeed supplemental indexes,
- the supplemental indexes will be retained as permanent indexes.
-
-
- USING THE CREATEFILE FUNCTION
- -----------------------------
- There is no easier way to create a file than with the technique in BTP. All
- it takes are two program statements. Given a variable of type PFileSpecObj,
- you simply instantiate the object, and pass its spec to the CreateFile
- function. The CreateFile function can perform multiple Btrieve calls; i.e.
- if you wish to create a file to have an owner name, you simply pass the
- owner name as a parameter to CreateFile. After the file is created, the owner
- name is added in a second Btrieve op automatically. Examine the following
- brief example, which will compile and run:
-
- PROGRAM BTPCreate;
-
- USES BTP;
-
- VAR
- MyFileSpec : PFileSpecObj;
-
- BEGIN
- MyFileSpec := new(PFileSpecObj, Init(100, 512, 1, 0, 0,
- NewKeySpec(1, 10, Duplicates or Modifiable or ExtType,
- BLString, nil)));
-
- CreateFile('Test', MyFileSpec^.Specs, '', '', 0);
-
- dispose(MyFileSpec, Done);
- END.
-
- The FileSpecObj constructor guarantees proper assembly of the buffers required
- for the Btrieve Create operation. The constructor's parameters are:
-
- 1. Record length
- 2. Page size
- 3. # keys
- 4. File flags
- 5. # pages to pre-allocate
- 6. Pointer to head of a linked list of pointers to key specifications
-
- Of these 6, all but #6 should be obvious. The easiest way to build the
- linked list of key specs is to let BTP handle it in the fashion shown.
- The NewKeySpec function creates a pointer to a key spec. Its parameters are:
-
- 1. Position
- 2. Length
- 3. Key flags {ALWAYS include ExtType in the list of OR'd flags!}
- 4. Key type
- 5. Pointer to next key spec, if any, else nil.
-
- In the BTPCreate program above, parameter #5 is simply nil. You can nest
- as many calls to NewKeySpec as required to complete the filespec. The file
- spec object's destructor will destroy the list of key specs on the heap.
- Here's an example of nesting:
-
- MyFileSpec := new(PFileSpecObj, Init(100, 2048, 2, 0, 0,
- NewKeySpec(1, 10, Duplicates or Modifiable or
- Segmented or ExtType, BLString,
- NewKeySpec(11, 20, Duplicates or Modifiable or
- ExtType, BLString,
- NewKeySpec(50, 10, Modifiable or ExtType, BFloat,
- nil)))));
-
- Now that you understand building the file spec, let's examine the CreateFile
- function. It takes 5 parameters:
-
- 1. File name
- 2. Pointer to a file spec of type PFileSpec
- 3. Name of alternate collating sequence file to be incorporated into the
- file, if any, else null string. The alternate collating sequence file
- MUST reside in the current directory. Internally, CreateFile takes
- advantage of another BTP object, TAltColSeq, when incorporating an
- alternate collating sequence into the file.
- 4. Owner name, if any, else null string
- 5. Owner name security level, if any, else 0
-
- WARNING: If a file by the name you specify in parameter #1 already exists on
- disk, it will be overwritten by CreateFile!
-
-
- ABOUT THE EXAMPLE PROGRAMS
- --------------------------
-
- The README.1ST file contains a list and description of the example programs.
- They should be extremely helpful in getting you up to speed with the BTP
- product. Several of them are utility programs you can use "out of the box"!
-
-
- PARTING SHOTS
- -------------
-
- ENJOY BTP PROGRAMMING! BTP is not free...it is shareware. Yours with no
- obligation for a 30 day trial period, you are expected to remit the $25
- registration fee if you use BTP. Please refer to the README.1ST file for
- license terms and remittance instructions.
-
- The most current version of BTP can usually be found in 3 places on Compu-
- serve if not available to you through your favorite BBS or shareware catalog:
-
- Borland's Pascal Forum (go BPASCAL), in download library 7 (DOS PROGRAMMING)
- IBM Programmer's Forum (go IBMPRO), in download library 5 (OTHER LANGUAGES)
- Novell Netwire (go NOVLIB), in download library 15 (SHAREWARE/DEMO),
-
- Even if you choose not to register your copy of BTP, I would like to hear
- from you about your experiences with and reaction to using this unit. In
- addition, of course, constructive criticism and suggestions are always
- welcome.
-
- John C. Leon
- 3807 Wood Gardens Court
- Kingwood, TX 77339
-
- 713-359-3641 (residence)
- CIS #72426,2077
-
-