The EX27A Example—Structured Storage

When you choose the Storage Write option in the EX27A example, the program walks through your entire disk directory looking for TXT files. As it looks, it writes a compound file (\direct.stg) on the top level of your directory structure. This file contains storages that match your subdirectories. For each TXT file that the program finds in a subdirectory, it copies the first line of text to a stream in the corresponding storage. When you choose the Storage Read option, the program reads the direct.stg compound file and prints the contents of this file in the Debug window.

If you create such an example from scratch, use AppWizard without any ActiveX or Automation options and then add the following lines in your StdAfx.h file:

#include <afxole.h>
#include <afxpriv.h> // for wide-character conversion

Then delete the following line:

#define VC_EXTRALEAN

To prepare EX27A, open the \vcpp32\ex27a\ex27a.dsw workspace and build the project. Run the program from the debugger. First choose Write from the Storage menu and wait for a "Write complete" message box. Then choose Read. Observe the output in the Debug window.

The Menu

The EX27A example has an added top-level Storage menu with Write and Read options.

The CEx27aView Class

This class maps the new Storage Read and Write menu commands listed above to start worker threads. The handlers are shown here:

void CEx27aView::OnStorageRead()
{
    CWinThread* pThread = AfxBeginThread(ReadThreadProc, GetSafeHwnd());
}

void CEx27aView::OnStorageWrite()
{
    CWinThread* pThread = AfxBeginThread(WriteThreadProc, GetSafeHwnd());
}

The Worker Threads

Figure 27-2 lists the code for the Storage Write and Storage Read worker threads.

THREAD.H

extern int g_nIndent;
extern const char* g_szBlanks;
extern const char* g_szRootStorageName;

UINT WriteThreadProc(LPVOID pParam);
UINT ReadThreadProc(LPVOID pParam);
void ReadDirectory(const char* szPath, LPSTORAGE pStg);
void ReadStorage(LPSTORAGE pStg);

WRITETHREAD.CPP

#include "StdAfx.h"
#include "Thread.h"

int g_nIndent = 0;
const char* g_szBlanks = "                                          ";
const char* g_szRootStorageName = "\\direct.stg";

UINT WriteThreadProc(LPVOID pParam)
{
    USES_CONVERSION;
    LPSTORAGE pStgRoot = NULL;
    g_nIndent = 0;
    VERIFY(::StgCreateDocfile(T2COLE(g_szRootStorageName),
           STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
           0, &pStgRoot) == S_OK);
    ReadDirectory("\\", pStgRoot);
    pStgRoot->Release();
    AfxMessageBox("Write complete");
    return 0;
}

