第四课
基于 COM技术的程序设计方法
COM技术简介
? COM (Component Object Model,组件对
象模型 ),是一种允许对象之间跨进程、跨
计算机进行交互 (Interact)的技术,并且使
得这种交互容易得好象在本地计算机同一
进程中进行一样。
COM技术简介
? COM不是一种面向对象的语言,而是一
种二进制标准,它定义了组件对象之间
基于这些技术标准进行交互的方法。
COM技术简介
? COM所建立的是一个软件模块与另一个
软件模块之间的链接,当这种链接建立
之后,模块之间就可以通过称之为, 对
象接口, (Interface on Object)的机制来
进行通信。
COM技术简介
? 为什么需要 COM技术
? 在程序 设计方法 上,编写代码的方式由最早的面向
过程,到现在的面向对象;相 应 的,代码重用的方
式也由从最早的源代码级重用,到库( LIB) 和动
态链接库( DLL) 等二进制(机器码)等级的重用。
但是,这时的代码重用方式却不能与编程技术相协
调。面相对象的编程技术要求能够重用一个对象,
而不仅仅是一个函数。这个要求在源代码级可以很
容易的实现,但 那时 的 二进制 代码 级 重用 无法很好
的做到这点。
COM技术简介
? COM的产生
? 为了协调代码重用方式和编程技术的协调,
COM应运而生。 COM使得不同的语言, 不
同的编译器产生的对象能够互操作。
? COM是 一个可执行代码级的标准,由它来规
定,一个编译器应该以何种二进制格式来组
织, 导出它的对象,以及如何在其他编译器
导出的对象的二进制数据中进行定位。
COM技术简介
? 早期的 COM
? 早期的 COM技术不具备跨计算机的远程调
用( RPC) 能力,这种通过通用接口操纵其
他对象的功能仅仅局限于同一计算机的不同
应用程序之间( LPC)。 例如,Microsoft
Visual Basic可以通过 COM通信机制控制和
操纵同一计算机中安装的 Microsoft Excel的
一个拷贝,但不能直接执行其他计算机上的
Excel。
COM技术简介
? COM的发展现状
? COM已经得到广泛应用,并逐渐成为业界事
实上的标准 。 目前,全球范围内至少有
150,000,000个运行系统应用了 COM技术。
与此同时,COM还带来巨大市场。
本章主要研究内容
? COM技术介绍及程序设计方法
? DCOM和 COM+技术介绍
COM基础 —— 三个概念
? COM组件
? 可独立发布的二进制组件
? 在 Windows平台上为 DLL或者 EXE
? COM对象
? 通过 COM接口提供服务
? 符合 OO中对象的基本概念
? COM接口
? 客户与对象之间的协议,对象实现 COM
接口,客户使用 COM接口
COM基础, 组件
组件是 COM对象的载体,包含多个 COM对象。
组件由:
进程内组件( 以 DLL形式存在,使用 LPC )
进程外组件 ( 以 EXE形式存在,使用 RPC )
COM基础, 对象
COM对象与 OOP中的 OBJECT一致。由 CLSID
( 128位整数唯一标识),实现多个接口。 在
windows系统中所有 COM对象的 CLSID值都保存在注
册表的一个分支下面( HKEY_CLASS_ROOOT\CLSID)。
COM对象特性:
身份确定
封装
可重用
Ob ject
IUn k n o w n
I n terf ac e1
I n terf ac e2
COM基础, 接口
COM接口由 IID,128位整数标识
采用 COM IDL接口定义语言定义接口
单继承
COM接口特性:
接口不变性,COM的接口一旦确定,将不
再改变,只能增加接口
继承性:与 OBJECT一样
多态性 —— 主要是运行时刻的多态性
设计 COM接口 —— 从 C++入手
? C++类:接口与实现的分离
? 接口:类的 public部分
class CMyString//字符串类
{
private:
char *m_psz;
public:
CMyString(const char * psz);
~CMyString();
const char*Find(const char *psz);//寻找字符,接口函数
int Length();//得到字符串长度,接口函数
};
设计 COM接口 —— 从 C++入手 (续 )
? C++类的实现
CMyString::CMyString(const char * psz)
,m_psz( new char[psz? strlen(psz)+1,1]) {
if ( psz )
strcpy(m_psz,psz);
else
m_psz[0] = 0;
}//构造函数
CMyString::~CMyString() {
delete [] m_psz;
}//析构函数
const char*CMyString::Find(const char *psz) {
return strstr(m_psz,psz);
} //返回第一个与 psz匹配的字符串指针
int CMyString::Length() {
return strlen(m_psz);
}//返回字符串长度
C++类的链接 linking
? 在完成 CMyString类的编写后,用户可采
用两种编译连接方式:
? 静态链接
? 许多类库的做法
? 在编译时刻链接
? 静态链接的缺点
? 代码重复:多个程序各有自己的代码,需要更
多的内存
? 客户程序占据更多的外存空间
? 库代码更新需要重新编译所有的客户程序
C++类的链接 linking(续 )
? 动态链接
? 在运行时链接
? 动态链接形式
? 编译时刻通过引入库
? 运行时刻完全动态
? 存在 Dll Hell风险
#ifdef MYSTRINGDLL
#define EXPORTORIMPORT _declspec(dllexport)
#else
#define EXPORTORIMPORT _declspec(dllimport)
#endif
class EXPORTORIMPORT CMyString
{//导出或导进
private:
char *m_psz;
public:
CMyString(const char * psz);
~CMyString();
const char*Find(const char *psz);
int Length();
};
C++接口如何走向 COM接口
? 动态链接符合 COM的需要
? C++中类形式的接口存在的问题
COM接口
? 概念:函数集,以二进制的形式给出了从一
方到另一方的调用规范,或者:
? COM接口是一个包含一个函数指针数组的内
存结构,每一个数组元素包含的是一个由组
件实现的函数的地址。
? 接口标识 —— IID
? IUnknown
? COM接口采用二进制结构
COM接口的标识 —— IID
? 是 GUID的一种用法
? GUID是一个 128位的长整数
? 产生规则保证了唯一性
? 例子,{54BF6567-1007-11D1-B0AA-444553540000}
? C语言结构和定义:
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
extern "C" const GUID
CLSID_MYSPELLCHECKER =
{ 0x54bf6567,0x1007,0x11d1,
{ 0xb0,0xaa,0x44,0x45,0x53,
0x54,0x00,0x00} } ;
IUnknown接口
? 所有的 COM接口都从 IUnknown派生
? C++定义:
class IUnknown
{
public:
virtual HRESULT__stdcall QueryInterface(
const IID& iid,void **ppv) = 0 ;
virtual ULONG __stdcall AddRef() = 0;
virtual ULONG __stdcall Release() = 0;
};
COM接口结构
接口指针 指针 指针函数 1
指针函数 2
指针函数 3
。。。。。。
对象实现
v t a b l ep Vt a b l e
C语言描述示例 —— IDictionary
struct IDictionaryVtbl;
struct IDictionary
{
IDictionaryVtbl * pVtbl;
};
struct IDictionaryVtbl
{
/* … QueryInterface,AddRef,Release */
BOOL (*Initialize)( IDictionary * this);//初始化函数
BOOL (*LoadLibrary)( IDictionary * this,String);//加载字典函

BOOL (*InsertWord)( IDictionary * this,String,String);//在字
典中插入词汇
void (*DeleteWord)( IDictionary * this,String);//删除字典中某
个词汇
BOOL (*LookupWord)( IDictionary * this,String,String *);//
寻找字典中某个词汇
BOOL (*RestoreLibrary )( IDictionary * this,String);//恢复字典
函数
void (*FreeLibrary)( IDictionary * this);//释放字典资源函数
};
C++语言描述示例 — IDictionary
class IDictionary, public IUnknown
{
virtual BOOL Initialize() = 0;
virtual BOOL LoadLibrary(String) = 0;
virtual BOOL InsertWord(String,String) = 0;
virtual void DeleteWord(String) = 0;
virtual BOOL LookupWord(String,String *) = 0;
virtual BOOL RestoreLibrary(String) = 0;
virtual void FreeLibrary() = 0;
};
pVt ab l e
H R ESUL T Quer y Inter f a c e ( … )
U L O N G A d d R e f () ;
U L O N G Re le a s e () ;
B O O L In iti a lize (thi s * )
B O O L L o a d L ib ra ry (thi s *,S trin g );
B O O L In s e rtW o rd)( t h is *,S trin g );
v o id D e le te W o rd)( t h is *,S trin g );
B O O L L o o k u p W o rd(th is *,S trin g,S trin g * );
B O O L Re s to re L ib ra ry (thi s *,S trin g );
v o id Fre e L ib ra ry (thi s * );
I D i ct i ona r y v t ab l e
t hi s
COM接口的内存模型
客户使用的
接口指针
p ID ictio n a ry
p V tab le Qu e ry In ter f a c e
A d d Re f
Re lea se
In it ialize
CD ictio n a ry 类中虚
函数的具体实现
v t abl e
m _pD at a
m _ Dic tF il e n a m e
。。。。。。
COM接口的内存模型 (续一 )
客户使用的
接口指针
p ID ictio n a ry 1
p ID ictio n a ry 2
p V tab le
Qu e ry In ter f a c e
A d d Re f
Re lea se
In it ialize
CD ictio n a ry 类中虚
函数的具体实现
v t abl e
m _pD at a
m _ Dic tF il e n a m e
p V tab le
m _pD at a
m _ Dic tF il e n a m e
。。。。。。
接口查询
? 目的:按照 COM规范,一个 COM对象可以实现
多个接口。即从一个接口到另一个接口的访问
途径有多个。
? 函数 QueryInterface(iid,ppv)
? 用法:用户首先调用 CoCreeateInstance函数得
到该 COM对象的一个指针(通常是 IUnknown),
然后调用该接口指针的 QueryInterface函数,获
得该 COM对象的其他指针。
? 返回值说明了对象对接口的支持情况
? S_OK( 查询成功),E_NOINTERFACE( 没有
查询的指针),E_UNEXPECTED( 未知错误)
接口查询用法示例
// load the dictionary
retValue = pIDictionary->LoadLibrary("eng_ch.dict");
if (retValue == FALSE)
{ pIDictionary->Release(); return; }
......
ISpellCheck *pISpellCheck;
HRESULT result
= pIDictionary->QueryInterface(IID_SpellCheck,
(void **)&pISpellCheck);//获得另一个接口指针
if (result != S_OK) // *pISpellCheck
{ pIDictionary->Release(); return; }
//,....,use interface pISpellCheck
// finally,release dictionary object
pIDictionary->Release();
pISpellCheck ->Release ( );
......
QueryInterface实现举例
class CDictionary, public IDictionary,public ISpellCheck(多重继承 )
{
public,
CDictionary();
~CDictionary();
public,
// IUnknown member function
virtual HRESULT QueryInterface(const IID& iid,void **ppv) ;
virtual ULONG AddRef() ;
virtual ULONG Release() ;
......
private,
int m_Ref ;
......
};
IUn k n o w n
IDi c t i o n a ry
CDicti o n a ry
IUn k n o w n
IS p e l l Ch e c k
QueryInterface实现举例(续一)
ID ictio n a ry 的 v tab le 指针
th is 指针
ISp e ll Ch e c k 的 v tab le 指针
CD ictio n a ry
的属性数据
Qu e ry In ter f a c e
A d d Re f
Re lea se
......
Qu e ry In ter f a c e
A d d Re f
Re lea se
......
ID ictio n a ry
成员函数
ISp e ll Ch e c k
成员函数
v tab le
QueryInterface实现举例(续二)
HRESULT CDictionary::QueryInterface(const IID& iid,
void **ppv)
{
if ( iid == IID_IUnknown ) {
*ppv = static_cast<IDictionary *>this ;
((IDictionary *)(*ppv))->AddRef() ;
} else if ( iid == IID_Dictionary ) {
*ppv = static_cast<IDictionary *>this ;
((IDictionary *)(*ppv))->AddRef() ;
} else if ( iid == IID_SpellCheck ) {
*ppv = static_cast<ISpellCheck * >this ;
((ISpellCheck *)(*ppv))->AddRef() ;
} else {
*ppv = NULL;
return E_NOINTERFACE ;
}
return S_OK;
}
COM对象的接口原则
? IUnknown接口一致性:组件的实例只有一个 Iunknown接口,
当查询组件的 Iunknown接口时得到的都应该是同一个指针。
? 接口对称性:如果可以从接口 A查询到接口 B,那么我们也可
以从接口 B查询到接口 A。
? 接口的自反性:指可以重复得到一个接口指针,比如我们得
到接口 A,那么我们可以通过 A再得到 A的一个接口指针。
? 接口的查询时间无关性:是指如果可以从组件的一个接口查
询到另一个接口,那么这个组件的所有接口都可以查询到该
接口
引用计数
? 引用计数是为了控制组件的生命周期所设定的。
? 多个客户可以独立地控制对象的生存
? 引用计数反映了被客户引用的次数
? 引用计数是个整数,从 0开始
? 当引用计数为 0时,表示没有客户在使用对象或者
接口 —— 删除
? 两个操作:增一和减一。 Addref函数使计数
( m_ref) 加 1,release使计数( m_ref) 减 1,当
计数为 0时,表示该组件不被使用,此时系统自动
将其移出内存。
实现引用计数
? 层次,或者粒度
组件模块
对象 1
IU n k n o w n
ISo m e In tf a c e
组件引用计数
IU n k n o w n
对象引用计数
IU n k n o w n
接口引用计数
对象 2
IU n k n o w n
IO th e rIntf a c e
。。。
对象引用计数
IU n k n o w n
接口引用计数
接口引用计数
接口引用计数
实现引用计数三种方案
? 组件级
? 计数分辨率太粗
? 对象级
? 接口级
? 计数分辨率太细
引用计数实现示例
class CDictionary, public IDictionary
{
public,
CDictionary();
~CDictionary();
public,
virtual HRESULT QueryInterface(const IID& iid,
void **ppv) ;
virtual ULONG AddRef() ;
virtual ULONG Release() ;
virtual BOOL Initialize();//初始化函数
virtual BOOL LoadLibrary(String);//装载字典函数
// ……
private,
struct DictWord *m_pData;//字典数据指针
char *m_DictFilename[128];//字典名
int m_Ref ;//引用计数值
};
引用计数实现示例 (续 )
CDictionary::CDictionary ()
{
m_Ref = 0;
//,.,初始化
}
ULONG CDictionary::AddRef ()
{
m_Ref ++;//引用计数加 1
return (ULONG) m_Ref;
}
ULONG CDictionary::Release ()
{
m_Ref --;//引用计数减 1
return (ULONG) m_Ref;
}
引用计数用法
? 客户要引用接口时 —— 增一操作
? 调用 IUnknown::AddRef()
? 客户用完接口时 —— 减一操作
? 调用 IUnknown::Release()
? 当对象的引用计数为 0时,释放
? 当组件中所有对象的引用计数为 0时,卸载
? AddRef和 Release的返回值不可靠
引用计数使用示例
// load the dictionary
BOOL retValue = pIDictionary->LoadLibrary("eng_ch.dict");
if (retValue == FALSE)//加载失败
{
pIDictionary->Release();//释放 COM对象
return;
}
......
IDictionary *pIDictionaryForWord = pIDictionary;
pIDictionaryForWord ->AddRef();//增加引用计数
// Insert or delete some word
pIDictionaryForWord ->InsertWord(“...”,“...”);//插入一个词汇
pIDictionaryForWord ->DeleteWord(“...”);//删除一个词汇
pIDictionaryForWord ->Release ( );//释放 COM对象
......
// finally,release dictionary object
pIDictionary->Release ( ); //释放 COM对象
使用引用计数规则
? 1是对于那些返回接口指针的函数
( QueryInterface, CoCreateInstance)
在返回前调用 AddRef
? 2使用完接口后调用 Release
? 3在赋值之后调用 AddRef。 在将一个接口
指针赋给另外一个接口指针时,应该调用
Addref
引用计数问题
? 在整个生存期内,AddRef与 Release一定
要配对,否则:
? 漏掉 AddRef,程序出错
? 漏掉 Release,对象永不释放
引用计数实现举例
ULONG CDictionary::AddRef ()
{
m_Ref ++;//引用计数加 1
return (ULONG) m_Ref;
}
ULONG CDictionary::Release ()
{
m_Ref --;//引用计数减 1
if (m_Ref == 0 )//如果引用计数值为 0
{
delete this;//删除 COM对象
return 0;
}
return (ULONG) m_Ref;
}
总结,COM接口特点
? 二进制特性
? 接口不变性
? 继承性 (扩展性 )
? 多态性 —— 运行过程中的多态性
DCOM和 COM+
? 主要内容
? DCOM介绍
? COM+与 Windows DNA策略
DCOM介绍
? DCOM使用一种基于标准的远程过程调
用,提供了网络透明及通信自动化,可
以使运行于不同机器上的对象之间进行
无缝互操, 而且一个对象无须了解另一
个对象的位置。
DCOM介绍
? 分布式对象技术也可以使全局的网络和
信息资源看上去象是本地的,这就使用
户更容易也更快地访问重要的业务信息。
通过分布式 COM 和远程自动化,用户
可以在整个网络内放置和执行部件,而
根本无须知道所处理的信息来自数千里
之外的地方。
DCOM介绍
? 使用 DCOM进行开发,不要求接口的使用
者与接口的提供者必须在同一计算机上。
? 值得一提的是,从纯粹的本地操作移植
到分布式操作只需要对现有代码进行少
量的修改,有时甚至不需要做任何修改。
DCOM介绍
? 在 DCOM中,客户调用 CoCreateInstanceEx()
传送服务器计算机的一个描述, 请求一个类
标识器( CLSID)和接口。该请求由服务器控
制管理器处理( SCM),它是 Windows的一
部分,负责在服务器计算机上创建和激活
COM对象。
DCOM介绍
客户端调用调用服务器端组件示意图
DCOM介绍
? 在 Win32上实现的 DCOM提供了许多很有用
的服务,包括连接点、事件、自动化、
NT事件日志和 NT服务器控制管理器等。
DCOM与 CORBA
? DCOM并不是唯一的分布对象协议,另外
一个最流行的就是 CORBA(Common Object
Request Broker Architecture,通用对
象请求代理结构 ) 。
? CORBA也有很多服务,但是在质量上和数
量上均劣于 WIN32上的 DCOM。
COM+
? COM+并不是 COM的新版本,我们可以
把它理解为 COM的新发展,或者把它看
做 COM更高层次上的应用。
? COM+的底层结构仍然以 COM为基础,
它几乎包容了 COM的所有内容,可以说
COM+是 COM,DCOM和 MTS(Microsoft
Transaction Server)的集成 。
COM+
? COM+更加注重于分布式网络应用的设计和
实现,已经成为 Microsoft系统平台策略和软
件发展策略的一部分。
? COM+继承了 COM几乎全部的优势,同时又
避免了 COM实现方面的一些不足。
? COM+紧紧地与操作系统结合起来,通过系
统服务为应用程序提供全面的服务,
Windows DNA策略
? Windows DNA是 Microsoft多年积累下来
的技术精华集合起来而形成一个完整的、
多层结构的企业应用总体方案,它使
Windows真正成为企业应用平台。
Windows DNA三层结构
COM+与 Windows DNA
? 在中间业务层,COM+已经成为现实,它
以系统服务的形式把原先散落的众多技
术综合起来,并提供简单的编程模型,
以直接应用层的编程接口为应用程序提
供服务。
? COM+是 DNA结构的核心,它将成为企业
应用或者分布式应用的基本工具。
COM+组成结构图
COM+对象环境
? COM+为每一个对象提供了一个对象环境
(Object Context),COM+系统可以在创建
COM+对象的时候为其分配一个环境对象,这
种技术也被称为截取 (intercept) 。
? 环境 (context),它是指共享同一套运行要求的
对象集合。由于不同的对象类可能使用了不同
的配置信息,所以一个进程通常包含一个或多
个环境,这些环境的配置互不兼容。
COM+系统服务介绍
? COM+队列组件
? 该服务可以有效地把客户与组件的生存期分开
? COM+事件模型
? 采用了多通道的发布 /订阅 (multicasting
publish/subscribe)事件机制,允许多个客户去
,订阅, 事件,这些事件由各种组件对象, 发布,
? 负载平衡
? 负载平衡服务可以以透明方式实现动态负载平衡
? 内存数据库 (IMDB)
? 用于保存应用的非永久状态信息
COM+总结
? 虽然 COM+仍然以 COM和 MTS为底层基础,但是由
于它定位的原因,所以 COM+新增加的内容较多。
与 COM相比较,COM+与 Windows操作系统结合得
更为紧密,反过来,Windows操作系统也更加依赖
于 COM+。
? 从目前计算机硬件以及 Windows操作系统的发展趋
势来看,COM+有可能成为推动 Windows 2000操作
系统的一个重要技术支柱,同时 COM+和 Windows
2000联合起来使得企业应用直接进入分布式应用领
域,这是我们目前已经可以感觉得到的一个发展方
向。