COM组件技术

★COM理论知识
COM是Component Object Model (组件对象模型)的缩写。


Windows使用DLLs在二进制级共享代码。这也是Windows程序运行的关键——重用kernel32.dll, user32.dll等。但DLLs是针对C接口而写的,它们只能被C或理解C调用规范的语言使用。由编程语言来负责实现共享代码,而不是由DLLs本身。这样的话DLLs的使用受到限制。COM不是Win32特有的,从理论上讲,它可以被移植到Unix或其它操作系统。但是我好像还从来没有在Windows以外的地方听说过COM。


COM是一种跨应用和语言共享二进制代码的方法。源码级重用虽然好,但只能用于C++,它还带来了名字冲突的可能性,更不用说不断拷贝重用代码而导致工程膨胀和臃肿。COM通过定义二进制标准解决了这些问题,即COM明确指出二进制模块(DLLs和EXEs)必须被编译成与指定的结构匹配。这个标准也确切规定了在内存中如何组织COM对象。COM定义的二进制标准还必须独立于任何编程语言(如C++中的命名修饰)。一旦满足了这些条件,就可以轻松地从任何编程语言中存取这些模块。由编译器负责所产生的二进制代码与标准兼容。这样使后来的人就能更容易地使用这些二进制代码。


COM+更像是对DLL监管器的改进与增强,它随操作系统一起启动,具有适时激活对象,对象池,负载平衡,事件服务,细粒度安全机制等新的增强功能。它是一种属性编程的模式,不需要编码,而只需设置适当的选项就可以获得安全性,负载等功能。它的竞争对手是EJB,并且EJB是跨平台的。


控件的最大好处是可以重复使用,甚至可以在不同的编程语言之间使用,例如你可以在VB中嵌入用VC开发的控件。控件的最早形式是以.VBX的格式出现的,后来变成了.OCX。由于Internet的广泛流行,微软公司推出了ActiveX技术,就是从OLE发展起来的,加入了WWW上的功能。所以目前最流行的是ActiveX控件。


IDL接口定义语言是用来定义COM类相关的内容。在该文件中,所以的IDL语法都用[]括起来。该文件使用MIDL来编译,例如,名为MyInterface.idl的文件在编译后产生如下文件:
1.MyInterface.h定义c++抽象类的地方,即接口的c++版本定义;
2.MyInterface_i.c包含GUID,即IIDs和CLSIDs的定义;
3.MyInterface_p.c,dlldata.c这两个附属文件用于创建代理/桩子DLL,一般不需要使用;
4.MyInterface.tlb即类型库,是一个2进制文件,用于定义MyInterface.h中的有关信息;
IDL文件中类型 C++中的类型
不同的
boolean bool
byte BYTE
(unsigned) hyper (unsigned) hyper 64位带符号数
Enum Enum 用法不同,IDL需要在IDL文件的开头定义这个类型
CY CY 钱币(8字节)
DATE DATE 日期(double)
相同的
char char
(unsigned) short (unsigned) short
(unsigned) long (unsigned) long
float float
double double


