五 COM对象 接口及其接口方法的实现 注册表 类厂 COM组件与客户程序的交互过程 类厂的由来 类厂的定义与实现 类厂的创建

Slides:



Advertisements
Similar presentations
全院教职工基础信息 采集工作培训会 2013 年 10 月 15 日.  一、采集工作基本情况  二、 Excel 表格组成  三、 Excel 采集模板操作.
Advertisements

软件开发技术基础 第 3 章 操作系统及程序设计 讲授教师:卫颜俊. 主 要 内 容 主 要 内 容 操作系统及其功能 进程管理应用程序设计 内存管理应用程序设计 设备与文件管理应用程序设计 人机接口管理应用程序设计.
一、页面设置:版心和页边距 1 、版心: 宽度 —— 版面中文字部分的宽度。(纸张宽度 — 左右页边距) 高度 —— 版面中文字部分的高度。(纸张高度 — 上下页边距) 2 、页边距:纸张边缘与文字之间的距离。
中国公务员管理 CHINAS CIVIL SERVICE SYSTEM
正修科技大學典範人物專訪 寫作徵文比賽實施要點
四資二甲 第三週作業 物件導向程式設計.
程序设计实习 3月份练习解答
Memory Pool ACM Yanqing Peng.
史料數位化之METADATA與AUTHORITY CONTROL / 陳雪華
项目:贪吃蛇游戏设计 工作任务一:系统设计(system design) 工作任务二:豆类(Bean)设计
第一章 面向对象程序设计.
设计模式可以帮助我们改善系统的设计,增强 系统的健壮性、可扩展性,为以后铺平道路。
雷 霆 战 机 By—谷恩轩&余万全.
C#程序设计 10软件1、2班 王槐彬 计算机工程学院.
2017年9月14日12时3分 DEV349 Visual C 无缝集成,无限潜力 李建忠 微软特约讲师 上海祝成科技
類別與物件 Class & Object.
第15章 繼承與多重繼承 15-1 繼承的基礎 15-2 覆寫與隱藏父類別的成員 15-3 子類別的建構與解構子 15-4 多重繼承
第7单元 面向过程编程—— 继承与多态.
潘爱民 COM开发 潘爱民
第一章 認識Visual C 環境架構 1-1 認識Visual C Visual Studio 概觀
第二十九章 DLL / LIB函式庫開發 當我們開發程式到一個階段之後,我們一定會希望各個Component的程式碼可以分開的越清楚越好。而這一章最主要就是要告訴各位讀者,我們常在Windows系統中看到的dll或是lib的檔案該怎麼實作?做出這樣的library我們又該如何運用?為什麼使用dll或是lib有利於我們開發程式?以上這些疑問都將會在這一章中得到解答。
内容提要 对象的生命周期 构造函数 析构函数 拷贝构造函数. 常宝宝 北京大学计算机科学与技术系
第二章 C# 基础知识.
第四章 在 C# 中实现 OOP 概念.
第十一章 面向对象设计 第十二章 面向对象实现
单片机原理与应用 C/C++在现代数字计算机上的实现.
潘爱民 COM对象的实现(续) 潘爱民
八. COM跨进程特性 进程外组件 列集 标准列集过程 总体结构 存根 代理 接口列集器 ORPC通道 标准列集的实现 自定义列集
第六章 继承性和派生类 胡昊 南京大学计算机系软件所.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建
立即啟動!免費開發人員帳戶!! Windows Store / Windows Azure / Windows Phone
MFC WinSock类的编程 为简化套接字网络编程,更方便地利用Windows的消息驱动机制,微软的基础类库(Microsoft Foundation Class Libary,简称MFC),提供了两个套接字类,在不同的层次上对Windows Socket API函数进行了封装,为编写Windows.
网络游戏开发语言基础 ——Windows程序设计
郑晟 昆明理工大学 云南省计算机技术应用重点实验室
COM:moniker、UDT、control
C/C++/Java 哪些值不是头等程序对象
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
$10 可空类型.
线程同步与异步套接字编程 主讲人:孙鑫
第六章 属性、索引器、委托和事件.
潘爱民 COM:可连接对象 & 结构化存储 潘爱民
第三章 C# 基础知识.
第7章 繼承/多型/介面 注意: 本投影片僅供本書上課教師使用,非經同意請勿上網轉載或供拷貝.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
潘爱民 自动化(Automation) 潘爱民
劉崇汎 崑山科技大學 電腦與通訊系 DLL的建立與引用 劉崇汎 崑山科技大學 電腦與通訊系
第五章 递归与广义表 递归的概念 递归过程与递归工作栈 递归与回溯 广义表.
C#程序设计基础 $3 成员、变量和常量.
四 COM接口 接口的结构与描述 IUnknown 接口 C++, C, Delphi IDL 接口的的标识 方法与结果 数据类型
COM组件及其设计 一、COM组件定义和DNA思想简介 二、COM组件的特点 三、COM组件对象的软件工程方法 四、DNS、MTS
保留字與識別字.
六 . COM接口的其他实现方法 基于表格驱动的接口查询 接口查询的本质 宏 应用 多重继承下的名字冲突 潜在的缺陷 临时的方案
Java程式初體驗大綱 大綱 在學程式之前及本書常用名詞解釋 Hello Java!程式 在Dos下編譯、執行程式
字符串 (String) 字符串是 n (  0 ) 个字符的有限序列, 记作 S = “c1c2c3…cn” 其中,S 是串名字
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
动态链接库 主讲人:孙鑫
第二章 Java语法基础.
Review 1~3.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
第五章 序列埠通訊 並列與序列通訊簡介 認識序列埠 認識字元資料類型 字串資料類型 從序列埠監控視窗觀察變數 從Arduino接收序列資料
第2章 Java语言基础.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
本节内容 在堆中创建对象 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
Presentation transcript:

