The PARADOX File Structure

 The PARADOX File Structure                         Compiled by Randy Beck
 ==========================                               [email protected]
                                                  http://www.randybeck.com

    I haven't made changes to this description for a long while, but not
    much has changed.  The latest revision concerns the tableName field,
    which I've discovered has lengthened in Paradox 7, and then another
    revision to that same field yet again.

    I'm not aware of any changes after Paradox 7.  But if anybody knows of
    anything different in Paradox 8 and 9 then please let me know.

    I should say here that many of the fields are listed here as pointers
    while the file is in memory.  I first made this connection back when
    Paradox 4 was still the state of the art.  I have not verified that
    this remains true, but a quick glance at some of the fields makes me
    think it's probably still true.


 This document details the binary file format for Paradox data files.
 There are still a few unknowns, but the important items are covered.

 I refer to the older table format as 3.0 tables and the newer ones as 4.0
 through 7.0, but I believe that most of the 3.0 information may also apply
 to the earlier versions.

 Pascal terms are used to describe data types:
     byte is 1 byte unsigned;
     integer is a 2-byte signed integer;
     word is a 2-byte unsigned integer;
     longint is a 4-byte signed integer;
     char is a 1-byte character;
     pchar is a pointer to a character;
     ^ modifies any type definition to a pointer to that type;
     ^pchar is a pointer to a pointer to a character.

 All pointers are 4-byte pointers.

 Please send additions and corrections to:

     Randy Beck
     P.O. Box 530433
     DeBary, FL 32753-0433
     USA

     email: [email protected]

 Distribute this freely, but please leave my name and address
 intact so that others may add information.




 GENERAL FILE STRUCTURE
 ======================

    ============================
    |     Header               |
    |     Data Block 0         |
    |     Data Block 1         |
    |     Data Block 2         |
    |     ...                  |
    |     Data Block n         |
    ============================

 The size of the Paradox file header is usually 2048 bytes (see headerSize
 at file offset 0002).  The first portion (offsets 0000 to $0057) has fixed
 field locations.  The next section ($0058 to $0077) was new to Paradox 4.0
 data files.  The rest varies in size -- depending upon the upon the number
 of fields -- and is listed sequentially.

 Some of the information in the file header seems to be needed only while
 it is being used by Paradox in RAM.  A few of these fields are pointers to
 other fields in the header, and are valid only during run-time.  They are
 listed here anyway, although their meaning is subject to change and they
 serve little purpose for third-party software.

 The data area which follows is divided into record blocks.  These use 1024,
 2048, 3072 or 4096 bytes each, depending upon the maximum table size set
 when the table was created.  The field structure of the data area is itself
 unusual in that each data field is arranged in hi-byte to low-byte order.

 Note:  This hi-byte to low-byte arrangement only applies to the user data
 in the table.  Everything else uses the normal low-byte to hi-byte format.

 The structure of primary and secondary index files generally follows that
 of Paradox version 3 data files.




 Paradox Common File Header  -- offsets 0000 to 0057
 ==========================

 With some noted exceptions, this part of the description is common to data
 and index files.  Offsets in this list are given in hexadecimal.  Other
 numbers should be assumed as listed in decimal format unless preceded by a
 dollar sign ('$').

 offset  type        usage
==============================================================================
| 0000 | integer     recordSize                                              |
|      |                                                                     |
|      |        This is the size of a user record in this table.             |
|      |                                                                     |
|      |        For primary index files, each "record" is actually the       |
|      |        field or fields in the index, plus three integers which      |
|      |        are not referenced in the header.                            |
|      |                                                                     |
|      |        Secondary index files also have additional fields, but       |
|      |        these are listed in the header.                              |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0002 | integer     headerSize (always $0800)                               |
|      |                                                                     |
|      |        You can change headerSize, and move the data blocks          |
|      |        accordingly, to create larger or smaller table headers.      |
|      |        Borland's TUTILITY program would flag an error, but          |
|      |        Paradox, the Borland Database Engine and the Paradox         |
|      |        Engine will all still work with these tables.                |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0004 | byte        fileType                                                |
|      |                                                                     |
|      |           0 = this is an indexed .DB data file                      |
|      |           1 = this is a primary index .PX file                      |
|      |           2 = this is a non-indexed .DB data file                   |
|      |           3 = this is a non-incrementing secondary index .Xnn file  |
|      |           4 = this is a secondary index .Ynn file (inc or non-inc)  |
|      |           5 = this is an incrementing secondary index .Xnn file     |
|      |           6 = this is a non-incrementing secondary index .XGn file  |
|      |           7 = this is a secondary index .YGn file (inc or non inc)  |
|      |           8 = this is an incrementing secondary index .XGn file     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0005 | byte        maxTableSize                                            |
|      |                                                                     |
|      |        This is the "maximum table size" determined when this        |
|      |        table was created.  It really indicates the size of each     |
|      |        block of records in the data section of the table.           |
|      |                                                                     |
|      |           1 =   64M    (block size = $0400 bytes)                   |
|      |           2 =  128M    (block size = $0800 bytes)                   |
|      |           3 =  192M    (block size = $0C00 bytes)                   |
|      |           4 =  256M    (block size = $1000 bytes)                   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0006 | longint     numRecords                                              |
|      |                                                                     |
|      |        This is the number of records in this file.                  |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 000A | word        nextBlock                                               |
|      |                                                                     |
|      |        I'm not certain what this really is, but it seems to be      |
|      |        the same as fileBlocks unless there is an empty block in     |
|      |        the table.                                                   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 000C | word        fileBlocks                                              |
|      |                                                                     |
|      |        This is the number of data blocks in the file.               |
|      |        (Each "block" is a cluster of records.)                      |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 000E | word        firstBlock                                              |
|      |                                                                     |
|      |        Always 1 unless the table is empty.                          |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0010 | word        lastBlock                                               |
|      |                                                                     |
|      |        This works out to the number of blocks that the table        |
|      |        would contain if every block was packed.                     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0012 | word        unknown                                                 |
|      |                                                                     |
|      |        The value of this field seems to the change when records     |
|      |        or blocks have been added to the table, but I still haven't  |
|      |        figured it out.                                              |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0014 | byte        modifiedFlags1                                          |
|      |                                                                     |
|      |        A rebuild is required if this is not zero.                   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0015 | byte        indexFieldNumber                                        |
|      |                                                                     |
|      |        In the .Xnn file of a secondary index, this is the number    |
|      |        of the field it is referencing.                              |
|      |                                                                     |
|      |        This will be zero in the other files.                        |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0016 | pointer     primaryIndexWorkspace                                   |
|      |                                                                     |
|      |        Pointer to the primary index file header (in RAM).           |
|      |        This will be a NIL if there is no primary index.             |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 001A | pointer     unknown  (suspected pointer)                            |
|      |                                                                     |
|      |        This field is usually a NIL pointer.  I've only seen it      |
|      |        used in 5.0 tables with BCD field types.  It is probably     |
|      |        just a workspace pointer.                                    |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 001E...0020        unknown                                                 |
|      |                                                                     |
|      |        I have only seen these three bytes used in .PX files.        |
|      |                                                                     |


