什么是com接口
按照前文所说的,你不用去释放COM对象,需要做的仅仅是告诉它们你使用完了。每个COM对象实现的IUnknown接口都有一个Release()方法。你应该调用这个方法通知COM对象你不再需要它了。一旦调用了Release(),COM对象就从内存中消失,因此也就不能再使用接口指针了。
如果你的应用程序使用许多不同的COM对象,那么当你使用完接口之后调用Release()就显得极为重要。如果你不释放(release)接口,COM对象(还有包括代码的那些DLLs)将被保存在内存中,并且毫无必要的加入到你的应用程序中。如果应用程序要运行很长一段时间,在程序闲时,你应该调用CoFreeUnusedLibraries()函数。这个函数将卸载没有显著作用的COM服务器,这也能减少应用程序的内存使用量。
继续上面的示例,下面展示应该如何使用Release():
// Create COM object as above. Then...
if ( SUCCEEDED ( hr ) )
...
// Call methods using pISL here.
// Tell the COM object that we're done with it.
pISL->Release();
IUnknown接口将在下一部分详细说明。
基本接口 - IUnknown
每个COM接口都是从IUnknown继承而来。这个名字有点容易让人误解,因为实际它并不是一个未知(unknown)接口。这个名字意味着即使你有了一个指向COM对象的IUnknown指针,你也不会知道它下面的对象是什么,因为每个COM对象都实现了IUnknown。
IUnknown 有三个方法:
1. AddRef() - 告知COM对象增加它的引用计数。如果你拷贝了一个接口指针,你就需要使用这个方法,无论原始指针还是拷贝的副本都需要使用。在本文中,我们不必使用AddRef()方法。
2. Release() - 告知COM对象减少它的引用计数。你可以从前面的代码片段中找到关于Release()的说明。
3. QueryInterface() - 从COM对象中获取一个接口指针。当COClass实现二个或二个以上接口的时候,需要使用这个方法。
我们已经了解Release()是怎样运作的,那么QueryInterface()又是怎样的呢?当你用CoCreateInstance()创建一个COM对象的时候,你将得到一个接口指针。如果COM对象实现了二个或二个以上的接口(不包括IUnknown),你可以使用QueryInterface()来获取任意你想要的额外指针。QueryInterface()的原型如下:
HRESULT IUnknown::QueryInterface (
REFIID iid,
void** ppv );
参数如下:
iid
所请求接口的IID
ppv
接口指针的地址。如果调用成功,QueryInterface()则通过这个参数返回接口。
让我们继续那个快捷方式的的示例。生成快捷方式的COClass实现了IShellLink和IPersistFile接口。如果你已经有个一个IShellLink指针pISL,那面你可以像下面一样来从COM对象中获取IPersistFIle接口:
HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) pIPF );
之后你可以用SUCCEEDED宏测试hr,然后证明QueryInterface()是否成功运行了。如果成功,你就可以像使用其它接口一样使用新的接口指针pIPF了。当然,在你使用完毕后,一定要调用pIPF->Release()。
在讨论COM以前,我们得认识到一个事实,编写软件实际上是一个非常耗费时间和金钱的活动,所以人们不断寻找方法以减少这些花费,一个很重要的就是“软件重用”。在一个理想的环境下,我们应该能够编写一次代码,在任何地方都可以运行,即使这个环境编写者都没有想到过。当一个程序员修改了自己发布给别人使用的函数功能后,使用者应该不需要改变或者重新编译程序就可以使用这个功能。
早期的努力是使用类库,这个工作在C++中比较常见,但是这种做法是有很大缺陷的,要共享C++的二进制代码是非常困难的。为了解决这个问题,程序员们试图建立一种标准去达到软件在二进制级别上的共用。
Components Object Model (COM) 是软件组件互相通讯的一种方式。它是一种二进制和网络标准,允许任意两个组件互相通讯,而不管它们是在什么计算机上运行(只要计算机是相连的),不管各计算机运行的是什么操作系统(只要该系统支持 COM),也不管该组件是用什么语言编写的。COM 还提供了位置透明性:当您编写组件时,其他组件是进程内 DLL、本地 EXE 还是位于其他计算机上的组件,对您而言都无所谓。COM 是基于对象的——但是这种对象概念与您熟悉的 C++ 或 Visual Basic 中的对象不太一样。
首先,COM 对象被很好地封装起来。您无法访问对象的内部实现细节;您无法知道对象使用了什么数据结构。实际上,对象的封装是如此的严密,以致于 COM 对象通常被描绘为盒子。细节是不会告诉你的,但是我们可以通过接口来访问COM对象里面的方法,当然如果COM组件提供商肯告诉你那些接口中的函数和属性起什么作用的话。
概括地说,COM具有如下一些优越性:
编程技术难度和工作量降低,开发周期变短,开发成本降低。一般编程人员只须根据应用功能要求选用合适的组件,而不必事无巨细都自己动手去完成。组件模块将编程的技术难度和工作量在人员个体和时间上进行了分摊。我们使用ESRI的COM组件编写程序就属于这一级别。
实现分层次的编程,从而促进了软件的专业化生产。专业人员可以开发出具有很强专业性的软件组件,这样既保证了普通的编程应用人员能够完成所需要的应用开发,又不至于降低使用的性能。应用人员不便实现的组件模块可以让专业人员定做。ESRI的程序员们使用C语言为我们辨析了一个个COM组件给我们使用。
软件的复用率提高,使软件的使用效率得到提高并延长了使用寿命。组件编程体系使大量的编程问题局部化了,使软件的更新和维护变得快速和容易,软件的成本大大降低。新的函数功能如果在接口没有改变的情况下很容易使用。
谈了这么多COM的好处,我们该讲点技术型的东西了,我们在AO编程中需要那些COM知识呢?
1. COM不是接口,也不是对象,它是一种标准。
2. 符合COM标准的对象就是我们要谈论的重点——COM对象。其实COM对象也无非是实现了很多接口的对象而已。
3. COM对象必须实现Iunknown接口,这个接口是管理COM对象生命周期的,当COM对象不使用的时候,是这个接口定义的方法负责释放内存。一个COM对象可以没有任何别的接口,但是这个必须要,它是默认实现的接口。
4. QI,即所谓查询接口。由于COM对象有很多个接口,不同的接口管理着COM的不同类型的方法,因此从一个接口可以使用的方法转到另一个接口可以使用的方法的过程称为QI,这个过程是由Idispatch接口管理的。
5. GUIDs 每个组件都有一个独一无二的标识,这就是所谓的广泛唯一标识符。这个标识符就是COM组件的身份,它是一个128bits的数字,由系统自由分配,不要担心这个标识会有重复的一天。如果我们每秒产生1000万个UID,那么到5770年才可能遇到重复。别告诉我那个时候我们还使用WINDOWS的玩意。
6. 一个COM对象可以有多个接口,一个接口也完全可以被多个COM对象实现。
7. 接口分为两种,内置接口和外置接口。前一种定义的是COM对象的方法和属性,用implements实现,COM对象必须实现所有的接口内容;后一种定义的是COM对象的事件,用withEvents实现,这种接口在实现的时候不必实现所有的内容。
8. COM组件必须被注册后才能使用,它得到注册表那里去登记“户口”。
COM组件很不错,可是它也有致命的缺陷,这个缺陷就来自它本身。我们知道,COM是可以被重用的,COM对象的实现过程也可以被修改升级(定义是不能修改的哦),如果两个程序都使用一个COM对象,而这个COM组件升级了的话,很可能就出现某个程序无法使用新组件的情况,这就被称为“DLL HELL(DLL灾难)”,我们有时候安装了新软件后很多别的软件都无法使用,很多原因就是因为这个DLL HELL。别以为这是个小问题,这可是人家微软提出.NET平台的一个主要原因。