COM技术内幕

COM技术内幕》

1章 组件

 

1COM,即组件对象模型,是关于如何建立组件以及如何通过组件建构应用程序的一个规范

2、组件的优点:应用程序可随时间的流逝而发展变化;定制应用程序;组件库;分布式组件。

3、对组件的需求:组件必须动态连接;必须隐藏其内部实现细节。

4COM组件是以Win32动态链接库(DLLs)或可执行文件(EXEs)的形式发布的可执行代码组成的。遵循COM规范编写的组件将能够满足对组件家够的所有需求。COM组件是动态链接的,COM使用DLL将组件动态链接起来。对于COM组件的封装是很容易的。COM组件按照一种标准的方式来宣布他们的存在。COM组件是一种给其他应用程序提供面向对象的API或服务的极好方法。

5COM并不是一种计算机语言。

6、将COMDLL相提并论是不合适的。实际上COM使用了DLL来给组件提供动态链接的能力。

7COM并不是像Win32API那样的函数集,它更主要的是一种编写能够按面向对象API形式提供服务的组件的方法。

8COM并不是类似于MFC这样的C++类库。COM给开发人员提供的是一种开发与语言无关的组件库的方法,但COM本身并没有提供任何实现

9COM具有一个被称作是COM库的API,它提供的是对所有客户及组件都非常有用的组件管理服务。

 

2章 接口

 

1、在COM中接口就是一切。
1)接口可以保护系统免首外界变化的影响。
2)接口可以使客户用同样的方式来处理不同的组件。

2、(1COM接口在C++中是用纯抽象基类实现的。

2)一个COM组件可以提供多个接口。

3)一个C++类可以使用多继承来实现一个可以提供多个接口的组件。

3、类并非组件。

4接口并非总是继承的。对接口的继承只不过是一种实现细节而已。除了可以使用一个类来实现几个不同的接口外,还可以用单个的类来实现每一个接口再使用指向这些类的指针。
5
、组件可以支持任意数目的接口。为支持多重接口,可以使用多重继承。支持多重接口的组件可以被看作是接口的集合。

6COM接口的不变性、多态以及接口继承。
1)一旦公布了一个接口,那么它将永远保持不变。当对组件进行升级时,一般不会修改已有的接口,而是加入一些新的接口。
2)多态指的是可以按同一种方式来处理不同的对象。

7、虚拟函数表(vtbl):包含一组指向虚拟函数实现的指针。
  
定义一个纯抽象基类也就是定义了相应的内存结构。但此内存只是在派生类中实现此抽象基类时才会被分配。当派生类继承一个抽象基类时,它将继承此内存结构。

8COM中,对一个组件的访问只能通过函数完成,而绝不能直接通过变量。

 

9、接口的真正的威力在于继承此接口的所有类均可以被客户按同一方式进行处理。



3 QueryInterface函数

1、接口查询:

客户同组件的交互都是通过一个接口完成的。在客户查询组件的其他接口时,也是通过接口完成的。这个接口就是IUnknown
IUnknown
接口的定义包含在Win32 SDK中的UNKNOWN.H头文件中。
interface IUnknown
{
    virtual HRESULT _stdcall QueryInterface(const IID& iid,void **ppv) = 0;
    virtual ULONG _stdcall AddRef() = 0;
    virtual ULONG _stdcall Release() = 0;
}
   
IUnknown中定义了一个名为QueryInterface的函数。客户可以调用QueryInterface来决定组件

是否支持某个特定的接口。

2、所有的COM接口都需要继承IUnknown

3、由于所有的COM接口都继承了IUnknown,每个接口的vtbl中的前三个函数都是

QueryInterface, AddRefRelease。若某个接口的vtbl中的前三个函数不是这三个,那么它将不是一个COM接口。由于所有的接口都是从IUnknown 继承的,因此所有的接口都支持QueryInterface.因此组件的任何一个接口都可以被客户用来获取它所支持的其他接口。

