Foxpro database reader and writer

The other day I was working on a software contract that needed a simple way to read and write FoxPro databases from a c++ application running on windows 7 & 8 64bit edition.
I tried using the ADO drivers and found that they would not work for me, so I took a look on the internet for the raw file format of the Foxpro dbf files. I found a partial description on a site that sells their own reader and writer at http://www.dbf2002.com/dbf-file-format.html.

There are other free dbf readers out there, I tried the one at http://sourceforge.net/projects/dbfconverter but it just displayed garbage, and I did not know where to look in his code to find the problem.
So I took the dbf information and wrote a simple program that would read and write simple dbf files. Kinda redundant, but I was using this in a commercial product, so I figured if I wrote it myself I could be sure the licensing was free for commercial use!

I decided to treat the dbf file more like a type less system and I created a single method for reading and writing that treated all fields like strings. Also, it will only handle integers floats, doubles, logicals and character fields. No support for memo or currency as I could not find documentation to describe how they as stored. I treat all unhandled field types as char fields.
I needed to be able to create a new dbf file from scratch, so I implemented a very simple set of functions to do that as well.

6-Apr-2013
After some real world use, I ran into the typical performance problems associated with type-less systems as I read in hundreds of thousands of records containing doubles. So I have created a function to read doubles from the DBF in their native format without conversion to or from strings. It is updated on this page and on the github project.

Also I bumped into an excellent and free program (but not open-source) that does much more than my library and works well. It is at dbfviewer.org and I have been using it to validate my code. Check it out if you need a great and free dbf viewer!

Here is a link to the github version of this project. https://github.com/rostafichuk/DBFEngine

Here are the files for including in your own project.

  • dbf.h
  • dbf.cpp
  • main.cpp

I am happy to get any feedback on this, I hope someone will find it useful!

File: dbf.h

#ifndef DBF_H
#define DBF_H

// Copyright (C) 2012 Ron Ostafichuk
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef unsigned char uint8;
typedef short int uint16;
typedef int uint32;

#define MAX_FIELDS 255
#define DBF_DELETED_RECORD_FLAG '*' // found by reading with hex editor
#define MAX_RECORD_SIZE 0xffff*50    // not idea if this is correct, but good enough for my needs

struct fileHeader
{
    uint8 u8FileType;
    uint8 u8LastUpdateYear;
    uint8 u8LastUpdateMonth;
    uint8 u8LastUpdateDay;
    uint32 uRecordsInFile;
    uint16 uPositionOfFirstRecord;
    uint16 uRecordLength; // includes the delete flag (byte) at start of record
    uint8 Reserved16[4*4]; // 16 bytes reserved, must always be set to zeros
    uint8 uTableFlags;
    uint8 uCodePage;
    uint8 Reserved2[2]; // 2 bytes reserved, must put zeros in all reserved fields
};

// after the file header, we can have n field definition records, terminated by the byte 0x0D
// must build as a union to make sure bytes are aligned correctly
struct fieldDefinition
{
    char cFieldName[11];
    char cFieldType;
    uint32 uFieldOffset; // location of field from start of record
    uint8 uLength; // Length of Field in bytes
    uint8 uNumberOfDecimalPlaces;
    uint8 FieldFlags;
    uint8 uNextAutoIncrementValue[4]; // 32 bit int
    uint8 uAutoIncrementStep;
    uint8 Reserved8[8]; // should always be zero
};
// terminated by the byte 0x0D then 263 bytes of 0x00
// then the records start


class DBF
{
public:
    DBF();
    ~DBF();

    int open(string sFileName,bool bAllowWrite=false); // open an existing dbf file
    int close();

    int markAsDeleted(int nRecord); // mark this record as deleted
    int create(string sFileName,int nNumFields); // create a new dbf file with space for nNumFields
    int assignField(fieldDefinition myFieldDef,int nField); // used to assign the field info ONLY if num records in file = 0 !!!
    int appendRecord(string *sValues, int nNumValues); // used to append records to the end of the dbf file

    int getFieldIndex(string sFieldName);
    int loadRec(int nRecord); // load the record into memory
    bool isRecordDeleted(); // check if loaded record is deleted
    string readField(int nField); // read the request field as a string always from the loaded record!
    double readFieldAsDouble(int nField); // read the request field as a double to get higher performance for 'B' type fields only!


    void dumpAsCSV(); // output fields and records as csv to std output