五 COM对象 接口及其接口方法的实现 注册表 类厂 COM组件与客户程序的交互过程 类厂的由来 类厂的定义与实现 类厂的创建 通过类厂创建对象 类厂对组件生存期的控制 COM组件与客户程序的交互过程

1 接口及其接口方法的实现 COM接口只是描述了它所代表的功能.实现这些功能的是COM对象.COM规范并没有规定对象应该如何实现,只要接口指针能够访问到对象对接口的具体实现即可. 这里我们使用C++语言来实现COM对象. 当然,使用C++语言也有不同的方法来实现COM对象,只要通过接口指针能够访问到对象的的方法和属性(私有数据)即可。 这里首先我们使用从接口类派生实现类的办法来实现COM对象。 实际上,使用C++还有别的办法也能实现COM对象。比如,MFC使用的嵌套类的办法,使用模板类的方法等等.我们将在后续章节接触到这些方法. 继续上一章的例子:我们的字典支持接口IDictionary:

class IDictionary:public IUnknown { virtual BOOL __stdcall Initialize() = 0; virtual BOOL __stdcall LoadLibrary(String) = 0; virtual BOOL __stdcall InsertWord(String, String) = 0; virtual void __stdcall DeleteWord(String) = 0; virtual BOOL __stdcall LookupWord(String, String *) = 0; virtual BOOL __stdcall RestoreLibrary(String) = 0; virtual void __stdcall FreeLibrary() = 0; };//注意都是纯虚函数 假设字典还支持另外一个接口: class ISpellCheck : public IUnknown { public : virtual BOOL __stdcall CheckWord(String, String*) = 0;}; 这个接口支持拼写检查的功能 实现类的定义如下:

class CDictionary : public IDictionary , public ISpellCheck{ CDictionary(); ~CDictionary(); //构造函数,析构函数 // IUnknown 成员函数 (在这里要实现,所以再次申明) virtual HRESULT __stdcall QueryInterface(const IID& iid, void **ppv) ; virtual ULONG __stdcall AddRef() ; virtual ULONG __stdcall Release() ; // IDictionary成员函数 virtual BOOL __stdcall Initialize(); ...... // ISpellCheck成员函数 virtual BOOL __stdcall CheckWord (String word, String *); private : struct DictWord *m_pData; char *m_DictFilename[128];……//私有与功能相关的数据. int m_Ref ;//用作引用计数 }; CDictionary类,对象,与接口的内存结构 如下:

CDictionary类,对象,与接口的内存结构 this pVtable for IDictionary QueryInterface AddRef Release Cdictionary 类中虚函数的具体实现 CDictionary的vtable m_pData m_DictFile pVtable for ISpellCheck Initialize InsertWord …… CheckWord IDictionary接口指针 另一个IDictionary接口指针 ISpellCheck接口指针 另一个COM对象 某接口指针 Cdictionary2 类中虚函数的具体实现 假设CDictionary2也实现了这两个接口 CDictionary2的vtable CDictionary类,对象,与接口的内存结构 浅蓝色: 类 绿色: 对象 灰色: 接口指针

COM对象的首要任务是要实现在IUnknown中定义的几个纯虚函数,(否则不能创建实例.) Cdictionary增加了一个成员变量作为引用计数。 CDictionary::CDictionary(){ m_Ref = 0;……} //引用计数赋初值 ULONG CDictionary::AddRef(){ m_Ref ++; //增加计数 return (ULONG) m_Ref;} ULONG CDictionary::Release(){ m_Ref --; //减少计数 if (m_Ref == 0 ) { delete this; //减到0时,删除自身 return 0; } 跟上章IExtensibleObject的 DuplicatePointer函数和DestroyPointer函数的实现方式一样。

接口查询函数为: HRESULT CDictionary::QueryInterface(const IID& iid, void **ppv) { if ( iid == IID_IUnknown ) { *ppv = (IDictionary *) this ; ((IDictionary *)(*ppv))->AddRef() ; } else if ( iid == IID_Dictionary ) } else if ( iid == IID_SpellCheck ) { *ppv = (ISpellCheck *) this ; ((ISpellCheck *)(*ppv))->AddRef() ; } else { *ppv = NULL; return E_NOINTERFACE ; } return S_OK; } 注意这里使用((IDictionary *)(*ppv))->AddRef() ; 而不是直接AddRef() ; 这里调用AddRef的指针可能不一样,当然,它们都是指向同一个对象, 都是使用的同一个AddRef() ; 但是也有例外.在使用其他方式实现接口时(嵌套类方式), 不同的指针调用AddRef版本可能不一致. 为了统一起见,一律使用新指针去调用AddRef,这样也符合直观的逻辑.