void ReadDirectory(const char* szPath, LPSTORAGE pStg)
{
    // recursive function
    USES_CONVERSION;
    WIN32_FIND_DATA fData;
    HANDLE h;
    char szNewPath[MAX_PATH];
    char szStorageName[100];
    char szStreamName[100];
    char szData[81];
    char* pch = NULL;
    LPSTORAGE pSubStg = NULL;
    LPSTREAM pStream = NULL;

    g_nIndent++;
    strcpy(szNewPath, szPath);
    strcat(szNewPath, "*.*");
    h = ::FindFirstFile(szNewPath, &fData);
    if (h == (HANDLE) 0xFFFFFFFF) return;  // can't find directory
    do {
        if (!strcmp(fData.cFileName, "..") ||
            !strcmp(fData.cFileName, ".") ) continue;
        while((pch = strchr(fData.cFileName, `!')) != NULL) {
            *pch = `|';
        }
        if (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            // It's a directory, so make a storage
            strcpy(szNewPath, szPath);
            strcat(szNewPath, fData.cFileName);
            strcat(szNewPath, "\\");

            strcpy(szStorageName, fData.cFileName);
            szStorageName[31] = `\0';    // limit imposed by OLE
            TRACE("%0.*sStorage = %s\n", (g_nIndent - 1) * 4,
                  g_szBlanks, szStorageName);
            VERIFY(pStg->CreateStorage(T2COLE(szStorageName),
                   STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                   0, 0, &pSubStg) == S_OK);
            ASSERT(pSubStg != NULL);
            ReadDirectory(szNewPath, pSubStg);
            pSubStg->Release();
        }
        else {
            if ((pch = strrchr(fData.cFileName, `.')) != NULL) {
                if (!stricmp(pch, ".TXT")) {
                    // It's a text file, so make a stream
                    strcpy(szStreamName, fData.cFileName);
                    strcpy(szNewPath, szPath);
                    strcat(szNewPath, szStreamName);
                    szStreamName[32] = `\0'; // OLE max length
                    TRACE("%0.*sStream = %s\n", (g_nIndent - 1) * 4,
                          g_szBlanks, szNewPath);
                    CStdioFile file(szNewPath, CFile::modeRead);
                    // Ignore zero-length files
                    if(file.ReadString(szData, 80)) {
                        TRACE("%s\n", szData);
                       VERIFY(pStg->CreateStream(T2COLE(szStreamName),
                               STGM_CREATE | STGM_READWRITE | 
                               STGM_SHARE_EXCLUSIVE,
                               0, 0, &pStream) == S_OK);
                        ASSERT(pStream != NULL);
                        // Include the null terminator in the stream
                        pStream->Write(szData, strlen(szData) + 1, NULL);
                        pStream->Release();
                    }
                }
            }
        }
    } while (::FindNextFile(h, &fData));
    g_nIndent—;
}

READTHREAD.CPP

#include "StdAfx.h"
#include "Thread.h"

UINT ReadThreadProc(LPVOID pParam)
{
    USES_CONVERSION;
    LPSTORAGE pStgRoot = NULL;
    // doesn't work without STGM_SHARE_EXCLUSIVE
    g_nIndent = 0;
    if (::StgOpenStorage(T2COLE(g_szRootStorageName), NULL,
        STGM_READ | STGM_SHARE_EXCLUSIVE,
        NULL, 0, &pStgRoot) == S_OK) {
        ASSERT(pStgRoot!= NULL);
        ReadStorage(pStgRoot);
        pStgRoot->Release();
    }
    else {
        AfxMessageBox("Storage file not available or not readable.");
    }
    AfxMessageBox("Read complete");
    return 0;
}

void ReadStorage(LPSTORAGE pStg)
// reads one storage — recursive calls for substorages
{
    USES_CONVERSION;
    LPSTORAGE pSubStg = NULL;
    LPSTREAM pStream = NULL;
    LPENUMSTATSTG pEnum = NULL;
    LPMALLOC pMalloc = NULL; // for freeing statstg
    STATSTG statstg;
    ULONG nLength;
    BYTE buffer[101];

    g_nIndent++;
    ::CoGetMalloc(MEMCTX_TASK, &pMalloc); // assumes AfxOleInit
                                          //  was called
    VERIFY(pStg->EnumElements(0, NULL, 0, &pEnum) == S_OK);
    while (pEnum->Next(1, &statstg, NULL) == S_OK) {
        if (statstg.type == STGTY_STORAGE) {
            VERIFY(pStg->OpenStorage(statstg.pwcsName, NULL,
                   STGM_READ | STGM_SHARE_EXCLUSIVE,
                   NULL, 0, &pSubStg) == S_OK);
            ASSERT(pSubStg != NULL);
            TRACE("%0.*sStorage = %s\n", (g_nIndent - 1) * 4,
                  g_szBlanks, OLE2CT(statstg.pwcsName));
            ReadStorage(pSubStg);
            pSubStg->Release();
        }
        else if (statstg.type == STGTY_STREAM) {
            VERIFY(pStg->OpenStream(statstg.pwcsName, NULL,
                   STGM_READ | STGM_SHARE_EXCLUSIVE,
                   0, &pStream) == S_OK);
            ASSERT(pStream != NULL);
            TRACE("%0.*sStream = %s\n", (g_nIndent - 1) * 4,
                  g_szBlanks, OLE2CT(statstg.pwcsName));
            pStream->Read(buffer, 100, &nLength);
            buffer[nLength] = `\0';
            TRACE("%s\n", buffer);
            pStream->Release();
        }
        else {
            ASSERT(FALSE);  // LockBytes?
        }
        pMalloc->Free(statstg.pwcsName); // avoids memory leaks
    }
    pMalloc->Release();
    pEnum->Release();
    g_nIndent—;
}

Figure 27-2. The Storage menu worker threads.

To keep the program simple, there's no synchronization between the main thread and the two worker threads. You could run both threads at the same time if you used two separate compound files.

From your study of the Win32 threading model, you might expect that closing the main window would cause the read thread or write thread to terminate "midstream," possibly causing memory leaks. But this does not happen because MFC senses that the worker threads are using COM objects. Even though the window closes immediately, the program does not exit until all threads exit.

Both threads use recursive functions. The ReadStorage function reads a storage and calls itself to read the substorages. The ReadDirectory function reads a directory and calls itself to read the subdirectories. This function calls the Win32 functions FindFirstFile and FindNextFile to iterate through the elements in a directory. The dwFileAttributes member of the WIN32_FIND_DATA structure indicates whether the element is a file or a subdirectory. ReadDirectory uses the MFC CStdioFile class because the class is ideal for reading text.

The USES_CONVERSION macro is necessary to support the wide-character conversion macros OLE2CT and T2COLE. These macros are used here because the example doesn't use the CString class, which has built-in conversion logic.