65.9K
CodeProject 正在变化。 阅读更多。
Home

在MDI应用程序对话框中使用修改后的CListCtrl类(虚拟模式)处理时,创建和内存映射现有DBF文件作为数据序列化的替代方案

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (6投票s)

2009年7月22日

CPOL

11分钟阅读

viewsIcon

48844

downloadIcon

1493

演示了在内存中读取、写入和创建标准的DBF文件(随机访问),以替代典型的MFC应用程序为虚拟模式下的CListCtrl类后代进行数据序列化。

引言

最后,我们可以为我们的应用程序设置一个任务,即从数据文件中保存和读取必要的信息。在典型的MFC程序中,标准的数据序列化通常由CDocument类支持。原则上,没有什么可以阻止我们为此目的使用此方法。然而,数据保存格式的问题立刻出现了。当然,我们可以发明自己的数据格式,并为其指定一个扩展名,例如*.eee,然后按照我们认为方便的方式处理它。也许,出于教育目的,或者在信息序列化原则具有重要意义的情况下,这种方法是相当不错的。但是,由于我们正在设计一个需要对文件内存进行随机访问的工作应用程序,使用序列化对我们来说似乎并不是一个最佳的解决方案。此外,开发自己的数据格式需要非常充分的理由。我认为满足个人虚荣心并非如此。

因此,首先,我们必须确定现有的数据格式,其次,定义文件信息随机访问的方法。按照这样的表述,这些任务的解决办法是显而易见的。最简单且广为人知的数据格式是dbf文件结构,它已经证明了其有效性。而通过利用内存映射文件(MMF)技术,可以轻松实现对数据文件内容的随机访问。这项技术的主要优点在于我们可以像处理普通内存一样处理文件。虽然我们可以为了优化而预先读取少量但频繁使用的数据(例如,用于描述数据库结构的元数据),但没有必要这样做。此外,可以直接利用数据库的static结构来随机访问其数据元素,避免了进行数据分块的必要性,这对于虚拟模式下的CListCtrl类及其后代的填充非常方便。而且,MMF技术易于使用。优点如此之多,以至于在付出几乎相同的努力的情况下,我们获得了内存映射文件随机访问的所有优势,因此没有必要讨论信息序列化。

在此阶段,我们按如下方式组织工作。在打开某个表单时,程序会搜索我们元数据中描述的相应dbf文件。如果找到,它会尝试打开并使用其信息来填充相应的列表;否则,它会创建该dbf文件,并从我们的静态变量读取信息。如果用外部dbf文件编辑器更改了该文件,在下次读取时,我们将看到新的信息。稍后,我们将学习直接在我们的程序中编辑dbf文件。本项目已提供准备好的文件,位于Dbf文件夹中。它们的文件名是“First.dbf”、“Second.dbf”和“Third.dbf”。它们具有相似的结构(在第三个文件中,“Name”和“Title”字段的位置已互换),但数据排序不同。它们都包含360条记录。例如,如果我们删除文件“Third.dbf”,我们的程序将创建一个同名的文件,并用我们的静态变量填充其数据。结果将是图1所示的示例。

Tables04.jpg

图 1. 使用dbf文件(包括创建的)在虚拟模式下填充列表。

1. DBF文件结构

在互联网上不难找到关于dbf文件的必要信息,因为这种格式是开放的且早已为人所知。然而,我们未能找到官方规范。因此,我们将简要说明其结构,参见图2。

Dbf04.jpg

图 2. dbf文件模式以及DBF_FILE1DBF_FILE2结构的定义范围。

如果我们预先知道数据库中的字段数量FLDCOUNT = N,它们的长度FLDLEN1 = M1, FLDLEN2 = M2, . . ., FLDLENn = Mn以及记录数量RECCOUNT = K,则相应的dbf文件结构可以定义为

//*** dbf 文件结构
typedef struct {
    DBF_HEADER DbfHdr;
    DBF_FIELD aDbfField[FLDCOUNT];  // FLDCOUNT = (DbfHdr.wFirstRec-296)/32 for VFP
    BYTE cHdrEnd;  // Header record terminator = 0x0D (13)
    BYTE acDbcFile[263];  // A data range for name of associated *.dbc file or contains 0x00
    DBF_RECORD aDbfRec[RECCOUNT];  // RECCOUNT = DbfHdr.nRecCount
    BYTE cFileEnd;  // File record terminator = 0x1A (26)
} DBF_FILE;

其中相应的结构定义为