COM概念:
UUID:普通唯一标识符(Universally Unique Identifier),主要用于标识RPC(远程过程调用)通信的双方. 它是一组128位的数,格式为rrrrrrrr-dddd-dddd-oooo-aa-aa-aa-aa-aa-aa,其中r为随机数,d标记对象开始运行的时间,o与重启的次数有关,a一般是网卡的地址。
GUID:全局唯一标识符(Globally Unique Identifier),分配给COM对象的标识符。它是微软使用的一个术语并广泛应用于微软的产品中。GUID是微软公司对UUID的解释,具有一致性。
类ID:CLSID,CoCreateInstance()的参数,是一个用来标识DLL/EXE文件的真实名称的GUID。
接口ID:IID,CoCreateInstance()的参数,是一个GUID,用来指示所要创建的对象的类,该类会实现若干个接口。
接口:是一个没有数据成员的纯虚类,即一个包含了若干可调用函数地址的数组。
属性:即成员变量,只能定义在实现COM接口的那个类中,并只允许用get()和set()方法访问。
ROT:运行对象表
DLL监管器:如果其他机器上有一个DLL来提供COM对象时,可以通过一个友好的EXE来装载该DLL并为COM对象和客户之间传递消息。
IDL:接口定义语言。
ODL:是(Object Definition Language)是MFC专用的用来描述COM组件的。
TLB:类型库。客户程序在编译,运行或其他任何时候都可以使用类型库来确定这个COM类有哪些方法已经方法需要那些参数等问题。
在进程中:COM DLL被称为“在进程中”,COM EXE被称为“在进程外”。
本地服务器:在本地系统执行的COM EXE称为“本地服务器”,在别的系统里执行的COM EXE称为“远程服务器”。
早绑定:如果客户程序能访问IDL的信息,尤其是类类型的重载,那么编译器就会把客户程序中调用方法的地方都填好地址,这种技术叫做“绑定”或“早绑定”。VB和VJ++只支持晚绑定接口,只有MFC和ATL支持早绑定接口。
晚绑定:如果编译时不知道COM对象的情况,也可以在以后写COM对象时再建立所需的功能,则已经做好准备的客户程序在运行过程中查询COM对象,找到它所支持的方法和所需要的参数,这种技术称为“晚绑定”。
Singleton:一个COM类只创建一个对象,并总是返回这个对象的指针,这样的COM类叫做一个Singleton。
单用类:对于COM EXE,如果每次创建对象时都需要启动一个新的EXE,则这样的类称为“单用类”,如果不需要启动一个新的EXE,则这样的类称为“多用类”。当应用程序第一次启动时,要在API调用中指定是单用类还是多用类。
线程安全性:是指在多任务环境中不允许有两个或两个以上线程在同一时刻写同一数据区。可以让COM保证你的对象线程安全,而不必自己去实现这个功能。
IUnknown:所有的COM对象都继承了IUnkown,这样所有的COM对象都提供了相同的受限函数集,且有多态性。它有三个方法:Release(),AddRef()和QueryInterface()。
IDispatch:是用于晚绑定的接口,它提供的主要的方法有:GetTypeInfo()和GetTypeInfoCount()用于查询一个支持晚绑定的COM对象的所有方法和函数;GetIDsOfNames()用于寻找与给定方法名的列表相匹配的ID;Invoke()用于通过方法ID和参数表来真正调用这个方法。
二元接口:二元接口允许COM对象同时支持早绑定和晚绑定。它的IDL文件中既有早绑定的接口ID和接口设计;又有晚绑定的方法ID。实现上,IDispatch的方法在一个固定位置,而你自己的方法则在任意位置。
interface:就是指一般的COM接口,可以是从IUnknown继承(就是Custom接口)也可以从IDispatch继承(就是dual接口,因为IDispatch也是从IUnknown继承的)。
dispinterface:是一种纯粹的自动化接口,可以简单的就把它看作是IDispatch接口   (虽然它实际上不是的),这种接口就只能通过自动化的方式来调用,COM组件的事件一般都用的是这种形式的接口。
VTBL:虚表。对于一个支持两种绑定类型的COM服务器,VTBL的前三项是IUnkown的方法,接着是IDispatch的四个方法,然后才是程序员自定义的方法。
连接点和接收器:一个或多个客户程序给服务器一个回呼地址,当有事情发生时服务器调用这些地址。这个被服务器调用的、位于客户端的地址就叫做接收器,而可以完成这个功能的服务器叫做连接点。
ActiveX事件:类似于连接点和接收器,都是允许服务器回呼客户端。但ActiveX不是直接调用客户程序的方法,而是向客户端发送一个窗口消息。
封装和聚合:是用来解决COM运行时刻继承机制的方案。两者都在“派生”的COM类中创建一个COM“基类”的实例,封装是由派生类提供接口来间接访问基类,聚合是由派生类的QueryInterface()函数来提供基类的接口,然后可以直接访问基类的方法。