或者,显示地使用静态转换: 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) ; } else if ( iid == IID_SpellCheck ) { *ppv = static_cast <ISpellCheck *>( this) ; ((ISpellCheck *)(*ppv))->AddRef() ; } else {*ppv = NULL; return E_NOINTERFACE ; return S_OK;

注意我们没有使用 if ( iid == IID_IUnknown ) { *ppv = static_cast < IUnknown *> (this) ; ……} 是因为这种转换存在二义性,无法通过编译。当客户请求Iunknown接口时,我们直接返回IDictionary接口,因为IDictionary接口虚表的前三个函数正是IUnknown的前三个函数。而且这样处理也满足接口查询的原则。(当然转换成ISpellCheck接口也是可以的) 然后要实现IDictionary中和ISpellCheck中定义的虚函数.这是COM对象之所以存在的价值. 过程略. 完成以上函数以后,COM对象的实质性的功能已经完成,但是为了能让客户顺利地使用使用. 但是,这其中还有许多的关节……

2 注册表 COM对象往往以一个DLL为载体.当然,有时也以一个exe程序为载体. 客户程序和COM对象可能位于不同的进程,甚至不同的机器上.我们需要一种中介机制在双方打起桥梁. 当COM对象的数目,接口的数目,COM客户的数目很大时,其必要性就尤其突出. 注册表是Windows操作系统的中心数据仓库。当组件程序安装到机器上以后,必须把它的信息注册到注册表中,客户才能从表中找到组件程序并对其进行操作。注册表可以看作是组件与客户的中介。是COM实现位置透明性的关键. COM对象的信息存储在注册表的HK_CLASSES_ROOT键的CLSID子键下。在每个COM对象的CLSID子键下,存储了一些相关的信息,比如组件程序的路径,版本号,类型库、ProgID, COM对象的唯一标识符CLSID,COM接口的唯一标识符IID等。ProgID(Program identifier)是字符串化的组件名字。COM提供了两个API用于CLSID和ProgID的相互转换:CLSIDFromProgID和ProgIDFromCLSID。

组件有两种方式将信息注册到注册表中。 1。非自注册。 非自注册组件本身不编写任何代码来支持注册操作,相反,由程序员手工编写注册文件,手工输入到注册表中。比如:使用文本编辑器将要注册的内容写入文件并以.reg后缀保存。双击之组件信息即由注册表编辑器输入注册表。以下是一个注册文件的例子: REGEDIT HKEY_CLASSES_ROOT\CLSID\{89A48671-20B3-11d0-8B80-EA9EFFE6330C} = MyDictionary HKEY_CLASSES_ROOT\CLSID\{89A48671-20B3-11d0-8B80-EA9EFFE6330C}\InprocServer32 = c:\MyDictDll.dll

2。自注册 (先只讨论进程内组件的情形) Windows系统提供了一个注册进程内组件的工具RegSvr32.exe, 只要进程内组件提供了入口函数,RegSvr32.exe就会调用入口函数完成注册工作或注销工作。 注册:RegSvr32.exe c:\MyDictDll.dll 注销:RegSvr32.exe \u c:\MyDictDll.dll 组件负责提供的入口函数名字分别为DllRegisterServer 和 DllUnRegisterServer 分别完成注册和注销任务。 DllRegisterServer 和 DllUnRegisterServer 要由组件程序实现,其中要使用Windows提供的操作注册表的API如RegCreateKey和RegSetValue等函数。

3 类厂 3.1 类厂的由来 最初使用源代码共享时,客户包含C++类的定义,然后使用诸如 3 类厂 3.1 类厂的由来 最初使用源代码共享时,客户包含C++类的定义,然后使用诸如 FastString *pFS=new FastString; 这样的方式 使用动态链接库作为C++对象的载体时, 仍然使用 FastString *pFS=new FastString; 使用接口类把接口从实现中分离出来: FastStringItf *pFIf=new FastStringItf; 使用抽象基类把接口与对象分开以后,COM组件只需引出一个诸如 extern "C" {__declspec(dllexport) IFastString *CreateFastString(const char*psz);} 的函数。 而客户只需调用此引出函数,就可以得到接口指针。 IFastString *pIF=CreateFastString(“abcdefg”); 但是 COM规范考虑地问题更多。比如我们希望在客户创建对象的时候需要客户提供口令或其他安全信息。我们使用另外一个类来创建对象并返回接口。比如说,用以下的方式:

class CDictionaryFactory { public: HRESULT CreateDictinary(IDictionary **ppv) { …… //在这里可以进行身份认证、安全认证等附加操作。 *ppv=new CDictionary; return NO_ERROR; }; } 组件DLL中再把CDictionaryFactory类暴露出去,客户则可以这样使用: IDictionary * pID=NULL; CDictionaryFactory *pCF=new CDictionaryFactory ; IDictionary * pID=pCF-> CreateDictinary(& pID); pID->……//使用字典接口的功能。 通过以上方式,可以进一步控制COM对象的安全性。 但是,很显然,和以前的FastString类一样,CDictionaryFactory类不能很安全地从DLL中引出。然而这一次,我们已经有了经验. 我们可以完全仿照COM对象的实现方式,对CDictionaryFactory的定义和实现也分离开。这就是类厂对象以及类厂接口的由来。

3.2 类厂的定义与实现 类厂,准确地说应该叫“对象厂”,(因为是用来创建对象的). 有的文献称为“类对象” (class object) . COM库通过类厂来创建COM对象。对应每个COM类,有一个类厂专门用于该COM类的对象的创建工作。而且,类厂本身也是一个COM对象。(当然,类厂不再需要别的类厂来创建了.) 类厂支持一个特殊的接口IClassFactory.(如前所述,它也派生自IUnknown) IClassFactory : public IUnknown { public: virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) = 0; // pUnkOuter 用于对象被聚合的情形,一般把它设为NULL。 Riid指COM对象创建完后,客户应该得到的初始接口IID,比如 IID_Idictionary,IID_ISpellCheck 等. ppv用来保存接口指针。 virtual HRESULT _stdcall LockServer( BOOL fLock) = 0; }; //LockServer用来控制DLL组件的卸载。 类厂的定义则为:

class CDictionaryFactory : public IClassFactory CDictionaryFactory (); ~CDictionaryFactory (); //IUnknown 成员 HRESULT __stdcall QueryInterface(const IID& iid, void **ppv); //查询接口,它对IUnkown接口和IClassFactory接口提供支持并返回其指针。 ULONG __stdcall AddRef(); ULONG __stdcall Release(); //AddRef,Release成员函数实现引用计数操作,m_Ref是其引用计数变量。以上三个函数的实现方法与一般的COM对象完全类似,略去. //IClassFactory 成员 HRESULT __stdcall CreateInstance(IUnknown *, const IID& iid, void **ppv); // CreateInstance是接口类最重要的成员函数。它的实现见下页: HRESULT __stdcall LockServer(BOOL);//组件生存期控制,见后. private: ULONG m_Ref;//类厂接口的引用计数 }; CreateInstance的实现如下:

HRESULT CDictionaryFactory::CreateInstance(IUnknown HRESULT CDictionaryFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) { CDictionary * pObj; HRESULT hr; *ppv=NULL; // 确保pUnknownOuter在这里是空指针 if (NULL != pUnknownOuter) return CLASS_E_NOAGGREGATION; pObj=new CDictionary(); // 创建COM对象 if (NULL==pObj) return hr; hr=pObj->QueryInterface(iid, ppv); //返回COM对象的初始接口 if (hr != S_OK) delete pObj; return hr; }

3.3 类厂的创建 现在的问题是类厂如何创建。COM并不使用如下的方法: 3.3 类厂的创建 现在的问题是类厂如何创建。COM并不使用如下的方法: IClassFactory*pIF=CreateClassFactory(…) 相反,COM规定使用DllGetClassObject函数来完成这一任务: extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void **ppv) { if (clsid == CLSID_Dictionary ) { CDictionaryFactory *pFactory = new CDictionaryFactory; if (pFactory == NULL) { return E_OUTOFMEMORY ;} HRESULT result = pFactory->QueryInterface(iid, ppv); return result; } else return CLASS_E_CLASSNOTAVAILABLE; } 首先确认clsid是我们要创建的字典对象的ID,然后创建类厂对象,调用类厂对象的QueryInterface成员函数返回类厂接口指针。整个过程与类厂创建COM对象并返回COM接口的过程完全一致。 Iid一般为IID_ICLassFactory. ppv用来保存类厂接口指针。 然而客户仍然不直接调用DllGetClassObject引出函数函数来获得类厂接口指针。COM规定,客户使用如下的COM库函数:

Extern “C”__stdcall HRESULT CoGetClassObject( REFCLSID rclsid, //将要创建的COM对象的ID DWORD dwClsContext, //指定组件的类别,进程内或进程外 LPVOID pvReserved, //用于DCOM,指定远程对象的服务器信息,此时为NULL REFIID riid, //类厂接口的ID,一般为IID_ICLassFactory void** ppv //用来保存类厂的接口指针。 ); 我们考虑进程内组件的情形。(即COM对象存在于DLL中) CoGetClassObject从注册表中查找组件clsid程序的路径名,(COM组件注册时最主要的任务之一就是注册路径名)然后加载组件到内存。再调用组件程序的引出函数DllGetClassObject以创建类厂接口对象并返回指针。(MSDN文档中指出,在这个过程中并没有调用CoLoadLibrary,但是是否调用了Win32API LoadLibrary和GetProcAddress,无从得知,也许Microsoft使用了未公开的其他API,但是从原理上,我们可以清晰地了解整个过程。) 在调用DllGetClassObject时,CoGetClassObject直接把clsid,riid和ppv三个参数传进去。

3.4 通过类厂创建对象 一旦客户得到了类厂接口指针,就可以使用该指针调用其CreateInstance成员函数来创建COM对象,并得到该COM对象的接口指针。实际上COM还提供了另一个函数把以上两步操作封装起来: Extern “C”__stdcall HRESULT CoCreateInstance ( REFCLSID rclsid, //将要创建的COM对象的ID LPUNKNOWN pUnkOuter, //用于被聚合的情形 DWORD dwClsContext, //指定组件的类别,进程内或进程外 REFIID riid, //COM接口的ID比如IID_IDictionary void** ppv //用来保存COM接口指针。 ); 它的实现方式可以是这样的:

HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID FAR* ppv) { IClassFactory *pCF; HRESULT hr; Hr=CoGetClassObject (clsid,dwClsContext,NULL,IID_IClassFactory,(void**)&pCF); If(FAILED)(hr)) return hr; Hr=pCF->CreateInstance(pUnkOuter,iid,(void**)&ppv); pCF->Release(); Return hr;} 使用这种方式把类厂屏蔽起来了,使得客户看不到类厂接口指针,方便客户的使用。 当然用户也可以使用CoGetClassObject先得到类厂指针,然后使用类厂指针去创建COM对象. 客户使用类厂创建COM对象的过程如下图:

客户程序 调用CoCreateInstance 调用CoGetClassObject 获得类厂接口指针 创建对象 使用对象 组件程序 DllGetClassObject 创建类厂 返回类厂接口指针 COM对象 类 厂 COM库 CoGetClassObject 根据注册表找到DLL 装入DLL程序 调用DllGetClassObject函数 返回类厂接口 6 1 9 7 10 8 4 5 3 2 1.CoCreateInstance 调用CoGetClassObject 2. CoGetClassObject 根据注册表找到Dll的路径并加载到内存中 3. CoGetClassObject调用组件程序的引出函数DllGetClassObject 4. DllGetClassObject函数创建类厂 5. DllGetClassObject函数把类厂接口指针返回给CoGetClassObject函数 6.CoGetClassObject函数把类厂接口指针返回给CoCreateInstance函数 7.CoCreateInstance函数得到类厂接口指针后,调用类厂的CreateInstance函数 8.类厂创建COM对象 9.类厂把COM对象的接口返回给CoCreateInstance函数,CoCreateInstance函数返回 10.客户可以通过接口使用COM对象提供的服务

3.5 类厂对组件生存期的控制 概念澄清: COM对象的引用计数是对COM对象的生存期的控制。组件指DLL或EXE, 系COM对象的载体. 客户有可能在一个载体内创建同一种COM对象类的多个对象. 每个对象及其接口指针的引用计数机制来对该对象进行生存期的控制. 而组件的生存期,是指组件何时可以从内存中卸载的时期. 当然,组件的生存期要比单个COM对象的生存期要长. 以下的讨论,我们假设组件中只有一种COM对象. 而对于有多种COM对象的情形, 完全可以类似地处理. 一般情况下,客户只是在创建COM对象的时候要用到类厂接口指针,创建完后就把类厂对象丢弃了。为了效率等原因,客户可能需要控制组件程序的生存期。因为如果组件程序被释放后,客户可能在将来还要重新加载,而且此时由于类厂对象也随着组件程序一起被销毁,客户再使用此接口指针会出错。因此,如果客户能控制其生存期,客户可以在将来继续使用类厂接口指针,以便创建新的COM对象,这种情况下可能会提高程序的工作效率。 类厂接口的LockServer函数正是为了这个目的而设置的。 LockServer函数的实现:

在组件程序中定义一个全局变量 ULONG g_LockNumber = 0; HRESULT CDictionaryFactory::LockServer(BOOL bLock) { if (bLock) g_LockNumber ++; else g_LockNumber --; return NOERROR; } 为了准确地判断组件程序能否卸载,我们还需要引入一个全局变量以记录COM对象的个数。 ULONG g_DictionaryNumber = 0; 在Cdictionary的构造函数和析构函数中分别进行增1和减1操作。

这样当锁计数器和组件对象个数计数器都为零是组件程序就可以安全卸载了。 extern "C" HRESULT __stdcall DllCanUnloadNow(void) { if ((g_DictionaryNumber == 0) && (g_LockNumber == 0)) return S_OK; else return S_FALSE; } 而这个引出函数是当客户执行CoFreeUnusedLibraries时,由COM库调用的函数。 通过COM对象引用计数器、组件对象个数计数器、锁计数器的引入,客户可以真正安全、方便且灵活地控制COM逻辑对象以及其运行载体。

至此,COM组件的四个引出函数已经全部介绍完毕: DllGetClassObject DllRegisterServer DllUnRegisterServer DllCanUnloadNow 我们也接触到了COM库中最为常用的几个函数CoGetClassObject CoCreateInstance CoFreeUnusedLibrary 我们所说的COM库是MS提供的在Windows平台上的COM的实现。主要的可执行程序为rpcss.exe ( SCM, Service Control Manager 服务控制管理器), ole32.dll和oleaut32.dll,包括几十个函数,其中最重要的我们已经接触到。在使用COM库提供的函数之前要先调用CoInitialize函数对COM库进行初始化,使用完以后使用CoUnInitialize以释放COM库所维护的资源。

4 COM组件与客户程序的交互过程 以下是一个小结, 整理出客户程序创建COM对象,使用COM接口,以及最后释放资源的全过程.

客户程序 COM库 组件程序 CLSID clsid; IClassFactory *pClf; Iunkown *pUnkown; CoInitialize(NULL); CLSIDFromProgID(“Dictionary”,&clsid); COM在注册表中查找字典的CLSID CoGetClasObject(clsid,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pClf; COM库在内存中查找clsid组件,如果没有装入内存,从注册表中获取其路径加载到内存中。然后调用DllGetClassObject 创建类厂对象CDictionaryFactory,并返回IClassFactory接口 COM库返回IClassFactory接口给客户 pClf->CreateInstance(NULL,IID_Iunkown,(void**)&pUnkown); 类厂对象的CreateInstance函数通过组件的vtalbe被客户直接调用。 New Cdictionary; 返回Iunkown指针

客户程序 COM库 组件程序 客户使用字典对象,通过接口指针进行各种操作 。。。。。。 响应客户的操作 pClf->Release(); pUnkown->Release(); 组件对象的Release被调用。如果计数为零,则删除自己。 CoFreeUnusedLibraries(); COM库调用字典组件的引出函数DllCanUnloadNow() DllCanUnloadNow中:如果不存在字典对象,且锁计数器为零则返回真,否则为假 如果DllCanUnloadNow中返回真则CoFreeLibrary();卸载组件程序 CoUninitialize(); COM库释放资源 客户程序退出