4、非虚拟继承:注意IUnknown并不是虚拟基类,所以COM接口并不能按虚拟方式继承IUnknown这是由于会导致与COM不兼容的vtbl。若COM接口按虚拟方式继承IUnknown,那么COM接口的vtbl中的头三个函数指向的将不是IUnknown的三个成员函数。

5、一个QuertyInterface可以用一个简单的if-then-else语句实现,但case语句是无法用的,因为接口标识符是一个结构而不是一个数。

6、多重类型及类型转换

7QueryInterface的规则
1QueryInterface返回的总是同一IUnknown指针。
2)若客户曾经获取过某个接口,那么它将总能获取此接口。
3)客户可以再次获取已经拥有的接口。
4)客户可以从任何接口返回到起始接口。
5)若能够从某个借口获取某特定接口,那么可以从任意接口都将可以获取此接口。

8、接口的IID决定了它的版本。当改变了下列条件中的任何一个时,就应给新接口指定新的ID
1)接口中函数的数目。
2)接口中函数的是顺序。
3)某个函数的参数。
4)某个函数参数的顺序。
5)某个函数参数的类型。
6)函数可能的返回值。
7)函数参数的含义。
8)接口中函数的含义。

9、避免违反隐含和约:
1)使接口不论在其成员函数怎么被调用都能正常工作。
2)强制客户按一定的方式来使用此接口并在文档中将这一点说明清楚。

 

//

4章 引用计数

 

1、生命期控制
IUnknown
的另外两个成员函数AddRefRelease的作用就是给客户提供一种让它指示何时处理完一个接口的手段。

2AddRefRelease实现的是一种名为引用计数的内存管理技术。

引用计数是使组件能够自己将自己删除的最简单同时也是效率最高的方法。
COM
组件将维护一个称做是引用计数的数值。当客户从组件取得一个接口时,此引用计数值将增1。当客户使用完某个接口后,组件的引用计数值将减1。当引用计数值为0时,组件即可将自己从内存中删除。

3、正确使用引用计数规则:
1)在返回之前调用AddRef。对于那些返回接口指针的函数,在返回之前应用相应的指针调用AddRef。这些函数包括QueryInterfaceCreateInstance。这样当客户从这种函数得到一个接口后,它将无需调用AddRef

2)在使用完接口之后调用Release。在使用完某个接口之后应调用此接口的Release函数。

3)在赋值之后调用AddRef。在将一个接口指针赋给另外一个接口指针时,应调用AddRef。换句话说,在建立接口的另外一个引用之后应增加相应组件的引用计数。

4、在客户看来,引用计数是处于接口级上而不是组件级上的。

5、为什么选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数?(1)使程序调试更为方便;(2)支持资源的按需获取。

6AddRef&Release的例子
ULONG _stdcall AddRef()
{
   return InterlockedIncrement(&m_cRef);
}
ULONG _stdcall Release()
{
   if(InterlockedDecrement(&m_cRef)
   {
       delete this;
       return 0;
   }
   return m_cRef;
}

7、当建立一个新组件时,应建立一个对此组件的引用。因此创建组件时,在将指针返回给客户之前,应该增大组件的引用计数值。这使程序员可以不必在调用CreateInstance

QueryInterface之后记着去调用AddRef

8、引用计数规则优化:
1)输出参数规则:任何在输出参数中或作返回值返回一个新的接口指针的函数必须对此接口指针调用AddRef
2)输入参数规则:对传入函数的接口指针,无需调用AddRefRelease,这是因为函数的生命期嵌套在调用者的生命周期内。
3)输入-输出函数规则:对于用输入-输出参数传递进来的接口指针,必须在给它赋另外一个接口指针之前调用其Release。在函数返回之前,还必须对输出参数中所保存的接口指针调用AddRef。如:
void ExchangeForCachedPtr( int i, IX **ppIX)
{
    (*ppIX)->Fx();      //Do something with in-parameter.
    (*ppIX)->Release(); //Release in parameter.
     *ppIX = g_Cache[i];//Get cached pointer.
     (*ppIX)->AddRef();//AddRef pointer.
     (*ppIX)->Fx();//Do something with out-parameter.
}
4)局部变量规则:对于局部复制的接口指针,由于它们只是在函数的生命周期内才存在,因此无需调用AddRefRelease
5)全局变量规则:对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。由于此变量是全局的,因此任何函数都可以通过调用其Release来终止其生命期。对于保存在成员变量中的接口指针,也应按此种方式进行处理。因为类中的任何成员函数都可以改变次中接口指针的状态。
6)不能确定时的规则:对于任何不能确定的情形,都应调用AddRefRelease对。