//*** dbf 头结构
typedef struct {
    /*00-00*/ BYTE cType;  // File type = 0x30 (48) for Visual FoxPro (VFP)
    /*01-01*/ BYTE cYear;  // Year of last update (for VFP nFullYear = 2000 + cYear)
    /*02-02*/ BYTE cMonth;  // Month of last update
    /*03-03*/ BYTE cDay;  // Day of last update
    /*04-07*/ ULONG nRecCount;    // Number of records in file
    /*08-09*/ WORD wFirstRec;  // Position of first data record
    /*10-11*/ WORD wRecSize;  // Length of one data record, including delete flag
    /*12-27*/ BYTE cReserv1227[16];  // Reserved, contains 0x00
    /*28-28*/ BYTE cFlag;  // Table Flags (Only Visual FoxPro)
    /*29-29*/ BYTE cCodePage;  // Code page mark = 0x03 (3) for code page 1252 (Windows ANSI)
    /*30-31*/ WORD wReserv3031;  // Reserved, contains 0x00
} DBF_HEADER;
//*** dbf 字段结构
typedef struct {
    /*00-10*/ BYTE cName[11];  // Field name with null terminator (0x00)
    /*11-11*/ BYTE cType;  // Field type (C – Character; N – Numeric; D – Date and so on)
    /*12-15*/ ULONG nOffset;  // Displacement of field in record
    /*16-16*/ BYTE cLen;  // Length of field (in bytes)
    /*17-17*/ BYTE cDec;  // Number of decimal places
    /*18-18*/ BYTE cFlag;  // Field flag
    /*19-22*/ LONG nNext;  // Value of auto increment Next value 
    /*23-23*/ BYTE cStep;  // Value of auto increment Step value 
    /*24-31*/ BYTE cReserv2431[8];  // Reserved, contains 0x00
} DBF_FIELD;
//*** dbf 记录结构
typedef struct {
    BYTE cDelete;  // Delete flag
    BYTE cData1[FLDLEN1];  // Where FLDLEN1 = aDbfField[0].cLen
    BYTE cData2[FLDLEN2],  // Where FLDLEN2 = aDbfField[1].cLen
    . . .
    BYTE cDataN[FLDLENN];  // Where FLDLENN = aDbfField[N-1].cLen
} DBF_RECORD;

这里显示了dbf文件的静态结构(我们以VFP格式为参考,文件类型为0x30(48))。对于普通内存中的动态结构,数据组织方式必须不同——通过指针(大小已知)指向完整的数据结构,而这些结构的大小在程序编译时(或不希望使用,以免限制我们在特定文件结构上)是未知的。而且,由于VS6 C++编译器无法创建大小仅在程序执行时(运行时)确定的动态数据类型,因此我们无法使用上面明确描述的结构来直接操作文件信息。但我们也不希望预先读取记录信息到普通内存中,以进行分析和转换以供将来使用。同样,我们也会丢失MMF的优势。因此,任务就变成了最大程度地用静态结构描述动态结构,以便在程序执行时有效地从数据库(在此例中为dbf文件)中选择数据元素。

2. 动态结构(未知大小)通过静态结构定义

这个主题本身足够重要,值得我们花一点时间来讨论。普通编译器无法创建动态数据类型,当结构的大小在运行时确定时。这里指的是“数据类型”,而不是为此保留的动态存储区。我们将通过一个已知的例子来说明这一点。假设我们想创建一个字节数组,其长度是动态确定的,例如

UINT nX = 100;
BYTE acData[nX];

但是,对于这个非常简单的结构,编译器会“抱怨”。它不希望看到nX,而是期望看到常量表达式,就像

BYTE acData[100];

因此,必须明确指定常量表达式。例如,表达式

UINT nCount  = sizeof(aKnownStructure)/ sizeof(aKnownStructure[0]);
acData[nCount];

将是错误的,而直接使用

acData[sizeof(aKnownStructure)/ sizeof(aKnownStructure[0])];

将是正确的。

然而,可以通过放弃明确的静态数组声明,将其替换为非明确的静态声明来构建指定大小的动态数组。也就是说,而不是

BYTE acData[nX];  // Correctly, BYTE acData[100];

我们写

BYTE *acData = new BYTE[nX];  // UINT nX = 100;

这里的区别在于,第一种情况下sizeof(acData) = 100,而第二种情况sizeof(acData) = 4。也就是说,在第二种情况下,我们实际上并没有一个大小为100字节的动态类型,而是一个长度为4字节的静态类型,因为它是指向一个大小为100字节的动态选定存储区的指针的大小。从这些数组元素的使用的角度来看没有区别,而从使用结构的大小来看则存在显著差异。下面再举一个动态构建二维数组的例子。

//*** Dynamic array of field names of dbf file
BYTE **aaсFldName = new BYTE *[m_nFldCount];

//*** Initializes arrays of names of dbf fields
for(int i = 0; i < m_nFldCount; i++) {
    //*** Field name with a maximum of 10 characters with null terminator
    m_aacFldName[i] = new BYTE[11];

    //*** Copies field name
    for(int j = 0; j < 11; j++)
            m_aacFldName[i][j] = aDbfField[i].acName[j];
}