★COM编程知识
一、使用ATL创建COM组件
选择菜单“插入->ATL对象”来插入新的COM类对象。
在类视图中对接口名点右键可以选择添加方法或属性。对于接口的方法需要在IDL文件中手动加入IDL语法。
产生GUID的方法:运行Visual Studio工具GUIDGEN.EXE




二、使用MFC编写ActiveX控件(原理简述)






三、使用COM API来访问COM对象(原理简述)
1.在工程中include待创建COM服务器的GUID标识符
需要把CLSID和IID整理到一个guids.h的头文件中供工程使用。对于ATL创建的COM类,可以在xxx_i.c中找到这些类ID和接口ID。
2.初始化COM库
在使用COM DLL前你要通知它,比如ole32.dll。只需要在程序开始时调用一次::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);或者::CoCreateInstance(NULL);,但每次创建新的线程还要在开始时执行调用。
3.include COM的定义文件
具体包括guids.h和COM类的.h文件(也是由MIDL生成的),<objbase.h>
4.创建COM对象
::CoCreateInstance(CLSID_myComObj,NULL,CLSCTX_ALL,IID_ImyComObj,(LPVOID*)&pImyComObj);其中的参数意义如下:CLSID_myComObj是创建的COM对象的CLSID;NULL表明此对象不是聚合式对象一部分。如果不是NULL, 则指针指向一个聚合式对象的IUnknown接口;CLSCTX_ALL表示组件类别;IID_ImyComObj是创建的COM对象上的接口的IID;pImyComObj是用来接收指向Com对象接口地址的指针变量。
一次创建多个COM对象使用::CoCtreateInstanceEx()函数。
::CoCreateInstance()和::CoCreateInstanceEx()在内部都调用了::CoGetObject()函数,如果由于调试等目的,可以直接调用该函数。
调用时如同普通对象pImyComObj->MyFun(1234);
5.消灭COM对象
使用如下语句:pImyComObj->Release();
6.关于接口的引用计数
对于接口的引用计数有较复杂的规则。简单来说,当吧对象的指针传给别的函数使用时,应调用pImyComObj->AddRef();使相应的接口引用计数加1。




四、使用智能指针来访问COM对象
智能指针概述:#import编译指令为你自动把GUID添加到工程中,而且那些API调用也封装在了#import指令创建的制定类中。这些制定类不仅使创建对象的工作变得更简单,而且可以用来确认COM对象最后被销毁,因为在它的解构函数中调用了Release()方法。
1.初始化COM库
::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED); //
2.装入COM类定义
#import "\xx\altcom.tlb" no_namespace //该行引用创建COM组件时所生成的.tlb文件,加于使用智能指针的文件的头部
#import "\xx\altcom.tlb" no_namespace //no_namespace当装入的所有变量与源文件已有的定义没有冲突时使用,这样就不必每次都使用范围操作符。默认是要使用IDL文件中声明的库的名字作为范围。类似的,raw_interfaces_only参数可以避免编译器在每次调用COM对象的方法时在前后加上额外的错误处理代码。
3.创建COM对象
ImyComObjPtr pPtr; //创建接口名为ImyComObj的接口的智能指针,也可以使用__uuidof(myComObj)为参数来构造对象
HRESULT hr=pPtr.CreateInstance(__uuidof(myComObj)); //调用智能指针的方法创建对象,myComObj为IDL文件中定义的类名
if(FAILED(hr)) //对于COM函数的返回值HRESULT参数只能使用FAILED()或SUCCEEDED()来判断
{
_com_error err(hr); //捕捉出错信息
AfxMessageBox(err.ErrorMessage());
return;
}
pPtr->MyFun(1234); //使用智能指针直接调用接口ImyComObj中的MyFun()方法
//注意:调用接口的方法使用->,调用包装器ImyComObjPtr的方法使用.
3.消灭COM对象
可以像对待普通对象一样对待pPtr。又如:
ImyComObjPtr *pPtr=new ImyComObjPtr(__uuidof(myComObj)); //使用动态方式创建智能指针
*(pPtr)->MyFun(1234); //使用智能指针
delete pPtr; //像普通类对象一样销毁







阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhiyuan411/article/details/6889220
个人分类: 学习笔记 C系技术
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