5章 动态链接

 

1DLL中输出函数:用extern "c"标记

2、在使用VC时,可以用DUMPBIN.EXE来得到某个DLL中所输出的符号的清单。如下面的命令:

dumpbin -exports Cmpnt1.dll

3、装载DLLLoadLibrary以被装载的DLL的名称作为参数并返回一个指向所装载的DLL的句柄。win32GetProcAddress函数可以使用此句柄以及待用的函数的名称,然后返回一个指向次函数的指针。

4使用DLL实现组件的原因:DLL可以共享它们所链入的应用程序的地址空间

//

 

6章 关于HRESULTGUID、注册表及其他细节

 

1HRESULT值的结构:
_________________________________________________________
||| |||15bits
设备代码|16bits返回代码|
|__|_________________________|______________________________|31 30 16  15                           0                   


2
、常用的HRESULT值:

3、一般不能直接将HRESULT值同某个成功代码(如S_OK)进行比较以决定某个函数是否成功也不能直接将其同某个失败代码(如E_FAIL)进行比较以决定函数调用是否失败。应该使用SECCEEDEDFAILED宏。
    HRESULT hr = CoCreateInstance(...);
    if(FAILED(hr))
    return ;
    hr = pI->QueryInterface(...);
    if(SUCCEEDED(hr))
    {
        pIX->Fx();
        pIX->Release();
    }
    pI->Release();

 

4、当前所定义的设备代码:
——————————————————————————————————
FACILITY_WINDOWS  8
FACILITY_STORAGE  3
FACILITY_SSPI     9
FACILITY_RPC      1
FACILITY_WIN32    7
FACILITY_CONTROL  10
FACILITY_NULL     0
FACILITY_ITF      4
FACILITY_DISPATCH 2
FACILITY_CERT     11
——————————————————————————————————

 

5、关于定义自己的HRESULT的一些一般性规则:
1)不要将0X0000IX01FF范围内的值作为返回代码。这些值是为COM所定义的FACILITY_ITF代码而保留的。只有遵循这一规则,才不致使用户自己定义的代码同COM所定义的代码相混淆。
2)不要传播FACILITY_ITF错误代码。
3)尽可能使用通用的COM成功及失败代码。
4)避免定义自己的HRESULT,而可以在函数中使用一个输出参数。

6、用MAKE_HRESULT宏来定义一个HRESULT值,此宏可根据所提供的严重级别、设备代码及返回代码生成一个HRESULT值。如:
MAKE_HRESULT
SEVERITY_ERRORFACILITY_ITF100);

7GUID是英文Globally Unique Identifier(全局唯一标识符)的首字母缩写。IID是一个128比特(16)字节的一个GUID结构。

8、生成GUID UUIDGEN.EXEGUIDGEN.EXE

9GUID的比较:操作符==;等价函数IsEqualGUID,IsEqualIID,IsEqualCLSID

10、将GUID作为组件标识符

11、由于一个GUID值占用了16个字节,因此一般不用值传递GUID参数。而大量使用的是按引用传递。


12
COM只使用了注册表的一个分支:HKEY_CLASSES_ROOT

13、注册表CLSID是一个具有如下格式的串:
{EBD25FD9-DD8E-478c-BA25-F1EEF7B32FBF}