这是我们应用程序中的真实代码。显然,这种技术对于复制大小在编译时未知的动态结构来说是很好的。但对于经常使用且不大的元数据来说还可以,但对于庞大的数据元素数组来说就不太好了。我们希望在直接使用它们时(无需预先复制和分析数据结构)就能够处理它们。但是,为此需要提前知道数据库文件的关键参数,以便在此基础上构建完整数据结构的静态类型。正如前面描述的dbf文件结构所示,需要明确了解那里指示的参数。但如前所述,即使我们知道这些参数,我们也不想在程序中静态地指定它们,因为这只会限制我们考虑特定文件。当然,我们可以预先描述程序中使用的所有dbf文件,这很常见,但是,如果我们希望访问任意dbf文件,这种技术就不适合我们了。

因此,我们遇到了一个矛盾。一方面,数据库结构的静态描述(如在第一部分开头所示)允许我们利用内存映射文件,但限制了我们使用固定集合的这些结构(即,一定数量的具体文件类型)。另一方面,通过指向大小未知的结构来动态描述数据结构,导致需要使用未知结构的输入数据流,这使我们失去了MMF技术明显的优势,即数据库元素的随机访问。

例如,作者“Alexey的用于处理DBF文件的alxBase类”走了第二条路,因此,他没有使用MMF技术。他采用经典的方法,通过明确地定位文件指针来检索dbf文件中的数据,然后读取数据元素。然而,当我们直接写入类似(右侧是文件内存元素)的东西时,有一件事

BYTE *acDataElement = acRecord[j].acField[i];  // ~ BYTE acDataElement[aDbfField[i].cLen];

而另一件事是,我们必须首先计算当前数据元素的文件的指针,定位到它,然后才能读取信息。但这不是最主要的(对于dbf文件来说,自己做并不难),而是需要处理共享(多用户、多线程)访问通用数据时的组织问题。据我所知,Alexey将自己限制在对数据库的独占访问,这极大地限制了他的库的使用。这一点已经很明显了,因为他从2005年就停止了他的项目支持(至少是公开的)。在MMF技术中,组织数据共享访问的可能性要大得多,所以,我认为,不必忽略这种可能性。

但是,让我们回到我们的“矛盾”。很明显,我们对第一种和第二种方式都不满意。如果有人知道第三种方法,我们很想了解。我们将努力尽可能地统一这些方法。由于我们无法提前完全静态地描述dbf文件结构,因此我们只能进行部分静态描述,而且不是一个类似的结构,而是两个。如果我们再次仔细查看上面给出的dbf文件结构(这种结构不会被编译器“理解”),我们可以很容易地看到,其中可以选出两个静态部分,它们将对编译器“可见”。即

//*** dbf 文件结构 (第1部分)
typedef struct {
    DBF_HEADER DbfHdr;  // Dbf header structure
    DBF_FIELD aDbfField[1];  // Really FLDCOUNT = (DbfHdr.wFirstRec-296)/32 fields
} DBF_FILE1;
//*** dbf 文件结构 (第2部分)
typedef struct {
    BYTE cHdrEnd;  // Header record terminator = 0x0D (13)
    BYTE acDbcFile[263];  // A data range for associated *.dbc file or contains 0x00
    BYTE aDbfRec[1];  // Really DbfHdr.wRecSize * DbfHdr.nRecCount records
    //BYTE cFileEnd;  // File record terminator = 0x1A (26)
} DBF_FILE2;

此外,我们将利用已知的“黑客”技术——有意将数据移出静态声明的数组。这种组合方法的“代价”将是从“平面”、“二维”索引(j, i)转变为“一维”、线性索引ji = j*nColCount + i(用于计算信息单元的线性编号),以及计算总体线性位移nLineInd = j*nRowSize + m_anOff[i](以字节为单位)的必要性。但我认为,对于这种方法来说,这代价并不算太大。

我们正是使用这些结构来实际附加到dbf文件的,通过前面提到的线性索引,有意将它们移出静态结构数组aDbfField[1]aDbfRec[1]的范围。实践表明,这是一种非常好的方法,也可以用于其他数据文件,例如,memo字段(fpt文件)、索引cdx文件、数据库容器(dbc文件)等。

3. MMF和现有DBF文件的读取

dbf文件的读取是通过MMF技术实现的,其使用在函数CMainDoc::OnOpenDocument中呈现。