    int GetNumRecords()
    {
        return m_FileHeader.uRecordsInFile;
    }
    int GetNumFields()
    {
        return m_nNumFields;
    }
    string GetFieldName(int nField)
    {
        return string(m_FieldDefinitions[nField].cFieldName);
    }

    string convertInt(int number)
    {
       stringstream ss;//create a stringstream
       ss << number;//add number to the stream
       return ss.str();//return a string with the contents of the stream
    }

    string convertNumber(uint8 *n, int nSize)
    {
       // convert any size of number (represented by n[] ) into a string
       long long nResult = 0;
       for( int i = 0 ; i < nSize ; i++ )
       {
           nResult += (((unsigned long long) n[i]) << (i*8) );
       }
       stringstream ss; //create a stringstream
       ss << nResult; //add number to the stream
       return ss.str(); //return a string with the contents of the stream
    }

    int ConvertStringToInt(string sInteger,int nSize, char *cRecord)
    {
        // convert the given string into an integer of nSize bytes (2 or 4 or 8 only)
        if( nSize == 2 )
        {
            union {
                short int i;
                uint8 n[4];
            } u;
            stringstream ss;
            ss << sInteger;
            ss >> u.i;

            for( int i = 0 ; i < nSize ; i++ )
                cRecord[i] = u.n[i];

            return 0;
        }
        else if( nSize == 4 )
        {
            union {
                int i;
                uint8 n[4];
            } u;
            stringstream ss;
            ss << sInteger;
            ss >> u.i;

            for( int i = 0 ; i < nSize ; i++ )
                cRecord[i] = u.n[i];

            return 0;
        }
        else if( nSize ==8 )
        {
            union {
                long i;
                uint8 n[8];
            } u;
            stringstream ss;
            ss << sInteger;
            ss >> u.i;

            for( int i = 0 ; i < nSize ; i++ )
                cRecord[i] = u.n[i];

            return 0;
        }

        // fail, clear the record
        for( int i = 0 ; i < nSize ; i++ )
            cRecord[i] = 0;
        return 1; // fail
    }

    int ConvertStringToFloat(string sFloat,int nSize, char *cRecord)
    {
        // convert the given string into a float or a double
        if( nSize == 4 )
        {
            union {
                float f;
                uint8 n[4];
            } u;
            stringstream ss;
            ss.precision(8); // ensure string conversion maintains single precision
            ss << sFloat;
            ss >> u.f;

            for( int i = 0 ; i < nSize ; i++ )
                cRecord[i] = u.n[i];

            return 0;
        } else if( nSize == 8 )
        {
            union {
                double d;
                uint8 n[8];
            } u;
            stringstream ss;
            ss.precision(17); // ensure string conversion maintains double precision
            ss << sFloat;
            ss >> u.d;

            for( int i = 0 ; i < nSize ; i++ )
                cRecord[i] = u.n[i];
            return 0;
        }

        // fail, clear the record
        for( int i = 0 ; i < nSize ; i++ )
            cRecord[i] = 0;
        return 1; // fail
    }

private:
    FILE * m_pFileHandle;
    string m_sFileName;

    bool m_bStructSizesOK; // this must be true for engine to work!
    bool m_bAllowWrite;
    fileHeader m_FileHeader;
    fieldDefinition m_FieldDefinitions[MAX_FIELDS]; // allow a max of 255 fields
    int m_nNumFields; // number of fields in use

    int updateFileHeader();

    char *m_pRecord;

};

#endif // DBF_H

File: dbf.cpp

#include "dbf.h"

// Copyright (C) 2012 Ron Ostafichuk
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

DBF::DBF()
{
    m_pFileHandle = NULL;
    m_nNumFields = 0;
    m_bAllowWrite = false;
    m_bStructSizesOK = true;
    if( sizeof( fileHeader ) != 32 )
    {
        std::cerr << __FUNCTION__ << " fileHeader Structure is padded and will not work! Must be 32, but is " << sizeof( fileHeader ) << std::endl;
        m_bStructSizesOK = false;
    }
    if( sizeof( fieldDefinition ) != 32 )
    {
        std::cerr << __FUNCTION__ << " fieldDefinition Structure is padded and will not work! Must be 32, but is " << sizeof( fieldDefinition ) << std::endl;
        m_bStructSizesOK = false;
    }

    m_pRecord = new char[MAX_RECORD_SIZE];
}

