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

另一个 OLE 文档查看器,但带有编辑功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (9投票s)

2004 年 6 月 18 日

CPOL

4分钟阅读

viewsIcon

94387

downloadIcon

2700

一篇关于如何将信息存入和取出 OLE 结构化文档的文章。

Sample Image - Another_OLE_Doc_Viewer.jpg

引言

这只是另一个 OLE 文档/结构化文档查看器。为了稍微改进一下,我添加了删除和插入功能。我开发它是因为它需要用于我们的内部办公使用。我们一直在处理结构化存储文件,并需要一个实用工具来不仅查看文件内所有存储和流的详细信息,而且还能编辑文件。所以,这是最终产品。

背景

要完全理解这篇文章,您必须熟悉结构化存储。为了向那些不了解它的人介绍一下,结构化存储文件通常被认为在文件内部有一个文件系统。就像普通文件系统中有文件夹和文件一样,在结构化存储文件中,我们有存储和流。一个存储可以包含流和存储,而流则像一个简单的二进制文件。

这是一个小的独立可执行文件。现在,有些人会问微软自己提供了 OLE Doc Viewer,那么这个有什么用呢?我想我之前已经回答了。我添加了我们办公室项目所需的编辑功能,这也是开发它的唯一原因。另外,我认为这是应该分享的东西。所以,如果有人因为重复工作而感到恼火,请接受我的道歉,尽管我认为这种情况不会发生。

代码

由于将文件系统表示为树形结构通常被认为是最佳的,因此这里也是这样做的。我使用树来显示存储文件内的完整文件系统(所谓的)。通过双击或从右键菜单中选择“显示内容”,您可以在右侧窗格中看到所选流的值。

好了,来说说代码。我将文件内的所有存储都存储在一个链表中。有一个名为 StgPointers 的结构。它用于双向链表,我使用双向链表只是为了方便链表内的移动。我只在链表中存储了存储,而没有存储流。这样做只是因为如果我们有一个存储指针,那么打开该特定存储内的流并不困难。

现在,使用链表来实现树结构给我带来了一些问题,尤其是在删除时,因为从逻辑上讲,树应该存储在树中,但那又很难处理。所以,我选择了第一个困难的选项,因为我认为我擅长那个 :-)。我知道自吹自擂会导致孤立,但有时……

无论如何,这里是一些重要的代码片段

首先,用于链表的结构

typedef struct StgPointers
{
    IStorage * pointer;
    IStorage * parent;
    CString stgName;
    StgPointers * next;
    StgPointers * prev;
    HTREEITEM hTree;
} Pointers;

这是用于在磁盘上打开存储文件的 API。

HRESULT hRes = StgOpenStorage(wcFName, NULL, m_dwMode, NULL, 0, &m_pRootStg);

现在,这是用于填充树的递归函数。EnumElements 返回一个 IEnumSTATSTG 类型的指针,其中包含存储中的枚举元素。通过调用它的 next 函数,我们在 STATSTG 类型的数组中获取其名称。然后将其转换为 CString 并添加到树中。

bool COLEDOCViewerDlg::PopulateTree(IStorage * pCurrentStorage, 
                                                   HTREEITEM htreeItem)
{
    HTREEITEM hNewItem;
    if(!m_bRootInserted)
    {
        htreeItem =  m_treeData.InsertItem(m_strFileName, 3 ,
        2);m_first->hTree = htreeItem;
        m_cur = m_prev = m_first;
        m_bRootInserted = true;
    }
    USES_CONVERSION;
    IEnumSTATSTG * ppenum = NULL;
    HRESULT hRes = pCurrentStorage->EnumElements(0,NULL, 0, &ppenum);
    CString addtoList;
    STATSTG arr[1];
    
    while(hRes != S_FALSE)
    {
        hRes = ppenum->Next(1, arr, NULL);
        addtoList = W2A(arr[0].pwcsName);
        if(arr[0].type == 1)
        {
            hNewItem = m_treeData.InsertItem(addtoList, 3, 2, htreeItem, NULL);
            WCHAR * stgName = A2W(addtoList);
            IStorage * pNewStorage = NULL;
            hRes = pCurrentStorage->OpenStorage(stgName, 
                   NULL, m_dwMode, NULL, 0, &pNewStorage);
            //Adding in Linked List
            m_prev = m_cur;
            m_cur =  new
            Pointers;m_cur->prev = 
            m_prev;m_cur->parent = 
            pCurrentStorage;m_cur->pointer = 
            pNewStorage;m_cur->stgName = 
            addtoList;m_cur->hTree = 
            hNewItem;m_cur->next = 
            NULL;m_prev->next = m_cur;
            //Added

            PopulateTree(pNewStorage, hNewItem);
            
        }
        if(arr[0].type == 2)
        {
            addtoList += "  <STREAM>";
            m_treeData.InsertItem(addtoList, htreeItem);
        }
        
    }
    ppenum->Release();
    return true;
}

Stream 的内容是这样获取的。这里,pStream 是我们要读取其值的 Stream 的指针。

            pStream->Read(readData, noofbytes, NULL);

DeleteItem 的逻辑有点复杂,因为正如我之前提到的,我将树存储在链表中,这给我带来了很大的麻烦。此外,插入 Stream/Storage 并不困难。从代码中可以清楚地看出,所以我认为我不必在此处进一步解释。

一件事很重要,那就是所有对象打开的模式。这些由一个变量 m_dwMode 处理,其值设置为

m_dwMode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT

STGM_READWRITE 表示它在两种模式下都打开,STGM_SHARE_EXCLUSIVE 表示在打开时其他人无法打开它,而 STGM_DIRECT 表示我们无需调用 Commit 来确保更改。

其余部分很简单。

我的感谢

我使用了一个名为 SnapLine 的函数,该函数来自外部源。这可能是 Borland C 的某个版本,但我记不太清楚了。感谢创建它的开发者。

© . All rights reserved.