/////////////////////////////////////////////////////////////////////////////
// OpenDocumentFile
/////////////////////////////////////////////////////////////////////////////
BOOL CMainDoc::OnOpenDocument(LPCTSTR szFileName) {  // szFileName isn't using
    TCHAR *szDbfName = m_MetaTable.szDbfName;

    CFileStatus FileStatus;

    //*** If file szDbfName does not exist creates its
    if(!CFile::GetStatus(szDbfName, FileStatus)) {
        //*** Creates current document (dbf) file on physical disk
        if(!CreateDocumentFile(szDbfName)) {
            //_M("CMainDoc: Failed to create a document file!");
            return FALSE;
        }
    }

    //*** Gets handle of dbf file
    m_hDbfFile = ::CreateFile(
            szDbfName,  // Name of dbf file
            GENERIC_READ | GENERIC_WRITE,  // Access (read-write) mode
            FILE_SHARE_READ | FILE_SHARE_WRITE,  // Share mode
            NULL,  // Pointer to security attributes
            OPEN_EXISTING,  // How to create
            FILE_ATTRIBUTE_NORMAL,  // File attributes
            NULL  // HANDLE hTemplateFile - Handle to file with attributes to copy
    );

    if(m_hDbfFile == INVALID_HANDLE_VALUE) {
        _M("CMainDoc: Failed to call ::CreateFile function!");
        return FALSE;
    }

    //*** The message buffer
    TCHAR szStr[MAXITEMTEXT];

    //*** Gets file size in bytes
    ULONG nDbfSize = ::GetFileSize(
        m_hDbfFile,  // Handle of file to get size of
        NULL  // Pointer to high-order word for file size
    );

    //*** Checks file size
    if(nDbfSize == 0) {
        swprintf(
                szStr,
                _T("CMainDoc: File '%s' is empty!"),
                szDbfName
        );

        _M(szStr);
        ::CloseHandle(m_hDbfFile);
        return FALSE;
    }

    //*** Creates file mapping
    m_hDbfMap = ::CreateFileMapping(
            m_hDbfFile,  // Handle to file to map
            NULL,  // Optional security attributes
            PAGE_READWRITE,  // Protection for mapping object
            0,  // High-order 32 bits of object size
            0,  // Low-order 32 bits of object size
            NULL  // Name of file-mapping object
    );

    if(!m_hDbfMap) {
        _M("CMainDoc: Failed to call ::CreateFileMapping function!");
        return FALSE;
    }

    //*** Maps view of dbf file for its first part (where are header)
    m_pDbfView1 = reinterpret_cast<DBF_FILE1 *>(::MapViewOfFile(
            m_hDbfMap,  // File-mapping object to map into address space
            FILE_MAP_WRITE,  // Access mode
            0,  // High-order 32 bits of file offset
            0,  // Low-order 32 bits of file offset
            0  // Number of bytes to map (if it is zero, the entire file is mapped)
    ));

    if(!m_pDbfView1) {
        _M("CMainDoc: Failed to call ::MapViewOfFile function!");
        return FALSE;
    }

    //*** Dbf header structure
    m_pDbfHdr = &m_pDbfView1->DbfHdr;

    //*** Checks dbf file type
    if(m_pDbfHdr->cType != VFPTYPE) {  // = 0x30 (48)
        swprintf(
                szStr,
                _T("CMainDoc: Dbf type: %d doesn't equal to VFP type: %d!"),
                m_pDbfHdr->cType,
                VFPTYPE
        );

        _M(szStr);
        return FALSE;
    }
    /*
    //*** Shows date of last update
    
    swprintf(
            szStr, 
            _T("Date of last update is %0.2d.%0.2d.%d"), 
            m_pDbfHdr->cDay, 
            m_pDbfHdr->cMonth, 
            BASEYEAR + m_pDbfHdr->cYear
    );

    _M(szStr);
    */
    //*** Number of records in file
    m_nRecCount = m_pDbfHdr->nRecCount;

    //*** Length of one data record (including delete flag)
    m_nRecSize = m_pDbfHdr->wRecSize;

    //*** Checks record size
    if((m_nRecSize == 0 && m_nRecCount != 0) ||
         (m_nRecSize != 0 && m_nRecCount == 0)) {
        swprintf(
                szStr,
                _T("CMainDoc: Not matches record size (%d) and record count (%d)!"),
                m_nRecSize,
                m_nRecCount
        );

        _M(szStr);
        return FALSE;
    }

    //*** Calculates size of all records
    ULONG nDataSize = nDbfSize - 1 - m_pDbfHdr->wFirstRec;

    //*** Checks record parameters
    if(nDataSize != m_nRecCount*m_nRecSize) {
        swprintf(
                szStr,
                _T("CMainDoc: Data size (%d) doesn't equal record count (%d) * record size (%d)!"),
                nDataSize,
                m_nRecCount,
                m_nRecSize
        );

        _M(szStr);
        return FALSE;
    }

    //*** Number of fields in file (for Visual FoxPro only)
    m_nFldCount = (m_pDbfHdr->wFirstRec - 296)/32;

    //*** Checks field count
    if(m_nFldCount > m_nRecSize - 1) {
        _M("CMainDoc: Field count is very large!");
        return FALSE;
    }

    //*** Dbf field structure
    DBF_FIELD *aDbfField = m_pDbfView1->aDbfField;

    //*** Maps view of dbf file for its first part (where are data)
    m_pDbfView2 = reinterpret_cast<DBF_FILE2 *>(
            &m_pDbfView1->aDbfField[m_nFldCount].acName[0]
    );

    BYTE cHdrEnd = 0;

    //*** Checks dbf reading
    try {
        cHdrEnd = m_pDbfView2->cHdrEnd;
  } catch(...) {
        _M("CMainDoc: Dbf file has wrong structure!");
        return FALSE;
    }

    //*** Checks dbf header record terminator
    if(cHdrEnd != HEADEREND) {  // = 0x0D (13)
        swprintf(
                szStr, 
                _T("CMainDoc: Header record terminator: %d doesn't equal to: %d!"),
                m_pDbfView2->cHdrEnd,
                HEADEREND
        );

        _M(szStr);
        return FALSE;
    }

    //*** Delete flag  // = " " or "*"
    //_M(pDbfFile2->aDbfRec[0]);

    //*** Dynamic array of field names
    m_aacFldName = new BYTE *[m_nFldCount];

    //*** Dynamic array of field types
    m_acFldType = new BYTE[m_nFldCount];

    //*** Dynamic array of offsets
    m_anOff = new UINT[m_nFldCount];
    
    //*** Dynamic array of lengths
    m_acLen = new BYTE[m_nFldCount];
    
    //*** Dynamic array of decimal places
    m_acDec = new BYTE[m_nFldCount];

    //*** Initializes arrays of length, offsets and etc. of dbf fields
    for(int i = 0; i < m_nFldCount; i++) {
        //*** Field name with a maximum of 10 characters with null terminator
        m_aacFldName[i] = new BYTE[11];

        //*** Copies field name
        for(int j = 0; j < 11; j++)
                m_aacFldName[i][j] = aDbfField[i].acName[j];

        //*** Field type
        m_acFldType[i] = aDbfField[i].cType;

        //*** Field lenght (in bytes)
        m_acLen[i] = aDbfField[i].cLen;

        //*** Number of decimal places (in bytes)
        m_anOff[i] = aDbfField[i].nOffset;

        //*** Number of decimal places (in bytes)
        m_acDec[i] = aDbfField[i].cDec;
    }

    //*** Testing for all field of j-th record
    /*
    //*** j-th record
    ULONG j = 11;
    
    //*** Line displacement of i-th field of j-th record
    ULONG ji = 0;
    
    for(i = 0; i < m_nFldCount; i++) {
        ji = j*m_nRecSize + m_anOff[i];

        //*** The copy of (j, i) field value of m_anLen[i]-th length
        // As it hasn't null terminator
        CString sFldVal((LPCSTR) &m_pDbfMap2->aDbfRec[ji], m_anLen[i]);

        sFldVal.TrimLeft();
        sFldVal.TrimRight();

        swprintf(
                szStr, 
                _T("%s : %c : %d : %d.%d :: '%s'"),
                (CString) aszFldName[i],  // As it has null terminator
                acFldType[i],
                m_anOff[i],
                m_anLen[i],
                m_anDec[i],
                sFldVal
        );

        _M(szStr);
    }
    */
    BYTE cFileEnd = 0;

    //*** Checks dbf reading
    try {
        cFileEnd = m_pDbfView2->aDbfRec[m_nRecCount * m_nRecSize];
  } catch(...) {
        _M("CMainDoc: Dbf file has wrong structure!");
        return FALSE;
    }

    //*** Checks dbf file record terminator
    if(cFileEnd != DBFEND) {  // = 0x1A (26)
        swprintf(
                szStr, 
                 _T("CMainDoc: Header record terminator: %d doesn't equal to: %d!"),
                m_pDbfView2->aDbfRec[m_nRecCount * m_nRecSize], 
                DBFEND
        );

        _M(szStr);
        return FALSE;
    }

    //*** Current table
    CListCtrlEx *pTable = m_pMainApp->m_apTable[m_eTable];
    
    if(!pTable) {
        _M("CMainDoc: Empty a CListCtrlEx object!");
        return FALSE;
    }

    //*** Sets the table rows count in the virtual mode (LVS_OWNERDATA)
    //*** Send messages LVN_GETDISPINFOW & HDM_LAYOUT
    //*** Calls the CListCtrlEx::DrawItem
    pTable->SetItemCount(m_nRecCount);

    //*** Shows the vertical scroll bar always
    //pTable->ShowScrollBar(SB_VERT);

    //*** Saves the current document
    m_pMainApp->m_apDoc[m_eTable] = this;

    return TRUE;
}  // OnOpenDocument