14CLSID关键字的子关键字InprocServer32关键字的缺省值是组件所在的DLL文件名称。

15、一些特殊关键字:
1AppID:此关键字下的子关键字的作用是将某个APPID(应用程序ID)隐射成某个远程服务器名称。分布式COM将用到此关键字。
2)组见类别:注册表的这一分支可以将CATID(组件类别ID)映射成某个特定的组件类别。
3Interface:用于将IID映射成与某个接口相关的信息。
4Licenses:保存授权使用COM组件的一些许可信息。
5TypeLib:类型库关键字所保存的是关于接口成员函数所用参数的信息等。

16ProgID命名约定:
<Program>.<component>.<version>

17、从ProgIDCLSID的转换:COM库函数

CLSIDFromProgIDProgIdFromCLSID:

CLSID clsid;
    CLSIDFromProgID("****.****.****",&clsid);

18自注册DLL一定要输出下边两个函数:

STDAPI DllRegisterServer();

STDAPI DllUnregisterServer();

用户可以使用程序REGSVR32.EXE来注册某个组件,它实际上是通过上述函数来完成组件的注册的。

 

19、组件类别使开发人员能够使开发人员无需创建组件实例就能决定它是否所需接口。一个组件类别实际上就是一个接口集合,该集合将被分配给一个GUID,此GUID此时被称做是CATID。对于某个组件而言,若它实现了某个组件类别的所有接口,那么它可以将其注册成该组件类别的一个成员。这样,客户就能够通过从注册表中选择只属于某个特定组件类别的组件而准确找到它所需的组件。


20
、组件类别的用途:指定某个组件必须实现的接口集合;用于指定组件需要其客户提供的接口集合。


22
、在使用COM库中的其他函数(除CoBuildVersion外,此函数将返回COM库的版本号)之前,进程必须先调用CoInitialize来初始化COM库函数。当进程不再需要使用COM库函数时,必须调用CoUninitialize对每一个进程,COM库函数只需初始化一次。这并不是说不能多次调用CoInitailize,但需保证每一个CoInitialize都有一个相应的CoUnoinitialize调用。当进程已经调用过CoInitialize后,再次调用此函数所得到的返回值将是S_FALSE而不再是S_OK.

23OLE是建立在COM基础之上的,它增加了对类型库、剪贴板、拖放、ActiveX文档、自动化以及ActiveX控件的支持。在OLE库中包含对这些特性的额外的支持。在需要使用这些特性时,应调用OleInitailizeOleUninitialize,而不是CoInitailize CoUninitializeOle*函数将调用Co*函数。但若程序中没有用到那些额外的功能,使用Ole*将会造成资源的浪费


24
COM中分配和释放内存的标准方法:任务内存分配器。使用此分配器,组件可以给客户提供一块可以由客户删除的内存。可在多线程应用程序中使用。
一些方便的函数:
void  *CoTaskMemAlloc(
       ULONG cb//size in bytes of block to be allocated
);


void  CoTaskMemFree(
      void *pv//pointer to memory block to be freed
);

25StringFromGUID2可以将某个GUID转换成一个字符串:
wchar_t  szCLSID[39];
int r  = ::StringFromGUID2(CLSID_Component1,szCLSID,39);
 
传给StringFromGUID2的参数是一个Unicode串(即一个宽字符wchar_t类型的数组而不是char类型的字符数组)。在非Unicode的系统中,需要将结果转化为单字节字符(char)。为此,可以使用ANSIwcstombs函数如下:
#ifndef _UNICODE
char szCLSID_single[39];
wcstombs(szCLSID_single,szCLSID,39);
#end if

26、几个转换函数

函数

用途

StringFromCLSID

CLSID转化成文本串

StringFromIID

IID转化成文本串

StringFromGUID2

GUID转化成文本串。此串将被存放在调用者所分配的缓冲区中

CLSIDFromString

将一个文本串转化成CLSID

IIDFromString

将一个文本串转化成IID



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值