+------+---------------------------------------------------------------------+
| 0021 | integer     numFields                                               |
|      |                                                                     |
|      |        This is the number of fields in the table.  If this is an    |
|      |        index file, then it would only be the number of fields in    |
|      |        this index.                                                  |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0023 | integer     primaryKeyFields                                        |
|      |                                                                     |
|      |        This is the number of fields in the file's primary key.      |
|      |        It will be a zero for .PX and .Ynn files; and 2 for .Xnn     |
|      |        secondary index files.                                       |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0025 | longint     encryption1                                             |
|      |                                                                     |
|      |        This was where the encryption information was stored for     |
|      |        versions 3.0 and 3.5.  (It was a zero if not encrypted.)     |
|      |                                                                     |
|      |        Subsequent versions store the value $FF00FF00 here, and      |
|      |        move the encryption code to offset $005C.  Even so, these    |
|      |        newer versions still maintain this information at both       |
|      |        locations while working in RAM.                              |
|      |                                                                     |
|      |        Primary and .Ynn secondary index files always use this       |
|      |        field to store the encryption code, but it is often a        |
|      |        zero because the Paradox Engine and the Borland Database     |
|      |        Engine do not always encrypt index files.  You can encrypt   |
|      |        unencrypted index files by following these steps:            |
|      |                                                                     |
|      |           Begin with an empty encrypted table;                      |
|      |           Truncate the index files to headerSize;                   |
|      |           Zeroize nextBlock, fileBlocks, firstBlock and lastBlock;  |
|      |           Copy four bytes from the .DB data file's encryption1      |
|      |               field (for version 3), or the encryption2 field       |
|      |               (for versions 4 and above) into the encryption1       |
|      |               or encryption2 field of the index file;               |
|      |           Test thoroughly.                                          |
|      |                                                                     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0029 | byte        sortOrder                                               |
|      |                                                                     |
|      |           $00:  ASCII                                               |
|      |           $B7:  International                                       |
|      |           $82:  Norwegian/Danish                                    |
|      |           $E6:  Norwegian/Danish (4.0)                              |
|      |           $F0:  Swedish/Finnish                                     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 002A | byte        modifiedFlags2                                          |
|      |                                                                     |
|      |        A rebuild is required if this is not zero.                   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 002B...002C        unknown    (always 0)                                   |
+------+---------------------------------------------------------------------+
| 002D | byte        changeCount1                                            |
|      |                                                                     |
|      |        This is incremented whenever the file header is updated.     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 002E | byte        changeCount2                                            |
|      |                                                                     |
|      |        I'm not certain when this is incremented.                    |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 002F | byte        unknown                                                 |
+------+---------------------------------------------------------------------+
| 0030 | ^pchar      tableNamePtrPtr                                         |
|      |                                                                     |
|      |        This is a pointer to tableNamePtr, which is a pointer to     |
|      |        tableName.  Paradox uses this field to gain faster access    |
|      |        to tableName because that part of the header is accessed     |
|      |        sequentially.                                                |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0034 | pointer     fldInfoPtr                                              |
|      |                                                                     |
|      |        Pointer to the list of field identifiers.  This is listed    |
|      |        in the accompanying Pascal record definition as a            |
|      |        PFldInfoRec type.                                            |
|      |                                                                     |
|      |        You can use this pointer value to locate the table header    |
|      |        in memory during run time.  Just subtract $0078 from this    |
|      |        value (for 4.0+ tables), or $0058 (for .PX and .Ynn index    |
|      |        files and version 3.0 tables).                               |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0038 | byte        writeProtected                                          |
|      |                                                                     |
|      |           0        write protection OFF                             |
|      |           1        write protection ON                              |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0039 | byte        fileVersionID                                           |
|      |                                                                     |
|      |           $03      version 3.0                                      |
|      |           $04      version 3.5                                      |
|      |           $05..09  version 4.x   (usually = $09)                    |
|      |           $0A,$0B  version 5.x                                      |
|      |           $0C      version 7.x                                      |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 003A | word        maxBlocks                                               |
|      |                                                                     |
|      |        I don't know what this is for.  It is usually the same       |
|      |        as fileBlocks (at offset 000C).                              |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 003C | byte        unknown                                                 |
+------+---------------------------------------------------------------------+
| 003D | byte        auxPasswords                                            |
|      |                                                                     |
|      |        Number of auxiliary passwords assigned to the table.         |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 003E...003F        unknown                                                 |
+------+---------------------------------------------------------------------+
| 0040 | pointer     cryptInfoStartPtr                                       |
|      |                                                                     |
|      |        Points to cryptInfo field.  It is always NIL when not        |
|      |        encrypted.  It is sometimes NIL even when encrypted.         |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0044 | pointer     cryptInfoEndPtr                                         |
|      |                                                                     |
|      |        Points to end of cryptInfo.  This is NIL if not encrypted.   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0048 | byte        unknown                                                 |
+------+---------------------------------------------------------------------+
| 0049 | longint     autoInc                                                 |
|      |                                                                     |
|      |        This long integer stores the value used for the next auto    |
|      |        incrementing field in tables with a "+" autoincrementing     |
|      |        field type.                                                  |
|      |                                                                     |
|      |        Formerly used as a modification count.                       |
|      |                                                                     |
|      |        My thanks to Orlando Ruiz for informing me of this change.   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 004D...004E        unknown                                                 |
+------+---------------------------------------------------------------------+
| 004F | byte        indexUpdateRequired                                     |
+------+---------------------------------------------------------------------+
| 0050...0054        unknown                                                 |
+------+---------------------------------------------------------------------+
| 0055 | byte        refIntegrity                                            |
|      |                                                                     |
|      |        A value here (=2?) denotes that this table uses              |
|      |        referential integrity checks.                                |
|      |                                                                     |
|      |                                                                     |
|      |             inxDirection (sec'y index file only)                    |
|      |                                                                     |
|      |        Secondary .Xnn index files of v7.0 tables use this           |
|      |        field to indicate sort order direction:                      |
|      |                                                                     |
|      |           $01      ascending sort                                   |
|      |           $11      descending sort                                  |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0056...0057        unknown                                                 |
==============================================================================





 Paradox 4+ Data File Header -- offsets 0058 to 0077
 ===========================

 This part of the description applies only to .DB data files and .Xnn index
 files for Paradox versions 4.0 and later.

 offset  type        usage
==============================================================================
| 0058 | integer     unknown  (file version ID?)                             |
|      |                                                                     |
|      |           $0105..$0109  version 4.x   (usually = $0109)             |
|      |           $010A, $010B  version 5.x   (usually = $010B)             |
|      |           $010C         version 7.0                                 |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 005A | integer     unknown  (file version ID?)                             |
|      |                                                                     |
|      |        same values as at 0058                                       |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 005C | longint     encryption2                                             |
|      |                                                                     |
|      |        This will be zero if not encrypted.                          |
|      |        See encryption1 at offset 0025.                              |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0060 | longint     fileUpdateTime (4.x only)                               |
|      |                                                                     |
|      |        Format similar to a packed date and time.                    |
|      |        I don't know what this does in 5.0 tables.                   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0064 | integer     hiFieldID                                               |
|      |                                                                     |
|      |        This number is always numFields + 1.                         |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0066 | integer     hiFieldIDinfo?                                          |
|      |                                                                     |
|      |        This is related to hiFieldID (above), but I don't really     |
|      |        know what it's for.                                          |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0068 | integer     sometimesNumFields?                                     |
|      |                                                                     |
|      |        This is sometimes the number of fields in the table, but     |
|      |        is often just a zero.  I don't know why.                     |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 006A | integer     dosGlobalCodePage                                       |
|      |                                                                     |
|      |        This was the Global Code Page when this table was created.   |
|      |                                                                     |
|      |           $01B5   United States                                     |
|      |           $02E1   Greek 1                                           |
|      |           $0352   Multilingual (Latin I)                            |
|      |           $0354   Eastern European (Latin II)                       |
|      |           $0359   Turkish                                           |
|      |           $035C   Portuguese                                        |
|      |           $035D   Icelandic                                         |
|      |           $035F   Canadian French                                   |
|      |           $0361   Nordic                                            |
|      |           $0365   Greek 2                                           |
|      |                                                                     |
|      |        Refer to an MS-DOS technical reference about interrupt $21,  |
|      |        function $66, for more information about this.               |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 006C...006F        unknown                                                 |
+------+---------------------------------------------------------------------+
| 0070 | integer     changeCount4                                            |
+------+---------------------------------------------------------------------+
| 0072...0077        unknown                                                 |
==============================================================================





 Paradox Common File Header  -- continued
 ==========================

 The file header continues sequentially.  Since the number of fields varies,
 there are no subsequent fixed offsets.

 This section begins where the previous section left off:

 offset
==============================================================================
| 0058   Paradox tables version 3.0 and 3.5                                  |
| 0058   Paradox .PX and .Ynn index files (any listed version)               |
| 0078   Paradox tables version 4.0 and above                                |
| 0078   Paradox .Xnn index files version 4.0 and above                      |
==============================================================================

         type                                   usage
==============================================================================
| ---- | array[1..(numFields)] of TFldInfoRec   fieldInfo                    |
|      |                                                                     |
|      |        type  TFldInfoRec  = RECORD                                  |
|      |                  fType   : byte;                                    |
|      |                  fSize   : byte;                                    |
|      |              end;                                                   |
|      |                                                                     |
|      |        These are the field identifiers for each field in the        |
|      |        table:                                                       |
|      |                                                                     |
|      |           fType  fSize(decimal)                                     |
|      |           -------------------------                                 |
|      |            $01     v   "A"  Alpha                                   |
|      |            $02     4   "D"  Date                                    |
|      |            $03     2   "S"  Short integer                           |
|      |            $04     4   "I"  Long integer                            |
|      |            $05     8   "$"  currency                                |
|      |            $06     8   "N"  Number                                  |
|      |            $09     1   "L"  Logical                                 |
|      |            $0C     v   "M"  Memo BLOb                               |
|      |            $0D     v   "B"  Binary Large Object                     |
|      |            $0E     v   "F"  Formatted Memo BLOb                     |
|      |            $0F     v   "O"  OLE                                     |
|      |            $10     v   "G"  Graphic BLOb                            |
|      |            $14     4   "T"  Time                                    |
|      |            $15     8   "@"  Timestamp                               |
|      |            $16     4   "+"  Autoincrement                           |
|      |            $17    17*  "#"  BCD                                     |
|      |            $18     v   "Y"  Bytes                                   |
|      |                                                                     |
|      |        The fSize given for BCD fields is not used for field size.   |
|      |        Instead, fSize denotes the number of digits following the    |
|      |        decimal point.  BCD fields are always 17 bytes long.         |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| ---- | pchar                                  tableNamePtr                 |
|      |                                                                     |
|      |        Pointer to tableName when header is in RAM.                  |
|      |                                                                     |
+======+=====================================================================+
| ---- | array[1..(numFields)] of pchar         fieldNamePtrArray            |
|      |                                                                     |
|      |      * These pointers are not present in the index files. *         |
|      |                                                                     |
|      |        This is an array of pointers that reference the field names  |
|      |        when Paradox (or one of the engines) is running.  The size   |
|      |        of this array depends upon the number of fields.             |
|      |                                                                     |
+======+=====================================================================+
| ---- | array[1..(length varies)] of char      tableName                    |
|      |                                                                     |
|      |        This was the name this file was assigned when created.       |
|      |                                                                     |
|      |        Most tables will use 79 bytes, padded with zeroes.           |
|      |        This was extended with Paradox 7 to 261 bytes.               |
|      |                                                                     |
|      |        NOTE:  It was previously listed here as 271 bytes but        |
|      |               I received a heads-up from George Thackray that       |
|      |               this was in error.  It may also be that there's       |
|      |               there are more variations.                            |
|      |                                                                     |
==============================================================================
|                                                                            |
|     The rest of this information does not apply to .PX and .Ynn files.     |
|                                                                            |
==============================================================================
| ---- | char[]                                 fieldNames                   |
|      |                                                                     |
|      |        These are the ASCIIZ field name(s), arranged sequentially.   |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| ---- | record                                 cryptInfo                    |
|      |                                                                     |
|      |        Encrypted tables would have additional data here.            |
|      |                                                                     |
|      |        Tables with auxiliary passwords would have 256 bytes here    |
|      |        (about which I have no information).                         |
|      |        Encrypted tables without auxiliary passwords would have      |
|      |        one byte for each field here.                                |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| ---- | array[1..(numFields)] of integer       fieldNumbers                 |
|      |                                                                     |
|      |        These seem to be field numbers.  Changing these numbers      |
|      |        in my limited experiments seemed to cause no change in       |
|      |        behavior.  TUTILITY didn't seem to mind either.              |
|      |                                                                     |
|      |        I define this as an array, but the size is determined by     |
|      |        the number of fields in the table.                           |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| ---- | char[]      sortOrderID                                             |
|      |                                                                     |
|      |        An ASCIIZ string representing the sort order for this        |
|      |        table ("ascii", "intl", etc.).                               |
|      |                                                                     |
==============================================================================






 Paradox Data Blocks
 ===================

 The data area begins at offset headerSize (usually $0800).  It is divided
 into blocks of 1024, 2048, 3072 or 4096 bytes -- depending upon the maximum
 table size set when the table was created.

 The entire data block will be encrypted if the table is encrypted.

  byte   type      usage
==============================================================================
| 0000 | word      nextBlock (block number + ???)                            |
|      |                                                                     |
|      |        I don't know what this does.                                 |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0002 | word      blockNumber                                               |
|      |                                                                     |
|      |        The first block is numbered zero.                            |
|      |                                                                     |
+------+---------------------------------------------------------------------+
| 0004 | integer   addDataSize                                               |
|      |                                                                     |
|      |        This represents the amount of data in this block -- in       |
|      |        addition to one record length.                               |
|      |                                                                     |
|      |        This will be a zero if there is one record in this block,    |
|      |        and a negative number if there are no records.               |
|      |                                                                     |
|      |           numRecsInBlock = (addDataSize / recordSize) + 1           |
|      |                                                                     |
==============================================================================
| 0006.......      fileData                                                  |
|                                                                            |
|               Block size varies according to maxTableSize (at 0005):       |
|                  maxTableSize =  1  (64M):   block size = $0400 bytes      |
|                  maxTableSize =  2 (128M):   block size = $0800 bytes      |
|                  maxTableSize =  3 (192M):   block size = $0C00 bytes      |
|                  maxTableSize =  4 (256M):   block size = $1000 bytes      |
|                                                                            |
|                                                                            |
|   The records in the data area are arranged in field order.  Alpha fields  |
|   are arrays of characters, padded with zeroes.  Other fields seem to be   |
|   arranged in hi-byte to low-byte order, with the first byte's high bit    |
|   set for positive numbers.                                                |
|                                                                            |
|   For example, the number 1 stored as a 2-byte Short integer field is      |
|   arranged this way:  $80, 01.  And the integer 256 would be stored as:    |
|   $81, 00.                                                                 |
|                                                                            |
|   Floating point Number and currency fields are 8-byte DOUBLE types,       |
|   hi-byte to low-byte, with the first byte's high bit set.                 |
|                                                                            |
|   Date fields are stored as the number of days since JAN-0-0000, in high-  |
|   byte to low-byte order with the first byte's high bit set.               |
|                                                                            |
|                                                                            |
|   If you are wondering why the first byte's high bit is set for positive   |
|   numbers, recall from the Paradox Engine's documentation that a "blank"   |
|   SHORT (2-byte) integer is assigned the value of $8000.  By using the     |
|   hi-byte to low-byte format, and by reversing the first byte's high bit,  |
|   the value $8000 is converted to all zeroes.  So a record stored as all   |
|   zeroes is actually all blanks.                                           |
|                                                                            |
|                                                                            |
|   The records in primary index files contain the field or fields in the    |
|   index -- plus three integers that are not described in the header's      |
|   fieldInfo area.                                                          |
|                                                                            |
|                                                                            |
==============================================================================





 Sample Program with Paradox File Header Defined as a Pascal Record
 ==================================================================

 The rest of this text may be copied to a separate file as source code.


Program PXFMT;

(*
    This program will list the field values in the given table.

      Usage:  PXFMT <tablename.db>

    The file name's extension is mandatory.  It also works with index
    files, and displays the index record's three additional fields.

    It will stop if there are any errors.
 *)

uses  Dos, Crt, Objects;

const
    { Paradox codes for field types }
    pxfAlpha        = $01;
    pxfDate         = $02;
    pxfShort        = $03;
    pxfLong         = $04;
    pxfCurrency     = $05;
    pxfNumber       = $06;
    pxfLogical      = $09;
    pxfMemoBLOb     = $0C;
    pxfBLOb         = $0D;
    pxfFmtMemoBLOb  = $0E;
    pxfOLE          = $0F;
    pxfGraphic      = $10;
    pxfTime         = $14;
    pxfTimestamp    = $15;
    pxfAutoInc      = $16;
    pxfBCD          = $17;
    pxfBytes        = $18;


type
    { field information record used in TPxHeader below }
    PFldInfoRec         = ^TFldInfoRec;
    TFldInfoRec         =  RECORD
        fType   : byte;
        fSize   : byte;
    end;


    PPxHeader           = ^TPxHeader;
    TPxHeader           =  RECORD
        recordSize              :  word;
        headerSize              :  word;
        fileType                :  byte;
        maxTableSize            :  byte;
        numRecords              :  longint;
        nextBlock               :  word;
        fileBlocks              :  word;
        firstBlock              :  word;
        lastBlock               :  word;
        unknown12x13            :  word;
        modifiedFlags1          :  byte;
        indexFieldNumber	:  byte;
        primaryIndexWorkspace   :  pointer;
        unknownPtr1A            :  pointer;
        unknown1Ex20            :  array[$001E..$0020] of byte;
        numFields               :  integer;
        primaryKeyFields        :  integer;
        encryption1             :  longint;
        sortOrder               :  byte;
        modifiedFlags2          :  byte;
        unknown2Bx2C            :  array[$002B..$002C] of byte;
        changeCount1            :  byte;
        changeCount2            :  byte;
        unknown2F               :  byte;
        tableNamePtrPtr         : ^pchar;
        fldInfoPtr              :  PFldInfoRec;
        writeProtected          :  byte;
        fileVersionID           :  byte;
        maxBlocks               :  word;
        unknown3C               :  byte;
        auxPasswords            :  byte;
        unknown3Ex3F            :  array[$003E..$003F] of byte;
        cryptInfoStartPtr       :  pointer;
        cryptInfoEndPtr         :  pointer;
        unknown48               :  byte;
        autoInc                 :  longint;
        unknown4Dx4E            :  array[$004D..$004E] of byte;
        indexUpdateRequired     :  byte;
        unknown50x54            :  array[$0050..$0054] of byte;
        refIntegrity            :  byte;
        unknown56x57            :  array[$0056..$0057] of byte;
        case INTEGER of
          3:   (fieldInfo35     :  array[1..255] of TFldInfoRec);
          4:   (fileVerID2      :  integer;
                fileVerID3      :  integer;
                encryption2     :  longint;
                fileUpdateTime  :  longint;  { 4.0 only }
                hiFieldID       :  word;
                hiFieldIDinfo   :  word;
                sometimesNumFields:integer;
                dosCodePage     :  word;
                unknown6Cx6F    :  array[$006C..$006F] of byte;
                changeCount4    :  integer;
                unknown72x77    :  array[$0072..$0077] of byte;
                fieldInfo       :  array[1..255] of TFldInfoRec);

      { This is only the first part of the file header.  The last field
        is described as an array of 255 elements, but its size is really
        determined by the number of fields in the table.  The actual
        table header has more information that follows. }

    end;


    PDataBlock  = ^TDataBlock;
    TDataBlock  =  RECORD
        nextBlock     : word;
        blockNumber   : word;
        addDataSize   : integer;
        fileData      : array[0..$0FF9] of byte;
        { fileData size varies according to maxTableSize }
    end;




procedure ConvertPxField(var N;  F: PFldInfoRec);
{ This will convert both ways, but blanks will be turned to zeroes. }
{ Warning:  Not all field types are converted. }
type TNRec= array[0..16] of byte;
var  i    : integer;
     size : integer;
     NRec : TNRec;

    function ItsBlank : boolean;
    var  i : integer;
    begin
      ItsBlank := TRUE;
      For i := 0 to pred(size) do If TNRec(N)[i] <> 0 then ItsBlank := FALSE;
    end;

begin
  If F^.fType = pxfBCD then { BCD field size value not used for field size }
    size := 17
   else
    size := F^.fSize;
  If (F^.fType in [pxfDate..pxfNumber, pxfTime..pxfAutoInc]) and
     not ItsBlank  { leave blank fields as all zeroes }
   then
    begin
    TNRec(N)[0] := TNRec(N)[0] xor $80;
    For i := 0 to pred(size) do
      NRec[pred(size-i)] := TNRec(N)[i];
    Move(NRec, N, size);
    end;
end;


procedure ConvertPxRecord(Hdr: PPxHeader; P: pointer);
const  IndexF : TFldInfoRec = (fType: pxfShort;  fSize: sizeof(INTEGER));
var  i    : integer;
     F    : PFldInfoRec;
begin
  F := Hdr^.fldInfoPtr;  { begin with the first field identifier }
  For i := 1 to Hdr^.numFields do
    begin
    ConvertPxField(P^, F);
    If F^.fType = pxfBCD then { BCD field size value not used for field size }
      Inc(ptrrec(P).ofs, 17)
     else
      Inc(ptrrec(P).ofs, F^.fSize);
    Inc(ptrrec(F).ofs, sizeof(F^));
    end;
  If Hdr^.fileType = 1 then  { convert primary index information }
    begin
    For i := 1 to 3 do
      begin
      ConvertPxField(P^, @IndexF);
      Inc(ptrrec(P).ofs, 2);
      end;
    end;
end;


procedure WritePxField(var N;  F: PFldInfoRec);
{ not all field types are supported here }
var  i    : integer;
     A    : string;
begin
  Case F^.fType of
    pxfAlpha, pxfMemoBLOb:
      begin
      Move(N, A[1], F^.fSize);
      A[0] := char(F^.fSize);
      For i := length(A) downto 1 do
        If (A[i] = #0) then A[0] := char(pred(i));
      write('"', A, '"');
      end;
    pxfShort:             write(integer(N));
    pxfLong, pxfAutoInc:  write(longint(N));
    pxfCurrency:          write('$', double(N):1:2);
    pxfNumber:            write(double(N):1:3);

    { the rest of the field types are not translated }
    pxfDate:              write('<Date:',longint(N),'>');
    pxfLogical:           write('<Logical:',byte(N),'>');
    pxfBLOb:              write('<BLOb>');
    pxfFmtMemoBLOb:       write('<FormattedBLOb>');
    pxfOLE:               write('<OLE>');
    pxfGraphic:           write('<Graphic>');
    pxfTime:              write('<Time:',longint(N),'>');
    pxfTimestamp:         write('<TimeStamp:',longint(N),'>');
    pxfBCD:               write('<BCD>');
    pxfBytes:             write('<Bytes>');
   else                   write('<unknown>');
    end;
end;


procedure WritePxRecord(Hdr: PPxHeader; P: pointer);
const  IndexF : TFldInfoRec = (fType: pxfShort;  fSize: sizeof(INTEGER));
var  i    : integer;
     F    : PFldInfoRec;
begin
  F := Hdr^.fldInfoPtr;  { begin with the first field identifier }
  For i := 1 to Hdr^.numFields do
    begin
    If i > 1 then write(', ');
    WritePxField(P^, F);
    If F^.fType = pxfBCD then { BCD field size value not used for field size }
      Inc(ptrrec(P).ofs, 17)
     else
      Inc(ptrrec(P).ofs, F^.fSize);
    Inc(ptrrec(F).ofs, sizeof(F^));
    end;
  If Hdr^.fileType = 1 then  { display primary index information }
    begin
    For i := 1 to 3 do
      begin
      If i = 1 then write(';  index fields: ') else write(', ');
      WritePxField(P^, @IndexF);
      Inc(ptrrec(P).ofs, 2);
      end;
    end;
  writeln;
end;


procedure ReadBlock(var S: TStream; Hdr: PPxHeader; var AData );
begin
  S.Read(AData, Hdr^.maxTableSize * $0400)
end;


procedure SeekBlock(var S: TStream; Hdr: PPxHeader; ABlock: word);
var  L   : longint;
begin
  L := ABlock;
  L := (L * Hdr^.maxTableSize * $0400) + Hdr^.headerSize;
  S.Seek(L);
end;



procedure ReadAllRecords(var S: TStream);
var  i      : integer;
     num,z  : word;
     Block  : PDataBlock;
     F      : TFldInfoRec;
     Hdr    : PPxHeader;

    function  FileFormatIsOK : boolean;
    begin
      FileFormatIsOK := (Hdr^.maxTableSize >= 1) and (Hdr^.maxTableSize <= 4)
    end;

    function  FileIsEncrypted : boolean;
    begin
      If (Hdr^.fileVersionID <= 4) or not (Hdr^.fileType in [0,2,3,5]) then
        FileIsEncrypted := (Hdr^.encryption1 <> 0)
       else
        FileIsEncrypted := (Hdr^.encryption2 <> 0)
    end;

begin
  New(Hdr);
  S.Seek(0);
  S.Read(Hdr^, sizeof(Hdr^));
  If (S.Status = stOK) and FileFormatIsOK then
    begin

    { assign the header's fldInfoPtr field }
    If (Hdr^.fileVersionID <= 4) or not (Hdr^.fileType in [0,2,3,5]) then
      Hdr^.fldInfoPtr := addr(Hdr^.fieldInfo35)
     else
      Hdr^.fldInfoPtr := addr(Hdr^.fieldInfo);

    If FileIsEncrypted then
      writeln('This file is encrypted.')
     else
      begin
      New(Block);
      num := 0;
      While (S.Status = stOK) and (num < Hdr^.fileBlocks) do
        begin
        SeekBlock(S, Hdr, num);
        ReadBlock(S, Hdr, Block^);
        If (S.Status = stOK) and (Block^.addDataSize >= 0) then
          begin
          z := 0;
          For i := 0 to (Block^.addDataSize div Hdr^.recordSize) do
            begin
            ConvertPxRecord(Hdr, addr(Block^.fileData[z]));
            WritePxRecord(Hdr, addr(Block^.fileData[z]));
            Inc(z, Hdr^.recordSize);
            end;
          end;
        Inc(num);
        end;

      Dispose(Block);
      end;

    end;
end;


var  Stream   : TBufStream;

Begin
  Assign(Output, '');
  Rewrite(Output);
  Stream.Init(paramstr(1), stOpenRead, 4096);
  ReadAllRecords(Stream);
  If Stream.Status <> stOK then
    writeln(^M^J'Error:  Status=', Stream.Status, ';  Error=', Stream.ErrorInfo);
  Stream.Done;
End.

PASCAL代码实现:

Program PXFMT;

(*
    This program will list the field values in the given table.

      Usage:  PXFMT <tablename.db>

    The file name's extension is mandatory.  It also works with index
    files, and displays the index record's three additional fields.

    It will stop if there are any errors.
 *)

uses  Dos, Crt, Objects;

const
    { Paradox codes for field types }
    pxfAlpha        = $01;
    pxfDate         = $02;
    pxfShort        = $03;
    pxfLong         = $04;
    pxfCurrency     = $05;
    pxfNumber       = $06;
    pxfLogical      = $09;
    pxfMemoBLOb     = $0C;
    pxfBLOb         = $0D;
    pxfFmtMemoBLOb  = $0E;
    pxfOLE          = $0F;
    pxfGraphic      = $10;
    pxfTime         = $14;
    pxfTimestamp    = $15;
    pxfAutoInc      = $16;
    pxfBCD          = $17;
    pxfBytes        = $18;


type
    { field information record used in TPxHeader below }
    PFldInfoRec         = ^TFldInfoRec;
    TFldInfoRec         =  RECORD
        fType   : byte;
        fSize   : byte;
    end;


    PPxHeader           = ^TPxHeader;
    TPxHeader           =  RECORD
        recordSize              :  word;
        headerSize              :  word;
        fileType                :  byte;
        maxTableSize            :  byte;
        numRecords              :  longint;
        nextBlock               :  word;
        fileBlocks              :  word;
        firstBlock              :  word;
        lastBlock               :  word;
        unknown12x13            :  word;
        modifiedFlags1          :  byte;
        indexFieldNumber    :  byte;
        primaryIndexWorkspace   :  pointer;
        unknownPtr1A            :  pointer;
        unknown1Ex20            :  array[$001E..$0020] of byte;
        numFields               :  integer;
        primaryKeyFields        :  integer;
        encryption1             :  longint;
        sortOrder               :  byte;
        modifiedFlags2          :  byte;
        unknown2Bx2C            :  array[$002B..$002C] of byte;
        changeCount1            :  byte;
        changeCount2            :  byte;
        unknown2F               :  byte;
        tableNamePtrPtr         : ^pchar;
        fldInfoPtr              :  PFldInfoRec;
        writeProtected          :  byte;
        fileVersionID           :  byte;
        maxBlocks               :  word;
        unknown3C               :  byte;
        auxPasswords            :  byte;
        unknown3Ex3F            :  array[$003E..$003F] of byte;
        cryptInfoStartPtr       :  pointer;
        cryptInfoEndPtr         :  pointer;
        unknown48               :  byte;
        autoIncVal              :  longint;
        unknown4Dx4E            :  array[$004D..$004E] of byte;
        indexUpdateRequired     :  byte;
        unknown50x54            :  array[$0050..$0054] of byte;
        refIntegrity            :  byte;
        unknown56x57            :  array[$0056..$0057] of byte;
        case INTEGER of
          3:   (fieldInfo35     :  array[1..255] of TFldInfoRec);
          4:   (fileVerID2      :  integer;
                fileVerID3      :  integer;
                encryption2     :  longint;
                fileUpdateTime  :  longint;  { 4.0 only }
                hiFieldID       :  word;
                hiFieldIDinfo   :  word;
                sometimesNumFields:integer;
                dosCodePage     :  word;
                unknown6Cx6F    :  array[$006C..$006F] of byte;
                changeCount4    :  integer;
                unknown72x77    :  array[$0072..$0077] of byte;
                fieldInfo       :  array[1..255] of TFldInfoRec);

      { This is only the first part of the file header.  The last field
        is described as an array of 255 elements, but its size is really
        determined by the number of fields in the table.  The actual
        table header has more information that follows. }

    end;


    PDataBlock  = ^TDataBlock;
    TDataBlock  =  RECORD
        nextBlock     : word;
        blockNumber   : word;
        addDataSize   : integer;
        fileData      : array[0..$0FF9] of byte;
        { fileData size varies according to maxTableSize }
    end;




procedure ConvertPxField(var N;  F: PFldInfoRec);
{ This will convert both ways, but blanks will be turned to zeroes. }
{ Warning:  Not all field types are converted. }
type TNRec= array[0..16] of byte;
var  i    : integer;
     size : integer;
     NRec : TNRec;

    function ItsBlank : boolean;
    var  i : integer;
    begin
      ItsBlank := TRUE;
      For i := 0 to pred(size) do If TNRec(N)[i] <> 0 then ItsBlank := FALSE;
    end;

begin
  If F^.fType = pxfBCD then { BCD field size value not used for field size }
    size := 17
   else
    size := F^.fSize;
  If (F^.fType in [pxfDate..pxfNumber, pxfTime..pxfAutoInc]) and
     not ItsBlank  { leave blank fields as all zeroes }
   then
    begin
    TNRec(N)[0] := TNRec(N)[0] xor $80;
    For i := 0 to pred(size) do
      NRec[pred(size-i)] := TNRec(N)[i];
    Move(NRec, N, size);
    end;
end;


procedure ConvertPxRecord(Hdr: PPxHeader; P: pointer);
const  IndexF : TFldInfoRec = (fType: pxfShort;  fSize: sizeof(INTEGER));
var  i    : integer;
     F    : PFldInfoRec;
begin
  F := Hdr^.fldInfoPtr;  { begin with the first field identifier }
  For i := 1 to Hdr^.numFields do
    begin
    ConvertPxField(P^, F);
    If F^.fType = pxfBCD then { BCD field size value not used for field size }
      Inc(ptrrec(P).ofs, 17)
     else
      Inc(ptrrec(P).ofs, F^.fSize);
    Inc(ptrrec(F).ofs, sizeof(F^));
    end;
  If Hdr^.fileType = 1 then  { convert primary index information }
    begin
    For i := 1 to 3 do
      begin
      ConvertPxField(P^, @IndexF);
      Inc(ptrrec(P).ofs, 2);
      end;
    end;
end;


procedure WritePxField(var N;  F: PFldInfoRec);
{ not all field types are supported here }
var  i    : integer;
     A    : string;
begin
  Case F^.fType of
    pxfAlpha, pxfMemoBLOb:
      begin
      Move(N, A[1], F^.fSize);
      A[0] := char(F^.fSize);
      For i := length(A) downto 1 do
        If (A[i] = #0) then A[0] := char(pred(i));
      write('"', A, '"');
      end;
    pxfShort:             write(integer(N));
    pxfLong, pxfAutoInc:  write(longint(N));
    pxfCurrency:          write('$', double(N):1:2);
    pxfNumber:            write(double(N):1:3);

    { the rest of the field types are not translated }
    pxfDate:              write('<Date:',longint(N),'>');
    pxfLogical:           write('<Logical:',byte(N),'>');
    pxfBLOb:              write('<BLOb>');
    pxfFmtMemoBLOb:       write('<FormattedBLOb>');
    pxfOLE:               write('<OLE>');
    pxfGraphic:           write('<Graphic>');
    pxfTime:              write('<Time:',longint(N),'>');
    pxfTimestamp:         write('<TimeStamp:',longint(N),'>');
    pxfBCD:               write('<BCD>');
    pxfBytes:             write('<Bytes>');
   else                   write('<unknown>');
    end;
end;


procedure WritePxRecord(Hdr: PPxHeader; P: pointer);
const  IndexF : TFldInfoRec = (fType: pxfShort;  fSize: sizeof(INTEGER));
var  i    : integer;
     F    : PFldInfoRec;
begin
  F := Hdr^.fldInfoPtr;  { begin with the first field identifier }
  For i := 1 to Hdr^.numFields do
    begin
    If i > 1 then write(', ');
    WritePxField(P^, F);
    If F^.fType = pxfBCD then { BCD field size value not used for field size }
      Inc(ptrrec(P).ofs, 17)
     else
      Inc(ptrrec(P).ofs, F^.fSize);
    Inc(ptrrec(F).ofs, sizeof(F^));
    end;
  If Hdr^.fileType = 1 then  { display primary index information }
    begin
    For i := 1 to 3 do
      begin
      If i = 1 then write(';  index fields: ') else write(', ');
      WritePxField(P^, @IndexF);
      Inc(ptrrec(P).ofs, 2);
      end;
    end;
  writeln;
end;


procedure ReadBlock(var S: TStream; Hdr: PPxHeader; var AData );
begin
  S.Read(AData, Hdr^.maxTableSize * $0400)
end;


procedure SeekBlock(var S: TStream; Hdr: PPxHeader; ABlock: word);
var  L   : longint;
begin
  L := ABlock;
  L := (L * Hdr^.maxTableSize * $0400) + Hdr^.headerSize;
  S.Seek(L);
end;


procedure ReadAllRecords(var S: TStream);
var  i      : integer;
     num,z  : word;
     Block  : PDataBlock;
     F      : TFldInfoRec;
     Hdr    : PPxHeader;

    procedure writeAmt(L: longint; S: string);
    begin
      write(L, S);
      If L <> 1 then write('s');
      writeln;
    end;

    function  FileFormatIsOK : boolean;
    begin
      FileFormatIsOK := (Hdr^.maxTableSize >= 1) and (Hdr^.maxTableSize <= 4)
    end;

    function  FileIsEncrypted : boolean;
    begin
      If (Hdr^.fileVersionID <= 4) or not (Hdr^.fileType in [0,2,3,5]) then
        FileIsEncrypted := (Hdr^.encryption1 <> 0)
       else
        FileIsEncrypted := (Hdr^.encryption2 <> 0)
    end;

begin
  New(Hdr);
  S.Seek(0);
  S.Read(Hdr^, sizeof(Hdr^));
  If (S.Status = stOK) and FileFormatIsOK then
    begin

    { assign the header's fldInfoPtr field }
    If (Hdr^.fileVersionID <= 4) or not (Hdr^.fileType in [0,2,3,5]) then
      Hdr^.fldInfoPtr := addr(Hdr^.fieldInfo35)
     else
      Hdr^.fldInfoPtr := addr(Hdr^.fieldInfo);

    writeAmt(Hdr^.numRecords, ' record');
    writeAmt(Hdr^.fileBlocks, ' block');

    If FileIsEncrypted then
      writeln('This file is encrypted.')
     else
      begin
      New(Block);
      num := 0;
      While (S.Status = stOK) and (num < Hdr^.fileBlocks) do
        begin
        SeekBlock(S, Hdr, num);
        ReadBlock(S, Hdr, Block^);
        If (S.Status = stOK) and (Block^.addDataSize >= 0) then
          begin
          z := 0;
          For i := 0 to (Block^.addDataSize div Hdr^.recordSize) do
            begin
            ConvertPxRecord(Hdr, addr(Block^.fileData[z]));
            WritePxRecord(Hdr, addr(Block^.fileData[z]));
            Inc(z, Hdr^.recordSize);
            end;
          end;
        Inc(num);
        end;

      Dispose(Block);
      end;

    end;
end;


var  Stream   : TBufStream;

Begin
  Assign(Output, '');
  Rewrite(Output);
  Stream.Init(paramstr(1), stOpenRead, 4096);
  ReadAllRecords(Stream);
  If Stream.Status <> stOK then
    writeln(^M^J'Error:  Status=', Stream.Status, ';  Error=', Stream.ErrorInfo);
  Stream.Done;
End.

C#代码实现:

  • ParadoxFile - base class working with common structures from Paradox data files and indexes
  • ParadoxFile.DataBlock - represents one block of data
  • ParadoxFile.FieldInfo - data type of the field
  • ParadoxFile.V4Hdr - structure which is present only in certain Paradox files/versions
  • ParadoxTable - represents table data file
  • ParadoxPrimaryKey - represents table index
  • ParadoxFileType - enum with all file types
  • ParadoxFieldType - enum with all data types
  • ParadoxRecord - represents a data record
  • ParadoxDataReader - standard IDataReader implementation
  • ParadoxCondition - base class for conditions which can be used for searching in index data
  • ParadoxCondition nested classes - various condition implementations
  • ParadoxCompareOperator - enum with supported compare operators (==, !=, <, <=, >, >=)

数据文件读取代码示例:

var table = new ParadoxTable(dbPath, "zakazky");
var recIndex = 1;
foreach (var rec in table.Enumerate())
{
    Console.WriteLine("Record #{0}", recIndex++);
    for (int i=0; i<table.FieldCount; i++)
    {
        Console.WriteLine("    {0} = {1}", table.FieldNames[i], rec.DataValues[i]);
    }
    if (recIndex > 10) break;
}

索引文件读取代码示例:

var index = new ParadoxPrimaryKey(table, Path.Combine(dbPath, "zakazky.PX"));
var condition =
    new ParadoxCondition.LogicalAnd(
        new ParadoxCondition.Compare(
            ParadoxCompareOperator.GreaterOrEqual, 1750, 0, 0),
        new ParadoxCondition.Compare(
            ParadoxCompareOperator.LessOrEqual, 1760, 0, 0));
var qry = index.Enumerate(condition);
var rdr = new ParadoxDataReader(table, qry);
recIndex = 1;
while (rdr.Read())
{
    Console.WriteLine("Record #{0}", recIndex++);
    for (int i = 0; i < rdr.FieldCount; i++)
    {
        Console.WriteLine("    {0} = {1}", rdr.GetName(i), rdr[i]);
    }
}

Paradox internal structure:pxformat.zip

ParadoxReader:ParadoxReader.zip

Paradox-database-native-NET-reader:https://www.codeproject.com/Articles/101014/Paradox-database-native-NET-reader

猜你喜欢

转载自www.cnblogs.com/88223100/p/The-PARADOX-File-Structure.html