4. DBF文件的创建和用静态数据填充

要创建一个数据库文件,需要提前了解其结构。在我们的演示程序中,有三种静态结构允许创建三个不同的dbf文件。通过改变它们的数量和内容,可以创建足够多的Visual FoxPro格式的数据库文件。以下是我们的元数据的主要结构:

//*** dbf 文件字段数据结构
typedef struct {
    TCHAR *szFldName;  // Field name
    TCHAR *szFldType;  // Field type
    UINT nFldLen;  // Field length (in bytes)
    UINT nDecLen;  // Number of decimal places (in bytes)
} META_DATA;
//*** 元表头结构
typedef struct {
    TCHAR *szHdrName;  // Column name
    DWORD nAdjust;  // Text formatting
    UINT nWidth;  // Column width
} META_HEADER;
//*** 元表结构
typedef struct {
    TCHAR *szDbfName;  // Dbf name
    META_DATA *aMetaData;  // Dbf-file fields data structure
    TCHAR *szTblName;  // Table name
    META_HEADER *apMetaHeader;  // Meta table header structure
    DWORD dwStyle;  // Table style
    DWORD dwExStyle;  // Extended table style
    RECT *pFrmRect;  // Frame rectangle pointer
    RECT *pViewRect;  // View rectangle pointer
    CFont *pHdrFont;  // Table header font pointer
    CFont *pListFont;  // Table list font pointer
    UINT nHdrHeight;  // Table header height
    UINT nListHeight;  // Table list height
    UINT nColCount;  // Table header columns count
    UINT nRowCount;  // Table list row count
    TCHAR **apRowText;  // Table rows text array
} META_TABLE;

