最近由于项目的需要 涉及到了 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 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:
三、复合文件的特点
五、复合文件函数
复合文件的函数和磁盘目录文件的操作非常类似。所有这些函数,被分为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;
}