| Tenouk C & C++ | MFC Home | Structured Storage 1 | Structured Storage, DLL & COM 3 | Download | Site Index |


 

 

 

 

 

Structured Storage 2

 

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are statstg, Thread.h, ReadThread.cpp and WriteThread.cpp.

  1. The MYMFC31A Example: Structured Storage

  2. The MYMFC31A From Scratch

  3. The Menu

  4. The CMymfc31aView Class

  5. The Worker Threads

 

The MYMFC31A Project Example: Structured Storage

 

When you choose the Storage Write option in the MYMFC31A 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 MYMFC31A, open the mymfc31a.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 MYMFC31A From Scratch

 

This is SDI application without ActiveX or Automation support.

 

Figure 2: MYMFC31A – Visual C++ new project dialog.

 

Figure 2: MYMFC31A – Visual C++ new project dialog.

 

Figure 3: MYMFC31A – AppWizard step 1 of 6, an SDI application.

 

Figure 3: MYMFC31A – AppWizard step 1 of 6, an SDI application.

 

Figure 4: MYMFC31A – AppWizard step 2 of 6.

 

Figure 4: MYMFC31A – AppWizard step 2 of 6.

 

Figure 5: MYMFC31A – AppWizard step 3 of 6, deselect Automation and ActiveX Controls support options.

 

Figure 5: MYMFC31A – AppWizard step 3 of 6, deselect Automation and ActiveX Controls support options.

 

Figure 6: MYMFC31A – AppWizard step 4 of 6

 

Figure 6: MYMFC31A – AppWizard step 4 of 6

 

Figure 7: MYMFC31A – AppWizard step 5 of 6

 

Figure 7: MYMFC31A – AppWizard step 5 of 6

 

Figure 8: MYMFC31A – AppWizard step 6 of 6

 

Figure 8: MYMFC31A – AppWizard step 6 of 6

 

Figure 9: MYMFC31A project summary.

 

Figure 9: MYMFC31A project summary.

 

Add the following menu items.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ID

Caption

-

Storage

ID_STORAGE_READ

Read

ID_STORAGE_WRITE

Write

 

Table 4.

 

Figure 10: Adding Storage menu and Write sub menu.

 

Figure 10: Adding Storage menu and Write sub menu.

 

Figure 11: Adding Read sub menu.

 

Figure 11: Adding Read sub menu.

 

Figure 12: A completed MYMFC31A menus.

 

Figure 12: A completed MYMFC31A menus.

 

Use ClassWizard to add command handlers as listed in the following Table for the menu items in CMymfc31aView class.

 

ID

Function

ID_STORAGE_READ

OnStorageRead()

ID_STORAGE_WRITE

OnStorageWrite()

 

Table 5.

 

Figure 13: Adding command handlers for menu items.

 

Figure 13: Adding command handlers for menu items.

 

Then, add codes to the command handlers.

 

void CMymfc31aView::OnStorageRead()

{

      // TODO: Add your command handler code here

      CWinThread* pThread = AfxBeginThread(ReadThreadProc, GetSafeHwnd());

}

 

void CMymfc31aView::OnStorageWrite()

{

      // TODO: Add your command handler code here

      CWinThread* pThread = AfxBeginThread(WriteThreadProc, GetSafeHwnd());

}

 

MFC C++ code snippet - Compound document/structured storage

 

Listing 1.

 

Add the #include directive to mymfc31aView.cpp.

 

#include "Thread.h"

 

MFC C++ code snippet - Compound document/structured storage

 

Listing 2.

 

Add files to the project, copy and paste the codes (the codes are provided in the following Listings). Add Thread.h file.

 

Figure 14: Adding new files for new class to the project.

 

Figure 14: Adding new files for new class to the project.

 

Figure 15: Adding Thread.h header file to project.

 

Figure 15: Adding Thread.h header file to project.

 

Repeat the same step for WriteThread.cpp and ReadThread.cpp source files (of course you have to select the C++ Source File in the previous Figure), copy and paste the codes to those files.

 

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

 

MFC C++ code snippet - Compound document/structured storage

 

Listing 3.

 

Build and run in debug mode. Click the Write menu.

 

 

Figure 16: Running MYMFC31A in debug mode.

 

Figure 16: Running MYMFC31A in debug mode.

 

Select the Write menu.

 

Figure 17: MYMFC31A output.

 

Figure 17: MYMFC31A output.

 

The message box displayed when Write menu is selected as shown below.

 

Figure 18: Message box displayed when Write menu was selected.

 

Figure 18: Message box displayed when Write menu was selected.

 

Then, click the Read menu.

 

Figure 19: Testing the Read menu.

 

Figure 19: Testing the Read menu.

 

Figure 20: Message box displayed when Read menu was selected.

 

Figure 20: Message box displayed when Read menu was selected.

 

Check the Debug window. It will look something like this (the storage and stream should be different with yours).

 

Storage = mfcjadik

    Storage = final

        Stream = \mfcjadik\final\log.txt

 

    Storage = myscribble

        Storage = Debug

        Stream = \mfcjadik\myscribble\ReadMe.txt

========================================================================

...

[trimmed]

...

========================================================================

 

    Storage = mymfc27B

        Storage = res

        Storage = Debug

        Stream = ReadMe.txt

========================================================================

 

The thread 0x398 has exited with code 0 (0x0).

The thread 0xD04 has exited with code 0 (0x0).

The program 'F:\mfcproject\mymfc31a\Debug\mymfc31a.exe' has exited with code 0 (0x0).

 

Verify the direct.stg file creation.

 

Figure 21: Verifying the direct.stg file creation by the write operation in the previous step.

 

Figure 21: Verifying the direct.stg file creation by the write operation in the previous step.

 

The Menu

 

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

 

The CMymfc31aView Class

 

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

 

void CMymfc31aView::OnStorageRead()

{

    CWinThread* pThread = AfxBeginThread(ReadThreadProc, GetSafeHwnd());

}

 

void CMymfc31aView::OnStorageWrite()

{

    CWinThread* pThread = AfxBeginThread(WriteThreadProc, GetSafeHwnd());

}

 

The Worker Threads

 

Listing 4 lists the code for the Storage Write and Storage Read worker threads.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

THREAD.H

 

// Thread.h (MYMFC31A)

 

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

// WriteThread.cpp (MYMFC31A)

 

#include "StdAfx.h"

#include "Thread.h"

 

int g_nIndent = 0;

const char* g_szBlanks = "                 ";

// the file for storage...

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);

    // you can start at any root...or directory...

    // if just "\\", current root will be used...

    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[10000];

    char szStreamName[10000];

    char szData[10001];

    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, 10000))

            {

              TRACE("%s\n", szData);

                       // For a very 'big' file number the Write() will trigger the

                       // Exception - Access violation, just click the ignore button...

                       // To avoid the Exception, memory allocation must be properly

                       // done...T2COLE do the string conversion...hohoho...

                       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();

 

                  // ---This is another snippet version----

                   // if(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

 

// ReadThread.cpp (MYMFC31A)

 

#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--;

}

 

 

Listing 4: 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.

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | Structured Storage 1 | Structured Storage, DLL & COM 3 | Download | Site Index |