使用这些结构,函数CMainApp::CreateDocumentFile创建所需的dbf文件。

/////////////////////////////////////////////////////////////////////////////
// CreateDocumentFile
/////////////////////////////////////////////////////////////////////////////
BOOL CMainDoc::CreateDocumentFile(TCHAR *szDbfName) {
    //*** The dbf file structure
    /*
    typedef struct {
        DBF_HEADER DbfHdr;
        DBF_FIELD aDbfField[FLDCOUNT];  // FLDCOUNT = (DbfHdr.wFirstRec-296)/32 for VFP
        BYTE cHdrEnd;  // Header record terminator = 0x0D (13)
        BYTE acDbcFile[263];  // A data range for associated *.dbc file, contains 0x00
        DBF_RECORD aDbfRec[RECCOUNT];  // RECCOUNT = DbfHdr.nRecCount
        BYTE cFileEnd;  // File record terminator = 0x1A (26)
    } DBF_FILE;
    */
    CFileStatus FileStatus;

    //*** If file szDbfName does exist simply return
    if(CFile::GetStatus(szDbfName, FileStatus))
            return TRUE;

    //*** Number of records in file
    ULONG nRecCount = m_MetaTable.nRowCount;

    //*** Data table fields count
    UINT nFldCount = m_MetaTable.nColCount;

    SYSTEMTIME SysTime = {0};

    //*** Gets system date and time
    GetSystemTime(&SysTime);

    //*** Dbf header structure

    DBF_HEADER DbfHdr = {0};

    DbfHdr.cType = VFPTYPE;  // DBF type
    DbfHdr.cYear = SysTime.wYear%BASEYEAR;  // Year of last update
    DbfHdr.cMonth = SysTime.wMonth;  // Month of last update
    DbfHdr.cDay = SysTime.wDay;  // Day of last update
    DbfHdr.nRecCount = nRecCount;  // Number of records in file
    //DbfHdr.wFirstRec = 0;    // Position of first data record
    //DbfHdr.wRecSize = 0;  // Length of one data record, including delete flag
    //DbfHdr.cFlag = 0;     // Table Flags (Only Visual FoxPro)
    DbfHdr.cCodePage = CP1252;    // Windows ANSI

    //*** Dbf field structure

    DBF_FIELD DbfField = {0};

    //*** Gets handle of dbf file
    HANDLE hDbfFile = ::CreateFile(
            szDbfName,  // Pointer to name of the dbf file
            GENERIC_WRITE,  // Access (read-write) mode
            0,  // Share mode
            NULL,  // Pointer to security attributes
            CREATE_ALWAYS,  // How to create
            FILE_ATTRIBUTE_NORMAL,  // File attributes
            NULL  // HANDLE hTemplateFile - Handle to file with attributes to copy
    );

    //*** Message buffer
    TCHAR szStr[MAXITEMTEXT];

    //*** Checks file creation
    if(hDbfFile == INVALID_HANDLE_VALUE) {
        swprintf(
                szStr,
                _T("CMainApp: Failed to create new file: '%s'!"),
                szDbfName
        );

        _M(szStr);
        ::CloseHandle(hDbfFile);
        return FALSE;
    }

    //*** Number of written bytes
    DWORD dwBytes = 0;

    //*** Writes bytes into file
    WriteFile(hDbfFile, &DbfHdr, sizeof(DbfHdr), &dwBytes, NULL);

    //*** Dinamic array of field length
    BYTE *acFldLen = new BYTE[nFldCount];

    //*** Dinamic array of field types
    BYTE *acFldType = new BYTE[nFldCount];

    UINT nOffset = 1;  // Skips delete byte

    //*** Writes array of DBF_FIELD aDbfField[FLDCOUNT] structures
    for(int i = 0; i < nFldCount; i++) {
        META_DATA MetaData = m_MetaTable.aMetaData[i];
        BYTE *acName = DbfField.acName;
        TCHAR *szFldName = MetaData.szFldName;

        //*** Field name with a maximum of 10 characters, a rest is padded 
        // with 0x00
        //for(int j = 0; j < 11; j++)
                //acName[j] = szFldName[j];
        
        //*** Simply copies
        while(*acName++ = *szFldName++);

        acFldType[i] = MetaData.szFldType[0];  // Field type
        DbfField.cType = acFldType[i];  // Field type
        DbfField.nOffset = nOffset;  // Displacement of field in record
        acFldLen[i] = MetaData.nFldLen;  // Length of field (in bytes)
        DbfField.cLen = acFldLen[i];  // Length of field (in bytes)
        DbfField.cDec = MetaData.nDecLen;  // Number of decimal places

        nOffset += acFldLen[i];

        //*** Writes bytes into file
        WriteFile(hDbfFile, &DbfField, sizeof(DbfField), &dwBytes, NULL);
    }

    //*** Length of one data record, including delete flag
    DbfHdr.wRecSize = nOffset;  // ARE NOT WRITTEN YET!

    //*** Header record terminator
    BYTE cHdrEnd = HEADEREND;  // = 0x0D (13)
    
    //*** Writes bytes into file
    WriteFile(hDbfFile, &cHdrEnd, sizeof(cHdrEnd), &dwBytes, NULL);

    //*** A data range for associated *.dbc file, contains 0x00
    BYTE acDbcFile[263] = {0};

    //*** Writes bytes into file
    WriteFile(hDbfFile, &acDbcFile, sizeof(acDbcFile), &dwBytes, NULL);

    //*** Gets current file pointer
    DWORD nCurOffset = SetFilePointer(hDbfFile, 0, NULL, FILE_CURRENT);

    //*** Position of first data record
    DbfHdr.wFirstRec = nCurOffset;  // ARE NOT WRITTEN YET!

    //*** The delete flag
    BYTE cDelete = 32;  // = 0x20 (" ")

    //*** Line table cell index
    UINT ji = 0;

    //*** Writes array of DBF_RECORD aDbfRec[RECCOUNT] structures
    for(ULONG j = 0; j < nRecCount; j++) {
        //*** Writes bytes into file
        WriteFile(hDbfFile, &cDelete, sizeof(cDelete), &dwBytes, NULL);

        //*** Writes array of BYTE's strings
        for(i = 0; i < nFldCount; i++) {
            ji = j*nFldCount + i;  // Line table cell index
            TCHAR *acRowText = m_MetaTable.apRowText[ji];

            BYTE cFldLen = acFldLen[i];
            BYTE cFldType = acFldType[i];

            //*** Dinamic array of field data
            BYTE *acFldData = new BYTE[cFldLen + 2];  // +2 for sake date format

            //*** Copies field data (into BYTEs from TCHARs)
            for(int k = 0; k < cFldLen; k++)
                    acFldData[k] = acRowText[k];

            //*** Formates our date string (DD.MM.YYYY) into dbf style (YYYYMMDD)
            if(cFldType == 68) {  // = 0x44 ("D") - Date
                if(cFldLen != 8) {
                    swprintf(
                            szStr, 
                            _T("CMainApp: Length of date format is %d. Must be 8!"), 
                            cFldLen
                    );

                    _M(szStr);
                    return FALSE;
                }

                if(cFldLen == 8) {  // Date length for dbf date format
                    //*** Our static date has 10 characters
                    acFldData[8] = acRowText[8];
                    acFldData[9] = acRowText[9];
                    
                    //*** Date format is d1d2.m1m2.y1y2y3y4 . Must be y1y2y3y4m1m2d1d2
                    acFldData[2] = acFldData[8];  // Writes y3
                    acFldData[5] = acFldData[4];  // Writes m2
                    acFldData[4] = acFldData[3];  // Writes m1
                    acFldData[3] = acFldData[9];  // Writes y4
                    acFldData[8] = acFldData[0];  // Saves d1
                    acFldData[9] = acFldData[1];  // Saves d2
                    acFldData[0] = acFldData[6];  // Writes y1
                    acFldData[1] = acFldData[7];  // Writes y2
                    acFldData[6] = acFldData[8];  // Writes d1
                    acFldData[7] = acFldData[9];  // Writes d2
                } 
                //*** Else do nothing
            }

            //*** Writes bytes into file
            WriteFile(hDbfFile, acFldData, cFldLen, &dwBytes, NULL);
        }
    }

    //*** File record terminator
    BYTE cFileEnd = DBFEND;  // = 0x1A (26)

    //*** Writes bytes into file
    WriteFile(hDbfFile, &cFileEnd, sizeof(cFileEnd), &dwBytes, NULL);

    //*** Calculates file pointer to DbfHdr.wFirstRec
    ULONG nPos = (ULONG) &DbfHdr.wFirstRec - (ULONG) &DbfHdr.cType;  // = 8
    WORD wFirstRec = DbfHdr.wFirstRec;
    WORD wRecSize = DbfHdr.wRecSize;

    //*** Sets file pointer in DbfHdr.wRecSize position
    SetFilePointer(hDbfFile, nPos, NULL, FILE_BEGIN);

    //*** Writes NOT WRITTEN YET bytes into file
    WriteFile(hDbfFile, &wFirstRec, sizeof(wFirstRec), &dwBytes, NULL);

    //*** Writes next NOT WRITTEN YET bytes into file
    WriteFile(hDbfFile, &wRecSize, sizeof(wRecSize), &dwBytes, NULL);

    ::CloseHandle(hDbfFile);
    
    return TRUE;
}  // CreateDocumentFile

