最近由于项目的需要 涉及到了 COM技术,所以就进行了以下学习 ,看关于COM方面的书籍不是很多,于是我便从网上学习,本来对COM感觉很神秘,等当真正接触的时候发现也不是那么难,以上扯淡中......兴趣就是动力嘛,本人比较懒,文中部分内容直接COPY原文 作者体谅。。
还有COM中都是以接口的形式提供的,Java中大家都学过接口 ,还有C++虚函数中大家都学过 其实都差不多 ,只是COM有自己的内存分配方式而已 。
简单地说,COM是一种跨应用和语言共享二进制代码的方法。与C++不同,它提倡源代码重用。ATL便是一个很好的例证。源码级重用虽然好,但只能用于C++。它还带来了名字冲突的可能性,更不用说不断拷贝重用代码而导致工程膨胀和臃肿。
Windows使用DLLs在二进制级共享代码。这也是Windows程序运行的关键——重用kernel32.dll, user32.dll等。但DLLs是针对C接口而写的,它们只能被C或理解C调用规范的语言使用。由编程语言来负责实现共享代码,而不是由DLLs本身。这样的话DLLs的使用受到限制。
MFC引入了另外一种MFC扩展DLLs二进制共享机制。但它的使用仍受限制——只能在MFC程序中使用。
COM通过定义二进制标准解决了这些问题,即COM明确指出二进制模块(DLLs和EXEs)必须被编译成与指定的结构匹配。这个标准也确切规定了在内存中如何组织COM对象。COM定义的二进制标准还必须独立于任何编程语言(如C++中的命名修饰)。一旦满足了这些条件,就可以轻松地从任何编程语言中存取这些模块。由编译器负责所产生的二进制代码与标准兼容。这样使后来的人就能更容易地使用这些二进制代码。
在内存中,COM对象的这种标准形式在C++虚函数中偶尔用到,所以这就是为什么许多COM代码使用C++的原因。但是记住,编写模块所用的语言是无关的,因为结果二进制代码为所有语言可用。
此外,COM不是Win32特有的。从理论上讲,它可以被移植到Unix或其它操作系统。但是我好像还从来没有在Windows以外的地方听说过COM。
据说COM最初的起源的微软的复合文档,关于什么是复合文档呢? 大家都用过WORD,知道这个文件可以包含图片 图标等等资源信息 ,很方便使用。但是对于开发的微软来说,
COM最初只是为了 在word中嵌入 EXCEL等。那么现在出现了一个问题? 如何在word中引入Excel呢 ?
下面是两种方法 :(此处从作者那里拷贝的)
方案 |
优点 |
缺点 |
建立一个子目录,把 DOC、XLS 存储在这同一个子目录中。 | 数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易扩展。 | 结构太松散,容易造成数据的损坏或丢失。 不易携带。 |
修改文件存储结构,在DOC结构基础上扩展出包容 XLS 的结构。 | 结构紧密,容易携带和统一管理。 | WORD 的开发人员需要通晓 EXCEL 的存储格式;缺少扩展性,总不能新加一个类型就扩展一下结构吧?! |
以上两个方案,都有严重的缺陷,怎么解决那?如果能有一个新方案,能够合并前两个方案的优点,消灭缺点,该多好呀......微软是作磁盘操作系统起家的,于是很自然地他们提出了一个非常完美的设计方案,那就是把磁盘文件的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。连微软当年都没有想到,就这么一个简单的想法,居然最后就演变出了 COM 组件程序设计的方法。可以说,复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:
三、复合文件的特点
- 复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做定位操作的时候,向后定位比向前定位要快;
- 复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!因为文件保存在磁盘上,即使一个字节也还要占用一个“簇”的空间那;
- 不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
- 大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力;
- 当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。
五、复合文件函数
复合文件的函数和磁盘目录文件的操作非常类似。所有这些函数,被分为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()调用中使用的内存句柄 |
//利用IStream 和IStroage接口来读写复合文件
// C++COMTEST.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "windows.h"
#include <objbase.h> //Component Object Module的声明
#include <afxcom_.h> //Microsoft Foundation Classes 的有关com的头文件 ASSERT就定义在这里 用于测试程序的正确性
class ComTest
{
public:
void CreateCompoundFile(TCHAR *path,TCHAR *streamName)
{
HRESULT hr ;//返回一个long的结果
IStorage * strageRoot ;//为复合文档定义根存储
IStorage * strageSubRoot;//为跟存储定义一个子存储
IStream * streamSubRoot;//为子存储定义一个流
::CoInitialize(NULL) ;//初始换Com Library
hr=::StgCreateDocfile(path,STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE,0,&strageRoot) ; //第一次创建一个复合文档 这个存储对象 是一个根对象
ASSERT(SUCCEEDED(hr)) ; //测试结果是否正确
hr=strageRoot->CreateStorage(L"SubTag",STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,0,0,&strageSubRoot) ;//在跟存储的基础上创建子存储
ASSERT(SUCCEEDED(hr)) ; //测试结果是否正确
strageSubRoot->CreateStream(streamName,STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE,0,0,&streamSubRoot) ;//在子存储的下面创建流对象
hr=streamSubRoot->Write("hello",5,NULL); //写入到流指针
ASSERT(SUCCEEDED(hr)) ;
//因为COM是独立于语言之外的 不能用C++的delete 在堆上分配 或者 在栈中分配 COM有自己的内存分配方式 这意味着 我们在创建完COM对象的时候 用完就需要自己手动删除并释放COM Library
strageRoot->Release() ;//释放内存
streamSubRoot->Release() ;//必须释放
strageSubRoot->Release() ;//释放子存储内存
CoUninitialize();//释放COM库
}
BOOL CheckIfProfoundFile(TCHAR* path) //在COM中通常需要宽字节
{
HRESULT r=StgIsStorageFile(path);
switch(r)
{
case STG_E_FILENOTFOUND:
printf("文件没有发现");
break;
case S_FALSE:
printf("文件不是复合文件");
break;
case S_OK:
printf("文件是复合文件");
break ;
}
}
} ;
int _tmain(int argc, _TCHAR* argv[])
{
ComTest test ;
TCHAR path[]=L"c:\\1.doc";
TCHAR name[]=L"stream" ;
test.CreateCompoundFile(path,name) ;
return 0;
}