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






4.81/5 (9投票s)
一篇关于如何将信息存入和取出 OLE 结构化文档的文章。
引言
这只是另一个 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 的某个版本,但我记不太清楚了。感谢创建它的开发者。