5. 虚拟模式下的数据处理

最后,我们将展示“负责”虚拟模式的LVN_GETDISPINFO处理程序的代码。

/////////////////////////////////////////////////////////////////////////////
// OnChildNotify
/////////////////////////////////////////////////////////////////////////////
BOOL CListCtrlEx::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT *pResult) {
    NMHDR *pNMHdr = reinterpret_cast<NMHDR *>(lParam);

    LV_DISPINFO *pLVDI = reinterpret_cast<LV_DISPINFO *>(lParam);
    LV_ITEM *pItem = &pLVDI->item;

    if(message == WM_NOTIFY) {
        switch(pNMHdr->code) {
            case LVN_GETDISPINFO: {
                if(pItem->mask & LVIF_TEXT) {
                    //*** Item row
                    UINT nRow = pItem->iItem;

                    //*** Item column
                    UINT nCol = pItem->iSubItem;

                    //*** The message buffer
                    TCHAR szStr[MAXITEMTEXT];

                    //*** Current document
                    m_pDoc = m_pMainApp->m_apDoc[m_eTable];

                    if(!m_pDoc) {
                        _M("CListCtrlEx::CListCtrlEx : Empty document!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Number of fields in dbf file
                    ULONG nColCount = m_pDoc->m_nFldCount;  // = m_MetaTable.nColCount;

                    if(nColCount == 0) {
                        _M("CListCtrlEx::OnChildNotify : Column count = 0!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    if(m_MetaTable.nColCount != nColCount) {
                        swprintf(
                                szStr, 
                                _T("CListCtrlEx::OnChildNotify : Table (%d) and Dbf (%d) columns are different!"), 
                                m_MetaTable.nColCount, 
                                nColCount
                        );

                        _M(szStr);
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Number of records in dbf file
                    ULONG nRowCount = m_pDoc->m_nRecCount;

                    if(nRowCount == 0) {
                        _M("CListCtrlEx::OnChildNotify : Row count = 0!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Sets row count into meta table
                    //if(m_MetaTable.nRowCount != nRowCount)
                    m_MetaTable.nRowCount = nRowCount;
                    
                    //*** Length of one data record (including delete flag)
                    ULONG nRowSize = m_pDoc->m_nRecSize;

                    if(nRowSize == 0) {
                        _M("CListCtrlEx::OnChildNotify : Row size = 0!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Line displacement of nCol-th field of nRow-th record
                    ULONG nLineInd = nRow*nRowSize + m_pDoc->m_anOff[nCol];

                    if(nLineInd < 0) {
                        _M("CListCtrlEx::OnChildNotify : Line index < 0!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Maps view of dbf file for its second part (where are data)
                    DBF_FILE2 *m_pDbfView2 = m_pDoc->m_pDbfView2;

                    if(!m_pDbfView2) {
                        _M("CListCtrlEx::OnChildNotify : Empty view of dbf file!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** The Visual FoxPro records
                    BYTE *aDbfRec = m_pDbfView2->aDbfRec;

                    if(!aDbfRec) {
                        _M("CListCtrlEx::OnChildNotify : Empty array of dbf file!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    if(!aDbfRec) {
                        _M("CListCtrlEx::OnChildNotify : Empty records into dbf file!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    //*** Line table cell index
                    UINT ji = nRow*nColCount + nCol;

                    if(ji < 0) {
                        _M("CListCtrlEx::OnChildNotify : Line cell index < 0!");
                        //*** Forces to exit from the application as else will be a lot messages
                        exit(-1);
                    }

                    if(ji < nRowCount*nColCount) {
                        try {
                            //*** Copies (j, i) field value of m_anLen[i]-th length
                            // We do this as it hasn't null terminator
                            CString sFldVal(
                                    (LPCSTR) &aDbfRec[nLineInd], 
                                    m_pDoc->m_acLen[nCol]
                            );

                            //*** Formates date string into German style
                            if(m_pDoc->m_acFldType[nCol] == 68) {  // = 0x44 ("D") - Date
                                CString sDate = 
                                        sFldVal.Right(2) + _T(".") + 
                                        sFldVal.Mid(4, 2) + _T(".") + 
                                        sFldVal.Left(4);
                                
                                sFldVal = sDate;
                            }

                            wcscpy((wchar_t *)(pItem->pszText), sFldVal);
                        } catch (...) {
                            //*** Forces to exit from the application as else will be a lot messages
                            exit(-1);
                        }
                    } else {
                        wcscpy((wchar_t *)(pItem->pszText), _T("***"));
                    }
                }

                break;
            }
        }
    }

    return CListCtrl::OnChildNotify(message, wParam, lParam, pResult);
}  // OnChildNotify
© . All rights reserved.