DBF::~DBF()
{
    if( m_pFileHandle != NULL )
        fclose(m_pFileHandle);

    m_pFileHandle = NULL;
    delete [] m_pRecord;
}

int DBF::open(string sFileName,bool bAllowWrite)
{
    // open a dbf file for reading only
    m_sFileName = sFileName;
    if( bAllowWrite && !m_bStructSizesOK )
        bAllowWrite = false; // DO NOT WRITE IF ENGINE IS NOT COMPILED PROPERLY!
    m_bAllowWrite = bAllowWrite;

    char cMode[10] = "rb"; // for windows we MUST open in binary mode ALWAYS!!!  Linux does not care
    if( m_bAllowWrite )
        strncpy(cMode,"rb+",3); // change to read write mode

    m_pFileHandle = fopen(sFileName.c_str(),cMode);
    if( m_pFileHandle == NULL )
    {
        std::cerr << __FUNCTION__ << " Unable to open file " << sFileName << std::endl;
       return errno;
    }

    // open is ok, so read in the File Header

    int nBytesRead = fread (&m_FileHeader,1,32,m_pFileHandle);
    if( nBytesRead != 32 )
    {
        std::cerr << __FUNCTION__ << " Bad read for Header, wanted 32, got " << nBytesRead << std::endl;
        return 1; // fail
    }

    std::cout << "Header: Type=" << m_FileHeader.u8FileType << std::endl
      << "  Last Update=" << (int) m_FileHeader.u8LastUpdateDay << "/" << (int) m_FileHeader.u8LastUpdateMonth << "/" << (int) m_FileHeader.u8LastUpdateYear << std::endl
      << "  Num Recs=" << m_FileHeader.uRecordsInFile << std::endl
      << "  Rec0 position=" << m_FileHeader.uPositionOfFirstRecord << std::endl
      << "  Rec length=" << m_FileHeader.uRecordLength << std::endl
      << "  CodePage=" << (int) m_FileHeader.uCodePage << std::endl
      << "  TableFlags=" << (int) m_FileHeader.uTableFlags << std::endl;

    m_nNumFields = 0;
    // now read in all the field definitions
    std::cout << "Fields: " << std::endl;
    do
    {
        int nBytesRead = fread(&(m_FieldDefinitions[m_nNumFields]),1,32,m_pFileHandle);
        if( nBytesRead != 32 )
        {
            std::cerr << __FUNCTION__ << " Bad read for Field, wanted 32, got " << nBytesRead << std::endl;
            return 1;
        }

        if( m_FieldDefinitions[m_nNumFields].cFieldName[0] == 0x0D || strlen(m_FieldDefinitions[m_nNumFields].cFieldName) <= 1 )
        {
            // end of fields
            break;
        }
        // show field in std out
        std::cout << "  " << m_FieldDefinitions[m_nNumFields].cFieldName << ", Type=" << m_FieldDefinitions[m_nNumFields].cFieldType
              << ", Offset=" << (int) m_FieldDefinitions[m_nNumFields].uFieldOffset << ", len=" << (int) m_FieldDefinitions[m_nNumFields].uLength
              << ", Dec=" << (int) m_FieldDefinitions[m_nNumFields].uNumberOfDecimalPlaces << ", Flag=" << (int) m_FieldDefinitions[m_nNumFields].FieldFlags << std::endl;

        m_nNumFields++;
    }while(!feof(m_pFileHandle));

    // move to start of first record
    int nFilePosForRec0 = 32+32*m_nNumFields+264;
    if( m_FileHeader.uPositionOfFirstRecord != nFilePosForRec0 )
    {
        // bad Rec0 position calc!!!  debug it!
        std::cerr << __FUNCTION__ << " Bad Rec 0 file position calculated " << nFilePosForRec0 << ", header says " << m_FileHeader.uPositionOfFirstRecord << std::endl;
        return 1;
    }

    return 0; // ok
}

int DBF::close()
{
    int nRet = fclose(m_pFileHandle);
    m_pFileHandle = NULL;
    m_sFileName = "";
    m_nNumFields = 0;
    m_bAllowWrite = false;
    m_FileHeader.u8FileType = 0;
    return nRet;
}

int DBF::getFieldIndex(string sFieldName)
{
    for( int i = 0 ; i < m_nNumFields ; i++ )
    {
        if( strncmp(m_FieldDefinitions[i].cFieldName,sFieldName.c_str(),10) == 0 )
            return i;
    }
    return -1; // not found
}

