学习 COM 组件设计与应用(一)起源及复合文件
这里拷贝主要部分
打开记事本程序,输入了一篇文章后,保存。——这样的文件叫“非结构化文件”;
打开电子表格程序,输入一个班的学生姓名和考试成绩,保存。——这样的文件叫“标准结构化文件”;
在我们写的程序中,需要把特定的数据按照一定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构化文件”;(比如 *.bmp 文件)
以上三种类型的文件,大家都见的多了。那么文件存储就依靠上述的方式能满足所有的应用需求吗?恩~~~,至少从计算机发明后的50多年来,一直是够用的了。嘿嘿,下面看看商业利益的推动作用,对文件 的存储形式产生了什么变化吧。30岁以上的朋友,我估计以前都使用过以下几个著名的软件:WordStar(独霸DOS下的英文编辑软件),WPS(裘伯君写的中文编辑软件,据说当年的市场占有率高达90%,各种计算机培训班的必修课程),LOTUS-123(莲花公司出品的电子表格软件)......
微软在成功地推出 Windows 3.1 后,开始垂涎桌面办公自动化软件领域。微软的 OFFICE 开发部门,各小组分别独立地开发了 WORD 和 EXCEL 等软件,并采用“自定义结构”方式,对文件进行存储。在激烈的市场竞争下,为了打败竞争对手,微软自然地产生了一个念头------如果我能在 WORD 程序中嵌入 EXCEL,那么用户在购买了我 WORD 软件的情况下,不就没有必要再买 LOTUS-123 了吗?!“恶毒”(中国微软的同志们看到了这个词,不要激动,我是加了引号的呀)的计划产生后,他们开始了实施工作,这就是 COM 的前身 OLE 的起源(注3)。但立刻就遇到了一个严重的技术问题:需要把 WORD 产生的 DOC 文件和 EXCEL 产生的 XLS 文件保存在一起。
方案 | 优点 | 缺点 |
建立一个子目录,把 DOC、XLS 存储在这同一个子目录中。 | 数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易扩展。 | 结构太松散,容易造成数据的损坏或丢失。 不易携带。 |
修改文件存储结构,在DOC结构基础上扩展出包容 XLS 的结构。 | 结构紧密,容易携带和统一管理。 | WORD 的开发人员需要通晓 EXCEL 的存储格式;缺少扩展性,总不能新加一个类型就扩展一下结构吧?! |
以上两个方案,都有严重的缺陷,怎么解决那?如果能有一个新方案,能够合并前两个方案的优点,消灭缺点,该多好呀......微软是作磁盘操作系统起家的,于是很自然地他们提出了一个非常完美的设计方案,那就是把磁盘文件的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。连微软当年都没有想到,就这么一个简单的想法,居然最后就演变出了 COM 组件程序设计的方法。可以说,复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:
![](https://i-blog.csdnimg.cn/blog_migrate/38f0b76bae18f4a587abe688555b3da1.png)
图一、左侧表示一个磁盘下的文件组织方式,右侧表示一个复合文件内部的数据组织方式。
三、复合文件的特点
- 复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做定位操作的时候,向后定位比向前定位要快;
- 复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!因为文件保存在磁盘上,即使一个字节也还要占用一个“簇”的空间那;
- 不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
- 大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力;
- 当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。
四、浏览复合文件
VC6.0 附带了一个工具软件“复合文件浏览器”,文件名是“vc目录/Common/Tools/DFView.exe”。为了方便使用该程序,可以把它加到工具(tools)菜单中。方法是:Tools/Customize.../Tools卡片中增加新的项目。运行 DFView.exe,就可以打开一个复合文件进行观察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到这个工具程序了,汗!不过这恰好提供给大家一个练习的机会,在你阅读完本篇文章并掌握了编程方法后,自己写一个“复合文件浏览编辑器”程序,又练手了,还有实用的价值。
五、复合文件函数
复合文件的函数和磁盘目录文件的操作非常类似。所有这些函数,被分为3种类型:WIN API 全局函数,存储 IStorage 接口函数,流 IStream 接口函数。什么是接口?什么是接口函数?以后的文章中再陆续介绍,这里大家只要把“接口”看成是完成一组相关操作功能的函数集合就可以了。
WIN API 函数 | 功能说明 |
StgCreateDocfile() | 建立一个复合文件,得到根存储对象 |
StgOpenStorage() | 打开一个复合文件,得到根存储对象 |
StgIsStorageFile() | 判断一个文件是否是复合文件 |
|
IStorage 函数 | 功能说明 |
CreateStorage() | 在当前存储中建立新存储,得到子存储对象 |
CreateStream() | 在当前存储中建立新流,得到流对象 |
OpenStorage() | 打开子存储,得到子存储对象 |
OpenStream() | 打开流,得到流对象 |
CopyTo() | 复制存储下的所有对象到目标存储中,该函数可以实现“整理文件,释放碎片空间”的功能 |
MoveElementTo() | 移动对象到目标存储中 |
DestoryElement() | 删除对象 |
RenameElement() | 重命名对象 |
EnumElements() | 枚举当前存储中所有的对象 |
SetElementTimes() | 修改对象的时间 |
SetClass() | 在当前存储中建立一个特殊的流对象,用来保存CLSID(注5) |
Stat() | 取得当前存储中的系统信息 |
Release() | 关闭存储对象 |
|
IStream 函数 | 功能说明 |
Read() | 从流中读取数据 |
Write() | 向流中写入数据 |
Seek() | 定位读写位置 |
SetSize() | 设置流尺寸。如果预先知道大小,那么先调用这个函数,可以提高性能 |
CopyTo() | 复制流数据到另一个流对象中 |
Stat() | 取得当前流中的系统信息 |
Clone() | 克隆一个流对象,方便程序中的不同模块操作同一个流对象 |
Release() | 关闭流对象 |
|
WIN API 补充函数 | 功能说明 |
WriteClassStg() | 写CLSID到存储中,同IStorage::SetClass() |
ReadClassStg() | 读出WriteClassStg()写入的CLSID,相当于简化调用IStorage::Stat() |
WriteClassStm() | 写CLSID到流的开始位置 |
ReadClassStm() | 读出WriteClassStm()写入的CLSID |
WriteFmtUserTypeStg() | 写入用户指定的剪贴板格式和名称到存储中 |
ReadFmtUserTypeStg() | 读出WriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是否是它需要的格式数据。 |
CreateStreamOnHGlobal() | 内存句柄 HGLOBAL 转换为流对象 |
GetHGlobalFromStream() | 取得CreateStreamOnHGlobal()调用中使用的内存句柄 |
为了让大家快速地浏览和掌握基本方法,上面所列表的函数并不是全部,我省略了“事务”函数和未实现函数部分。更全面的介绍,请阅读 MSDN。
下面程序片段,演示了一些基本函数功能和调用方法。
示例一:建立一个复合文件,并在其下建立一个子存储,在该子存储中再建立一个流,写入数据。
注意在此例在Vc6中创建一个控制台程序,并加入头文件
#include <ole2.h>
#include <assert.h>
vc下的实际代码
include
"
stdafx.h
"
#include
<
ole2.h
>
#include
<
assert.h
>
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleCreateDoc();
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
int
main(
int
argc,
char
*
argv[])
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
SampleCreateDoc();
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleCreateDoc()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
::CoInitialize(NULL);
HRESULT hr;
IStorage *pStg = NULL;
IStorage *pSub = NULL;
IStream *pStm = NULL;
//
hr = ::StgCreateDocfile(
L"c:/a1.stg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,
&pStg);
assert(SUCCEEDED(hr));
hr = pStg->CreateStorage(
L"SubStg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub);
assert(SUCCEEDED(hr));
hr = pSub->CreateStream(
L"Stm",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm);
assert(SUCCEEDED(hr));
hr = pStm->Write("Hello",5,NULL);
assert(SUCCEEDED(hr));
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (pStm) pStm->Release();
if (pSub) pSub->Release();
if (pStg) pStg->Release();
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
::CoUninitialize();
}
解说代码
void SampleCreateDoc()
{
::CoInitialize(NULL); // COM 初始化
// 如果是MFC程序,可以使用AfxOleInit()替代
HRESULT hr; // 函数执行返回值
IStorage *pStg = NULL; // 根存储接口指针
IStorage *pSub = NULL; // 子存储接口指针
IStream *pStm = NULL; // 流接口指针
hr = ::StgCreateDocfile( // 建立复合文件
L"c://a.stg", // 文件名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打开方式
0, // 保留参数
&pStg); // 取得根存储接口指针
ASSERT( SUCCEEDED(hr) ); // 为了突出重点,简化程序结构,所以使用了断言。
// 在实际的程序中则要使用条件判断和异常处理
hr = pStg->CreateStorage( // 建立子存储
L"SubStg", // 子存储名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub); // 取得子存储接口指针
ASSERT( SUCCEEDED(hr) );
hr = pSub->CreateStream( // 建立流
L"Stm", // 流名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm); // 取得流接口指针
ASSERT( SUCCEEDED(hr) );
hr = pStm->Write( // 向流中写入数据
"Hello", // 数据地址
5, // 字节长度(注意,没有写入字符串结尾的/0)
NULL); // 不需要得到实际写入的字节长度
ASSERT( SUCCEEDED(hr) );
if( pStm ) pStm->Release();// 释放流指针
if( pSub ) pSub->Release();// 释放子存储指针
if( pStg ) pStg->Release();// 释放根存储指针
::CoUninitialize() // COM 释放
// 如果使用 AfxOleInit(),则不调用该函数
}
![](https://i-blog.csdnimg.cn/blog_migrate/27ba44336006c9af6f939aad8e3f2d99.png)
图二、运行示例程序一后,使用 DFView.exe 打开观察复合文件的效果图
此外也可以创建一个Win32 Application程序,下面贴出代码
//
doc.cpp : Defines the entry point for the application.
//
#include
"
stdafx.h
"
#include
"
resource.h
"
#include
<
assert.h
>
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
#define
MAX_LOADSTRING 100
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
#define _WIN32_WINNT 0x0500
#include
<
ole2.h
>
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
Global Variables:
HINSTANCE hInst;
//
current instance
TCHAR szTitle[MAX_LOADSTRING];
//
The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];
//
The title bar text
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
Foward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE,
int
);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void
SampleCreateDoc();
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
int
APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int
nCmdShow)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_DOC, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
return FALSE;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_DOC);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
return msg.wParam;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
//
FUNCTION: MyRegisterClass()
//
//
PURPOSE: Registers the window class.
//
//
COMMENTS:
//
//
This function and its usage is only necessary if you want this code
//
to be compatible with Win32 systems prior to the 'RegisterClassEx'
//
function that was added to Windows 95. It is important to call this function
//
so that the application will get 'well formed' small icons associated
//
with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
WNDCLASSEX wcex;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
wcex.cbSize = sizeof(WNDCLASSEX);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_DOC);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDC_DOC;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
return RegisterClassEx(&wcex);
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
//
FUNCTION: InitInstance(HANDLE, int)
//
//
PURPOSE: Saves instance handle and creates main window
//
//
COMMENTS:
//
//
In this function, we save the instance handle in a global variable and
//
create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance,
int
nCmdShow)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
HWND hWnd;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
hInst = hInstance; // Store instance handle in our global variable
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (!hWnd)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
return FALSE;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
return TRUE;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
//
FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//
PURPOSE: Processes messages for the main window.
//
//
WM_COMMAND - process the application menu
//
WM_PAINT - Paint the main window
//
WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
switch (message)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case ID_MNU_TEST:
SampleCreateDoc();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
//
Mesage handler for about box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
switch (message)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
case WM_INITDIALOG:
return TRUE;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleCreateDoc()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
::CoInitialize(NULL);
HRESULT hr;
IStorage *pStg = NULL;
IStorage *pSub = NULL;
IStream *pStm = NULL;
//
hr = ::StgCreateDocfile(
L"c:/a.stg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,
&pStg);
assert(SUCCEEDED(hr));
hr = pStg->CreateStorage(
L"SubStg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub);
assert(SUCCEEDED(hr));
hr = pSub->CreateStream(
L"Stm",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm);
assert(SUCCEEDED(hr));
hr = pStm->Write("Hello",5,NULL);
assert(SUCCEEDED(hr));
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (pStm) pStm->Release();
if (pSub) pSub->Release();
if (pStg) pStg->Release();
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
::CoUninitialize();
}
示例二:打开一个复合文件,枚举其根存储下的所有对象。
在vc中的代码,注意头文件中加入atlbase.h, atlconv.h
#include
"
stdafx.h
"
#include
"
ole2.h
"
#include
"
atlbase.h
"
#include
"
atlconv.h
"
#include
<
assert.h
>
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleCreateDoc();
void
SampleEnum();
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
int
main(
int
argc,
char
*
argv[])
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
SampleCreateDoc();
SampleEnum();
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleCreateDoc()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
::CoInitialize(NULL);
HRESULT hr;
IStorage *pStg = NULL;
IStorage *pSub = NULL;
IStream *pStm = NULL;
//
hr = ::StgCreateDocfile(
L"c:/a1.stg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,
&pStg);
assert(SUCCEEDED(hr));
hr = pStg->CreateStorage(
L"SubStg",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub);
assert(SUCCEEDED(hr));
hr = pSub->CreateStream(
L"Stm",
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm);
assert(SUCCEEDED(hr));
hr = pStm->Write("Hello",5,NULL);
assert(SUCCEEDED(hr));
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (pStm) pStm->Release();
if (pSub) pSub->Release();
if (pStg) pStg->Release();
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
::CoUninitialize();
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
void
SampleEnum()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
LPCTSTR lpFileName = _T("c:/a.stg");
HRESULT hr;
IStorage *pStg = NULL;
//
USES_CONVERSION;
LPCOLESTR lpwFileName = T2COLE(lpFileName);
hr = StgIsStorageFile(lpwFileName);
if (FAILED(hr)) return;
hr = ::StgOpenStorage(
lpwFileName,
NULL,
STGM_READ | STGM_SHARE_DENY_WRITE,
0,
0,
&pStg);
IEnumSTATSTG *pEnum = NULL;
hr = pStg->EnumElements(0,NULL,0,&pEnum);
assert(SUCCEEDED(hr));
STATSTG statstg;
while (NOERROR == pEnum->Next(1, &statstg, NULL))
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
printf("statstg.type = %d ",statstg.type);
::CoTaskMemFree( statstg.pwcsName );
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
#include <atlconv.h> // ANSI、MBCS、UNICODE 转换
void SampleEnum()
{ // 假设你已经做过 COM 初始化了
LPCTSTR lpFileName = _T( "c://a.stg" );
HRESULT hr;
IStorage *pStg = NULL;
USES_CONVERSION; // (注6)
LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 转换T类型为宽字符
hr = ::StgIsStorageFile( lpwFileName ); // 是复合文件吗?
if( FAILED(hr) ) return;
hr = ::StgOpenStorage( // 打开复合文件
lpwFileName, // 文件名称
NULL,
STGM_READ | STGM_SHARE_DENY_WRITE,
0,
0,
&pStg); // 得到根存储接口指针
IEnumSTATSTG *pEnum=NULL; // 枚举器
hr = pStg->EnumElements( 0, NULL, 0, &pEnum );
ASSERT( SUCCEEDED(hr) );
STATSTG statstg;
while( NOERROR == pEnum->Next( 1, &statstg, NULL) )
{
// statstg.type 保存着对象类型 STGTY_STREAM 或 STGTY_STORAGE
// statstg.pwcsName 保存着对象名称
// ...... 还有时间,长度等很多信息。请查看 MSDN
::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存(注6)
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
六、小结
复合文件,结构化存储,是微软组件思想的起源,在此基础上继续发展出了持续性、命名、ActiveX、对象嵌入、现场激活......一系列的新技术、新概念。因此理解
和
掌握 复合文件是非常重要的,即使在你的程序中并没有全面使用组件技术,复合文件技术也是可以单独被应用的。祝大家学习快乐,为社会主义软件事业而奋斗:-)
留作业啦......
作业1:写个小应用程序,从 MSWORD 的 doc 文件中,提取出附加信息(作者、公司......)。
作业2:写个全功能的“复合文件浏览编辑器”。
注1:踅摸(xuemo),动词,北方方言,寻找搜索的意思。
注2:问:为什么不上网查资料学习?
答:开什么国际玩笑!在那遥远的1995年代,我的500块工资,不吃不喝正好够上100小时的Internet网。
注3:OLE,对象的连接与嵌入。
注4:可以用 DFView.exe 打开 MSWORD 的 DOC 文件进行复合文件的浏览。但是该程序并没有实现国际化,不能打开中文文件名的复合文件,因此需要改名后才能浏览。
注5:CLSID,在后续的文章中介绍。
注6:关于 COM 中内存使用的问题,在后续的文章中介绍。