《从面向对象到面向COM》由会员分享,可在线阅读,更多相关《从面向对象到面向COM(77页珍藏版)》请在金锄头文库上搜索。
1、从面向对象到面向COMStillwatersrundeep.流静水深流静水深,人静心深人静心深Wherethereislife,thereishope。有生命必有希望。有生命必有希望本课的内容及目标本课的内容及目标内容:内容: 面向对象的一般概念 从面向对象到面向COM COM组件技术目标:目标: 了解程序设计技术的发展动向 提升程序设计能力 开阔专业视野课程参考书目 COM原理与应用COM本质论 COM技术内幕 Advanced CORBA Programming with C+ 面向对象的基本概念面向对象的基本概念 为节省每一个字节而努力的阶段为节省每一个字节而努力的阶段例:用PASCAL
2、语句写成的程序段 AI:=AI + AT; AT:=AI AT; AI: =AI AT;目的是什么?我们现在习惯的写法是什么? 不关心不关心“空间的浪费空间的浪费”,更关心程序的清晰框架结构阶段,更关心程序的清晰框架结构阶段例:用FORTRAN语言编写的程序段 DO 5 I=1,N DO 5 J=1,N5 V(I,J) = (I/J)*(J/I)程序设计的发展历程程序设计的发展历程IF (X .LT. Y) GOTO 30IF (Y .LT. Z) GOTO 50SMALL = ZGOTO 7030 IF (X .LT. Z) GOTO 60SMALL = ZGOTO 7050 SMALL =
3、 YGOTO 7060 SMALL = X70 CONTINUESMALL = XIF (Y .LT. SMALL) SMALL = YIF (Z .LT. SMALL) SMALL = Z 注重注重“重用性重用性”的问题。的问题。 面向对象 注重注重“标准化元件标准化元件”的阶段。的阶段。 面向组件?软件企业分工细化阶段?软件企业分工细化阶段 类类是具有相同属性特征事物的集合。计算机专业语境下,类是封装了状态(变量)和操作(对变量处理的过程和函数)的抽象数据类型。(对应于标准数据类型) 对象对象是实例,它反映了具体的事物。 鸟,是类的概念。麻雀是鸟类的一个实例。一只麻雀? 对象由类来定义 一
4、个对象可以与多个具有继承关系的类相联系,即:A是一个类,B、C、D是在A的基础上扩展后的新类,E,F,G则是在C类的基础上发展而来,而对象x 是F类的一个实例。有关术语有关术语封装封装、继承继承与与多态多态封装、继承与多态面向对象概念的三个关键词面向对象概念的三个关键词 封装是实现面向对象程序设计的第一步,封装就是将数据或函封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。数等集合在一个个的单元中(我们称之为类)。 C+对类和对象的描述:对类和对象的描述:Class studentPublic:成员函数成员函数1;成员函数成员函数2;Private
5、:成员变量成员变量1;成员变量成员变量2; Int x,y;Student z;类名例一:C+中类的定义与实现说明:包含成员变量,成员函数,类的定义,对象的定义。例二、例三:成员函数在类内、类外实现的情形例四:成员函数是私有函数时的存取特性与意义 封装的意义在于保护或者防止代码(数据)被我们封装的意义在于保护或者防止代码(数据)被我们无无意意中破坏。中破坏。 在面向对象程序设计中数据被看作是一个中心的元素在面向对象程序设计中数据被看作是一个中心的元素并且和使用它的函数结合的很密切,从而保护它不被其它并且和使用它的函数结合的很密切,从而保护它不被其它的函数意外的修改。的函数意外的修改。 从程序语
6、言角度来看,在一个对象中代码和(或)数据可以是从程序语言角度来看,在一个对象中代码和(或)数据可以是这个对象私有的,不能被对象外的部分直接访问。因而对象提供了这个对象私有的,不能被对象外的部分直接访问。因而对象提供了一种高级保护以防止程序被无关部分错误修改或错误地使用了对象一种高级保护以防止程序被无关部分错误修改或错误地使用了对象的私有部分。的私有部分。 当从对象外部试图直接对受保护的内部数据进行修改时,将被当从对象外部试图直接对受保护的内部数据进行修改时,将被程序拒绝,只有通过对象所提供的对外服务函数才能够对其内部数程序拒绝,只有通过对象所提供的对外服务函数才能够对其内部数据进行必要的加工,
7、从而保证了数据加工的合法性。据进行必要的加工,从而保证了数据加工的合法性。 从这一意义上讲,把这种代码和数据的联系称为从这一意义上讲,把这种代码和数据的联系称为“封装封装”。换。换句话说,封装是将对象封闭保护起来,是将内部细节隐蔽起来的能句话说,封装是将对象封闭保护起来,是将内部细节隐蔽起来的能力。力。 实现的细节是实现的细节是“可变的部分可变的部分”。如果。如果“块块”是单个类,那么是单个类,那么可变的部分通常用可变的部分通常用 private: 或或 protected: 关键字来封装。关键字来封装。 “稳定的部分稳定的部分”是接口。好的接口提供了一个以用户的词汇简是接口。好的接口提供了一
8、个以用户的词汇简化了的视图,并且被从外到里的设计。(此处的化了的视图,并且被从外到里的设计。(此处的“用户用户”是指其它是指其它开发者,而不是购买完整应用的最终用户)。开发者,而不是购买完整应用的最终用户)。 设计一个清晰的接口并且将实现和接口分离,只不过是允许用设计一个清晰的接口并且将实现和接口分离,只不过是允许用户使用接口并强迫用户使用接口。户使用接口并强迫用户使用接口。 如何才能防止其它程序员查看我的类的私有部如何才能防止其它程序员查看我的类的私有部分而破坏封装?分而破坏封装?讨论讨论 只要其它程序员写的代码不依赖于他们的所见,那么即使它们只要其它程序员写的代码不依赖于他们的所见,那么即
9、使它们看了你的类的看了你的类的 private: 和和/或或 proteced: 部分,也不会破坏封装。换部分,也不会破坏封装。换句话说,句话说,封装不会阻止人认识类的内部。封装只是防止他们写出依封装不会阻止人认识类的内部。封装只是防止他们写出依赖类内部实现的代码。倘若他们写的代码依赖于接口而不是实现,赖类内部实现的代码。倘若他们写的代码依赖于接口而不是实现,就不会增加维护成本。就不会增加维护成本。 不必这么做不必这么做封装是对于代码而言的,而不是对人。封装是对于代码而言的,而不是对人。 封装是一种安全装置吗?封装是一种安全装置吗? 封装要防止的是错误,封装要防止的是错误, 而不是而不是封装封
10、装 != 安全。安全。钱掉了!钱掉了!成员函数、成员变量中的另类成员函数、成员变量中的另类例五:构造函数例六:析构函数例七:何时使用构造函数例八:友元函数例九:静态变量2005-9-301.继承的概念及重要性继承的概念及重要性 inheritance :是软件重是软件重用的一种形式,将相关的类用的一种形式,将相关的类组织起来,并分亨其间的共组织起来,并分亨其间的共通数据和操作行为。通数据和操作行为。最具吸引力的特点最具吸引力的特点: 新类可以从现有的类库中继承。提倡建立与现有的新类可以从现有的类库中继承。提倡建立与现有的类有许多共性的新类,添加基类的所没有的特点以及取代类有许多共性的新类,添加
11、基类的所没有的特点以及取代和改进从基类继承来的特点来实现软件的重用和改进从基类继承来的特点来实现软件的重用 单重继承形成树状层次结构,由基类和派生类构成了单重继承形成树状层次结构,由基类和派生类构成了一种层次关系,继承的层次在系统的限制范围内是任意的。一种层次关系,继承的层次在系统的限制范围内是任意的。2.基类基类 父类定义了所有子类共通的对外接口和父类定义了所有子类共通的对外接口和私有实现内容,父类被称为基类私有实现内容,父类被称为基类 成员函数:成员函数: 基类的私有成员只能被基类的成员函数基类的私有成员只能被基类的成员函数和友元访问,基类的受保护成员只能被基类和友元访问,基类的受保护成员
12、只能被基类及派生类的成员函数和友元访问及派生类的成员函数和友元访问3.派生类新类继承预定义基类的数据成员和成员函数,而不必重新编写数据成员和成员函数,这种新类叫派生(derived)类派生类永远不能直接直接访问基类类的私有成员重定义函数:派生类中无需继承的功能及要扩充的基类功能可以重定义成员函数,但在派生类再调用基类的同名函数同名函数时要用到作用域运算符Employee:print()派生类的构造函数和析构函数:由于派生类继承了基类的成员,所以在建立派生类的实例对象时,必须调用基类的构造函数来初始化派生类对象中的基类成员。可隐式的调用基类构造函数,也可在派生类的构造函数中通过给基类提供初始化值
13、(成员初始化值列表)明确的调用构造函数。构造函数调用顺序:先执行基类的构造函数-派生类构造函数析构函数调用顺序正好相反。派生类不继承基类的构造函数、析构函数和赋值运算符,但派生类的构造函数和赋值运算符能调用基类的构造函数和赋值运算符。类指针类指针:指向基类的指针,指向派生类的指针。两者关系可以可以直接用基类指针引用基类对象可以直接用派生类指针引用派生类对象可以用基类指针引用一个派生类对象,但只能引用基类成员。用派生类指针引用基类对象,绝对不行不行。必须先强制转换为基类指针例一:基类和派生类的构造函数说明:在继承关系中构造函数执行顺序。例二:指向类的指针说明:int *p; /指向整型的指针P。
14、例三:对不同的类使用相同的指针说明:程序从头到尾始终只用一个“万能”指针指引一切。例四:使用指针时基类和派生类名字的冲突说明:如果基类和派生类中有同名函数,会怎样呢?多多态态 多态性是面向对象的核心,它的最主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现。通常多态性被简单的描述为“一个接口,多个实现”。在C+里面具体的表现为通过基类指针访问派生类的函数和方法。 动态联编: 联编就是将模块或者函数合并在一起生成可 执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态
15、联编和动态联编。在编译阶段就将函数实现和函数调用关联起来称之为静态联编,静态联编静态联编在编译阶段就必须了解所有的函数或模块执行所需要检测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型对函数的选择是基于指向对象的指针(或者引用)的类型。反之在程序执行的时候才进行这种关联称之为动态联编,动态联动态联编编对成员函数的选择不是基于指针或者引用,而是基于对象类型,不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。不同的对象类型将做出不同的编译结果。换一种说法:如果使用基类指针访问派生类中的同名函数,希望执行的是派生类中的成员函数,怎样实现? 下面我下面我们看一个
16、静看一个静态联编的例子的例子:# #include class shape public: void draw()cout“I am shape endl; void fun()draw(); ;class circle:public shape public: void draw()cout“I am circle fun(); 程序的程序的输出出结果果? 程序的程序的输出出结果我果我们希望是希望是I am circle造成造成这个个结果的原因是静果的原因是静态联编。解释:解释: 静静态联编需要在需要在编译时候就确定函数的候就确定函数的实现,但事,但事实上上编译器在器在仅仅知道知道shape
17、的的地址地址时候无法候无法获取正确的取正确的调用函数,它所知用函数,它所知道的道的仅是是shape:draw(),最最终结果只能是果只能是draw操作束操作束缚到到shape类上。上。产生生I am shape的的结果就不足果就不足为奇了。奇了。事事实上却上却输出了出了“I am shape” 为了能够引起动态联编,我们只需要将需要动态联编的函数声明为虚函数即可。动态联编只对虚函数起作用。我们在通过基类而且只有通过基类访问派生类的时候,只要这个基类中直接的或者间接(从上层继承)的包含虚函数,动态联编将自动唤醒。下面我们将上面的程序稍微改一下。 1.#include2.classshape3.p
18、ublic:4.virtualvoiddraw()coutIamshapeendl;5.voidfun()draw();6.;7.classcircle:publicshape8.public:9.voiddraw()coutIamcircledraw();15.程序执行得到了正确的结果I am circle。关键动态联编过程: 编译器在执行过程中遇到virtual关键字的时候,将自动安装动态联编需要的机制,首先为这些包含virtual函数的类(注意不是类的实例)-即使是祖先类包含虚函数而本身没有-建立一张虚拟函数表VTABLE。在这些虚拟函数表中,编译器将依次按照函数声明次序放置类的特定虚函
19、数的地址。同时在每个带有虚函数的类中放置一个称之为vpointer的指针,简称vptr,这个指针指向这个类的VTABLE。 关于虚拟函数表,有几点必须声明清楚:1. C+编译时候编译器会在含有虚函数的类中加上一个指向虚拟函数表的指针vptr。 2. 从一个类别诞生的每一个对象,将获取该类别中的vptr指针,这个指针同样指向类的VTABLE。 因此类、对象、VTABLE的层次结构可以用下图表示。其中X类和Y类的对象的指针 都指向了X,Y的虚拟函数表,同时X,Y类自身也包含了指向虚拟函数的指针。#includeclassshapepublic:virtualvoiddraw()coutshape:
20、draw()endl;virtualvoidarea()coutshape:area()endl;voidfun()draw();area();classcircle:publicshapepublic:voiddraw()coutcircle:draw()endl;virtualvoidadjust()coutcircle:adjust()Release();基本接口基本接口IUnknown每一个COM接口都派生于IUnknown。这个名字有点误导人,其中没有未知(Unknown)接口的意思。它的原意是如果有一个指向某COM对象的IUnknown指针,就不用知道潜在的对象是什么,因为每个CO
21、M对象都实现IUnknown。IUnknown 有三个方法:AddRef() 通知COM对象增加它的引用计数。如果你进行了一次接口指针的拷贝,就必须调用一次这个方法,并且原始的值和拷贝的值两者都要用到。Release() 通知COM对象减少它的引用计数。QueryInterface() 从COM对象请求一个接口指针。当coclass实现一个以上的接口时,就要用到这个方法。当你用CoCreateInstance()创建对象的时候,你得到一个返回的接口指针。如果这个COM对象实现一个以上的接口(不包括IUnknown),你就必须用QueryInterface()方法来获得任何你需要的附加的接口指针
22、从从C+到到COM客户重用客户重用C+对象对象C+C+客户重用客户重用客户重用客户重用C+C+对象对象对象对象功能介绍: 用字符串数组模拟数据库管理系统的工作原理。 实现对“数据库”的建立、删除 读、写 表或记录的定位实现方法: 定义DB类,将定义类的.h文件单独放在一个文件夹中,假装自己是接口。文件夹起名为interface。 实现DB类,将实现类的.CPP文件单独放在一个文件夹中,文件夹起名为object。 建一个VC+工程,将上面两个文件加入工程,增加菜单映射函数,实现菜单功能。生成C+对象CDB (Dbsev.h)class CDB public:HRESULT Read(short
23、nTable, short nRow, LPTSTR lpszData);HRESULT Write(short nTable, short nRow, LPCTSTR lpszData);HRESULT Create(short &nTable, LPCTSTR lpszName);HRESULT Delete(short nTable);HRESULT GetNumTables(short &nNumTables);HRESULT GetTableName(short nTable, LPTSTR lpszName);HRESULT GetNumRows(short nTable, sho
24、rt &nRows);private:CPtrArray m_arrTables; / Array of pointers to CStringArray (the database)CStringArray m_arrNames; / Array of table namespublic:CDB();#endifCDB类的实现文件DBsrv.cpp:#include stdafx.h#include .InterfaceDBsrv.h#define new DEBUG_NEW/ Database objectHRESULT CDB:Read(short nTable, short nRow,
25、 LPTSTR lpszData) CStringArray *pTable; pTable=(CStringArray*) m_arrTablesnTable; lstrcpy (lpszData, (*pTable)nRow); return NO_ERROR;HRESULT CDB:Write(short nTable, short nRow, LPCTSTR lpszData) CStringArray *pTable; pTable=(CStringArray*) m_arrTablesnTable; pTable-SetAtGrow(nRow, lpszData); return
26、NO_ERROR;HRESULT CDB:Create(short &nTable, LPCTSTR lpszName) CStringArray *pTable=new CStringArray; nTable=m_arrTables.Add(pTable); m_arrNames.SetAtGrow(nTable, lpszName); return NO_ERROR;HRESULT CDB:Delete(short nTable) CStringArray *pTable; pTable=(CStringArray*) m_arrTablesnTable; delete pTable;
27、m_arrTablesnTable=NULL; m_arrNamesnTable=; if (nTable=m_arrTables.GetSize()-1) m_arrTables.RemoveAt(nTable);m_arrNames.RemoveAt(nTable); return NO_ERROR;客户程序1、创建客户程序,起名为DB。2、添加菜单项: 建表:添加一个名称为“Testing”的表到文档的数据库对象中。 写表:写一个字符串到新表的第一行。 读表: 读出新表第一行的内容并放在CDBDoc:m_csData中,然后 由CDBView将它显示在窗口客户区。3、实现菜单函数4、显示
28、读表内容 pDC-TextOut(10,10,pDoc-m_csData);5、 添加对象代码程序运行效果演示程序运行效果演示将C+对象打包成DLLDB_cppdll要将对象的实现封装成要将对象的实现封装成DLL,必须考虑如下事情:必须考虑如下事情: 成员函数的引出;成员函数的引出; Unicode/ASCII兼容性。兼容性。 引出函数的一个简单方法是用_declspec(dllexport)例如:_declspec(dllexport) int MyFunction(int n);_declspec(dllexport)可用于任何函数,包括类的成员函数,它可以告诉编译器将入口放进引出函数表中
29、。 要引出CDC类中的所有成员函数,只需在每个成员函数之前加上_declspec(dllexport)。Unicode/ASCII兼容兼容 问题的由来:问题的由来: 所有与所有与DB工程相关的例程都可以创建成使用工程相关的例程都可以创建成使用Unicode和使用和使用ASCII。但是有一些函数的参数是字符串类型,以但是有一些函数的参数是字符串类型,以Unicode或或ASCII形式编译,得到的二进制文件将有所不形式编译,得到的二进制文件将有所不同。所以应将所有函数参数标准化为同。所以应将所有函数参数标准化为Unicode,因为因为Unicode是是ASCII的超集。的超集。步骤一:修改接口文件
30、步骤一:修改接口文件#define DEF_EXPORT _declspec(dllexport)class CDB / Interfaces public:DEF_EXPORT Read(short nTable, short nRow, LPWSTR lpszData);DEF_EXPORT Write(short nTable, short nRow, LPCWSTR lpszData);/ Implementation private: CPtrArray m_arrTables; / pointers to CStringArray (the database) CStringArr
31、ay m_arrNames; / Array of table namespublic:CDB(); 为CDB类的每个成员函数添加_declspec(dllexport) 声明。 添加成员函数Release(),该函数在对象不再被使用删除自己; 声明类厂CDBSrvFactory; 声明返回类厂对象的引出函数DllGetClassFactoryObject。步骤二:修改对象程序1、创建DLL工程框架 Win32 Dynamic-Link Library。起名为DB。2、添加对象文件DBSrv.cpp和stdafx.h到工程。3、修改对象实现文件。在CDB对象的实现文件DBSrv.cpp中添加
32、CDB:Release()的实现代码。ULONG CDB:Release() delete this;return 0;4、 实现CDBSrvFactory。新建DBSrvFactory.cpp及对象的引出函数DllGetClassFactoryObject。5、 将参数标准化为Unicode.6、 创建程序。生成引入库文件(.LIB)和动态链接库文件(.DLL)。步骤三:修改客户程序1、修改对象删除方式。将CDBDoc:CDBDoc()中的 delete m_pDB 改成m_pDB-Release()2、 通过类厂创建对象CDB。修改CDBDoc:OnNewDocment()3、 将参数标准
33、化为Unicode。4、 连接DLL。5、 创建客户程序。将DB.DLL拷贝到客户程序所在目录下,编译。运行程序,观看效果运行程序,观看效果过过渡渡问题:私有成员变量被暴露解决方案:抽象基类 即,将“接口”头文件中定义的成员函数定义成虚函数,将成员变量删除。 将CDB改成 IDB,CDBSrvFactory改成IDBSrvFactory。第二步:修改对象程序 增加头文件DBSRVIMP.H 其中包含dbsrv.h 其他部分也做相应修改第三步:修改对象实现文件 将包含的头文件dbsrv.h改成dbsrvimp.h#include stdafx.h#include DBsrvImp.h“HRESU
34、LT CDBSrvFactory:CreateDB(IDB* ppvDBObject) *ppvDBObject=(IDB*) new CDB; return NO_ERROR; ULONG CDBSrvFactory:Release() delete this;return 0; HRESULT DEF_EXPORT DllGetClassFactoryObject(IDBSrvFactory * ppObject) *ppObject=(IDBSrvFactory*) new CDBSrvFactory; return NO_ERROR; 第四步:修改客户程序 将CDBDoc的数据成员类型
35、由CDB*改成IDB*public:IDB *m_pDB; 在CDBDoc:OnNewDocument函数中,将CDBSrvFactory*改成 IDBSrvFactory*第五步: 新生成DLL,拷贝到客户程序下,运行效果和原来一样!改由COM库装载C+对象前面的示例中,前面的示例中,DLL声明了一个入口点声明了一个入口点DllGetClassFactoryObject,客户程序调用客户程序调用此函数可以获得类厂对象,再由类厂创建真正的对象此函数可以获得类厂对象,再由类厂创建真正的对象DB。这样做隐含的问题:这样做隐含的问题:如何在一个如何在一个DLL中实现多个对象(类)。中实现多个对象(类
36、)。解决办法:解决办法: 为每一个准备引出的类提供一个入口点;为每一个准备引出的类提供一个入口点; 给一个标准入口点传递一个额外参数,表明所需要的类。给一个标准入口点传递一个额外参数,表明所需要的类。事实上,事实上,COM正是采用的第二种做法!它使用用统一的类厂获取函数:正是采用的第二种做法!它使用用统一的类厂获取函数:STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void* ppObject)REFCLSID rclsid 是个是个128位的二进制数字标识,做为类标识。位的二进制数字标识,做为类标识。REFIID riid 也是
37、个也是个128位的二进制数字标识,做为类的接口标识。位的二进制数字标识,做为类的接口标识。void* ppObject 是返回的对应类的类厂指针。是返回的对应类的类厂指针。现在的例子实际上只有一个接口,如果一个类中嵌有多个类,那么内部的每一个现在的例子实际上只有一个接口,如果一个类中嵌有多个类,那么内部的每一个类就是一个接口,这就是为什么除了类标识还要有接口标识的原因。类就是一个接口,这就是为什么除了类标识还要有接口标识的原因。第一步:修改接口文件第一步:修改接口文件 在在dbsrv.h中增加中增加类类ID和接口和接口ID的说明的说明 删除删除DllGetClassFactoryObject,
38、为使用统一的为使用统一的DllGetClassObject做准备做准备 第二步:第二步: 修改对象程序修改对象程序 在在dbsrvfact.cpp中定义中定义GUID。在在dbsrvfact.cpp用用DllGetClassObject替换替换DllGetClassFactoryObject注意:注意: DllGetClassObject在系统提供的在系统提供的objbase.h中已经声明所以不用在申明。中已经声明所以不用在申明。第三步:手工注册第三步:手工注册 运行运行Regedit.exe,打开打开HKEY_CLASSES_ROOTCLSID 增加一个子键增加一个子键,名称为名称为30DF
39、3430-0266-11cf-BAA6-00AA003E0EED第四步第四步: 修改客户程序修改客户程序 调用调用COM库函数库函数创建对象创建对象 初始化初始化COM库库 增加增加ID定义定义将C+对象变成COM对象最后 妈呀,总算到头了要将C+对象变成一个真正的COM对象只要实现如下操作: 实现接口的引用计数。 对象容许实现多个接口。 类厂对象使用标准的IClassFactory。 使用_stdcall调用约定(COM对象在Win32下采用的标准调用约定)。 实现DLL动态卸载。 实现对象自注册。比较枯燥的几个重要概念:1、引用计数 ULONG AddRef(); ULONG Releas
40、e();2、多接口 如果每次都通过IID和CoGetClassObject调用接口,系统开销比较大为了方便在一个COM里多个接口之间转换,对象可以提供一个接口查询函数:HRESULT QueryInterface(RIID riid, void * ppObj);3、IUnKnown接口 实现以上三个函数,定义在一个类里。其他接口继承这个类。4、标准类厂接口 IClassFactory,改造CreateDB(IDB *ppObj)为:CreateInstance5、自动注册、自动注册调用调用DllRegisterServer和和DllUnRegisterServer具体操作如下:具体操作如下:
41、第一步:修改第一步:修改接口接口文件文件修改修改IDB删除类厂说明和删除类厂说明和IID_IDBSrvFactory。第二步:修改对象程序第二步:修改对象程序修改原来修改原来类厂类厂有关说明有关说明增加增加QueryInterface、AddRef和和Release三个成员函数三个成员函数实现实现QueryInterface、AddRef和和Release实现实现CreateInstance等。等。编译后,注册编译后,注册DB.DLL(使用使用regsvr32)第三步:修改客户程序第三步:修改客户程序定义定义IID修改对象创建过程修改对象创建过程第一次使用自己的COM组件使用使用ATL开发开发COM组件组件创建并测试组件创建并测试组件