int DBF::loadRec(int nRecord)
{
    // read as a string always!  All modern languages can convert it later
    int nPos = m_FileHeader.uPositionOfFirstRecord + m_FileHeader.uRecordLength*nRecord;
    int nRes = fseek(m_pFileHandle,nPos,SEEK_SET);
    if ( nRes != 0 )
    {
        std::cerr << __FUNCTION__ << " Error seeking to record " << nRecord << " at " << nPos << " err=" << ferror (m_pFileHandle) << std::endl;
        return 1;
    }

    if( nRes != 0)
    {
        for( unsigned int i=0;itm_year % 100; // convert yr to 2 digits
    m_FileHeader.u8LastUpdateDay = timePtr->tm_mday;
    m_FileHeader.u8LastUpdateMonth = timePtr->tm_mon+1;
    m_FileHeader.u8LastUpdateYear = nYear;
    m_FileHeader.uCodePage = 0; // copied from another db, no idea what this corresponds to
    m_FileHeader.uPositionOfFirstRecord = 32+32*nNumFields+264; // calculated based on the file header size plus the n*FieldDef size + 1 term char + 263 zeros
    m_FileHeader.uRecordLength = 0;
    m_FileHeader.uRecordsInFile = 0;
    m_FileHeader.uTableFlags = 0; // bit fields, copied from another db , 0x01=has a .cdx?, 0x02=Has Memo Fields, 0x04=is a .dbc?

    // write the File Header for the first time!
    fwrite(&m_FileHeader,1,sizeof(m_FileHeader),m_pFileHandle);

    // now write dummy field definition records until the real ones can be specified
    for( int i = 0; i < nNumFields ; i++ )
    {
        for( int j = 0; j < 11 ; j++ )
            m_FieldDefinitions[i].cFieldName[j]=0; // clear entire name
        m_FieldDefinitions[i].cFieldType='C';
        m_FieldDefinitions[i].FieldFlags=0;
        m_FieldDefinitions[i].uAutoIncrementStep=0;
        m_FieldDefinitions[i].uNextAutoIncrementValue[0]=0;
        m_FieldDefinitions[i].uNextAutoIncrementValue[1]=0;
        m_FieldDefinitions[i].uNextAutoIncrementValue[2]=0;
        m_FieldDefinitions[i].uNextAutoIncrementValue[3]=0;
        m_FieldDefinitions[i].uLength=0;
        m_FieldDefinitions[i].uNumberOfDecimalPlaces=0;
        m_FieldDefinitions[i].Reserved8[i]=0;

        // write the definitions
        fwrite(&m_FieldDefinitions[i],1,sizeof(fieldDefinition),m_pFileHandle);
    }
    // write the field definition termination character
    char FieldDefTermination[2];
    FieldDefTermination[0] = 0x0D;
    FieldDefTermination[1] = 0;

    fwrite(FieldDefTermination,1,1,m_pFileHandle);
    // write the 263 bytes of 0
    char cZero[263];
    for( int j=0; j<263;j++)
        cZero[j]=0;

    fwrite(&cZero[0],1,263,m_pFileHandle);
    // this is now the starting point for the first record
    // ready to assign the field definitions!

    // make sure change is made permanent, we are not looking for speed, just reliability and compatibility
    fflush(m_pFileHandle);

    return 0;
}

int DBF::updateFileHeader()
{
    // move to file start
    int nRes = fseek(m_pFileHandle,0,SEEK_SET);
    if( nRes != 0)
        return 1; //fail

    // update the last modified date
    time_t t = time(NULL);
    tm* timePtr = localtime(&t);
    int nYear = timePtr->tm_year % 100; // convert yr to 2 digits
    m_FileHeader.u8LastUpdateDay = timePtr->tm_mday;
    m_FileHeader.u8LastUpdateMonth = timePtr->tm_mon+1;
    m_FileHeader.u8LastUpdateYear = nYear;

    // write the current header info
    int nBytesWritten = fwrite(&m_FileHeader,1,sizeof(m_FileHeader),m_pFileHandle);
    if( nBytesWritten != sizeof(m_FileHeader) )
    {
        // error!
        std::cerr << __FUNCTION__ << " Failed to update header!" << std::endl;
        return 1;
    }
    return 0;
}

int DBF::assignField(fieldDefinition fd,int nField)
{
    // used to assign the field info ONLY if num records in file = 0 !!!
    if( m_FileHeader.uRecordsInFile != 0)
    {
        std::cerr << __FUNCTION__ << " Failed to AssignField Can not change Fields once the File has records in it!" << std::endl;
        return 1; // fail
    }

    // set the unused characters for the field name to zero
    int nPos = strlen(fd.cFieldName);
    for( int i=nPos; i < 11 ; i++ )
        fd.cFieldName[i]=0;

    // this engine does not support auto increment, set it to zero
    fd.uAutoIncrementStep=0;
    fd.uNextAutoIncrementValue[0]=0;
    fd.uNextAutoIncrementValue[1]=0;
    fd.uNextAutoIncrementValue[2]=0;
    fd.uNextAutoIncrementValue[3]=0;

    for( int i=0; i<8;i++)
        fd.Reserved8[i] = 0; // must always be set to zeros!

    // add some rules to prevent creation of invalid db.
    if( fd.cFieldType=='I' )
    {
        fd.uLength = 4;
    }else if( fd.cFieldType=='B' )
    {
        fd.uLength = 8; // actual double, not text!
    }else if( fd.cFieldType=='L' )
    {
        fd.uLength = 1;
    } else
    {
        //default case
        if( fd.uLength < 1 )
            fd.uLength=1;
    }

    // calculate the proper field offset based on corrected data
    if( nField == 0 )
        fd.uFieldOffset = 1;
    else
    {
        fd.uFieldOffset = 1;
        for( int i=0;i 0 )
                std::cerr << "Unable to convert '" << sFieldValue << "' to int "
                          << m_FieldDefinitions[f].uLength << " bytes" << std::endl;
        }
        else if( cType== 'B' )
        {
            // float or double
            int res = ConvertStringToFloat(sFieldValue,m_FieldDefinitions[f].uLength,&m_pRecord[m_FieldDefinitions[f].uFieldOffset]);
            if( res > 0 )
            {
                std::cerr << "Unable to convert '" << sFieldValue << "' to float "
                          << m_FieldDefinitions[f].uLength << " bytes" << std::endl;
            }
        }
        else if( cType== 'L' )
        {
            // logical
            if( sFieldValue=="T" || sFieldValue=="TRUE" )
                m_pRecord[m_FieldDefinitions[f].uFieldOffset] = 'T';
            else if( sFieldValue=="?")
                m_pRecord[m_FieldDefinitions[f].uFieldOffset] = '?';
            else
                m_pRecord[m_FieldDefinitions[f].uFieldOffset] = 'F';
        } else
        {
            // default for character type fields (and all unhandled field types)
            for( unsigned int j=0;j 0 ; i-- )
            {
                if( s[i] == ' ' )
                    s.erase(i,1);
                else
                    break; // done
            }
            // trim left spaces
            for( int i = 0 ; i < s.length() ; i++ )
            {
                if( s[i] == ' ' )
                {
                    s.erase(i,1);
                    i--;
                }
                else
                    break; // done
            }

            int nFind = s.find(",");
            if( nFind > -1 )
            {
                // put string in double quotes!
                // need quotes (make sure string also does not have double quotes, NOT DONE!)
                nFind = s.find("\"");
                while( nFind > -1 )
                {
                    s[nFind] = '\''; // convert double quote(34) to single quote to prevent errors reading this csv
                    nFind = s.find("\"");
                }
                std::cout << ",\"" << s << "\"";
            }
            else
                std::cout << "," << s;
        }
        std::cout << std::endl;
    }
}

File: main.cpp

#include "dbf.h"

using namespace std;

int main(int argc, char *argv[])
{

    std::cout << "Test of DBFEngine Started"<< std::endl;

    // any param is assumed to be a filename to do a read test and dump to std output
    if( argc > 1 )
    {
        string sFileReadTest = "test.dbf";
        sFileReadTest = argv[1]; // do a read test on the given file

        DBF mydbf;
        std::cerr << "Read Test of " << sFileReadTest << std::endl;
        int nRet = mydbf.open(sFileReadTest);
        if( nRet )
        {
            std::cerr << "Unable to Open File " << sFileReadTest << std::endl;
        } else
        {
            int nRecs = mydbf.GetNumRecords();
            int nFields = mydbf.GetNumFields();
            std::cout << "Open() found " << nRecs << " records, and " << nFields << " fields." << std::endl;
            mydbf.dumpAsCSV();
            std::cout << "Done Read Test " << std::endl;

            mydbf.close();
        }
    } else
    {
        // no param, means to run a test to create, read and delete a record in the dbf

        // now create a db
        std::cout << "Create file TestCreate.dbf from code" << std::endl;
        DBF newdbf;
        int nRet = newdbf.create("TestCreate.dbf",5);
        if( nRet == 0 )
        {
            // create the 5 fields
            fieldDefinition fd;
            strncpy(fd.cFieldName,"ID",10);
            fd.cFieldType='I';
            fd.FieldFlags=0;
            fd.uAutoIncrementStep=0;
            fd.uFieldOffset=0;
            fd.uLength=4;
            fd.uNumberOfDecimalPlaces=0;
            newdbf.assignField(fd,0);

            strncpy(fd.cFieldName,"FirstName",10);
            fd.cFieldType='C';
            fd.FieldFlags=0;
            fd.uAutoIncrementStep=0;
            fd.uFieldOffset=0;
            fd.uLength=20;
            fd.uNumberOfDecimalPlaces=0;
            newdbf.assignField(fd,1);

            strncpy(fd.cFieldName,"Weight",10);
            fd.cFieldType='F';
            fd.FieldFlags=0;
            fd.uAutoIncrementStep=0;
            fd.uFieldOffset=0;
            fd.uLength=12;
            fd.uNumberOfDecimalPlaces=0;
            newdbf.assignField(fd,2);

            strncpy(fd.cFieldName,"Age",10);
            fd.cFieldType='N';
            fd.FieldFlags=0;
            fd.uAutoIncrementStep=0;
            fd.uFieldOffset=0;
            fd.uLength=3;
            fd.uNumberOfDecimalPlaces=0;
            newdbf.assignField(fd,3);

            strncpy(fd.cFieldName,"Married",10);
            fd.cFieldType='L';
            fd.FieldFlags=0;
            fd.uAutoIncrementStep=0;
            fd.uFieldOffset=0;
            fd.uLength=1;
            fd.uNumberOfDecimalPlaces=0;
            newdbf.assignField(fd,4);


            // now create some records to test it!
            string s1[5]= {"1" ,"Ric G","210.123456789123456","43","T"};
            newdbf.appendRecord(&s1[0],5);

            string s2[5]={"1000" ,"Paul F","196.2","33","T"};
            newdbf.appendRecord(s2,5);

            string s3[5]={"20000" ,"Dean K","186.1","23","F"};
            newdbf.appendRecord(s3,5);

            string s4[5]={"300000" ,"Gary\" Q","175.123456789","13","F"};
            newdbf.appendRecord(s4,5);

            string s5[5]={"2000000" ,"Dan'e \"D, with comma and over sized field that will be truncated","65.2","6","F"};
            newdbf.appendRecord(s5,5);

            // now add a huge pile of random records to see if it crashes
            int nID = 2000001;
            for( int i=0; i < 1000; i++)
            {
                stringstream ssName;
                ssName << "FirstName" << nID;

                float fWeight = (float) nID / 40000.0;
                int nAge = i % 120;


                stringstream ssID;
                ssID << nID;

                stringstream ssWeight;
                ssWeight << fWeight;

                stringstream ssAge;
                ssAge << nAge;

                string sMarried = "F";
                if( nID % 2 == 0 )
                    sMarried = "T";

                string sRec[5]={ssID.str(),ssName.str(),ssWeight.str(),ssAge.str(),sMarried};
                newdbf.appendRecord(sRec,5);

                nID++;
            }



            newdbf.close();

            std::cout << "Done Creating the DBF" << std::endl;

            std::cout << "Open the DBF for reading and writing" << std::endl;
            // now read the dbf back to test that it worked!
            DBF readTest;
            readTest.open("TestCreate.dbf",true);
            int nRecs = readTest.GetNumRecords();
            int nFields = readTest.GetNumFields();
            std::cout << "Open() found " << nRecs << " records, and " << nFields << " fields." << std::endl;
            readTest.dumpAsCSV();
            std::cout << "Done Reading a freshly created DBF! " << std::endl;

            std::cout << "Test Delete Record 1 in DBF! " << std::endl;
            std::cout << "Test Delete Record 999 in DBF! " << std::endl;
            readTest.markAsDeleted(1);
            readTest.markAsDeleted(999);
            readTest.dumpAsCSV();
            std::cout << "Done Test Delete Record DBF! " << std::endl;

            readTest.close();
        }
    }
    return 0;
}