COM技术及Delphi消息机制总结

COM技术

COM((Component Object model))即组件对象模型,是一种以组件为发布单元的对象模型,这种模型使各软件组件可以用一种统一的方式进行交互。Microsoft的许多技术,如ActiveX, DirectX以及OLE等都是基于COM而建立起来的。并且Microsoft的开发人员也大量使用COM组件来定制他们的应用程序及操作系统。

 

组件与对象

组件是一个可重用的模块,它是由一组处理过程、数据封装和用户接口组成的业务对象(Rules Object)。组件看起来像对象,但不符合对象的学术定义。它们的主要区别是:

1)组件可以在另一个称为容器(有时也称为承载者或宿主)的应用程序中使用,也可以作为独立过程使用;

2)组件可以由一个类构成,也可以由多个类组成,或者是一个完整的应用程序;

3)组件为模块重用,而对象为代码重用。

 

ActiveX、OLE和COM

自从 Windows操作系统流行以来,"剪贴板"(Clipboard)首先解决了不同程序间的通信问题(由剪贴板作为数据交换中心,进行复制、粘贴的操作),但是剪贴板传递的都是"死"数据,应用程序开发者得自行编写、解析数据格式的代码,于是动态数据交换Dynamic Data ExchangeDDE)的通信协定应运而生,它可以让应用程序之间自动获取彼此的最新数据,但是,解决彼此之间的"数据格式"转换仍然是程序员沉重的负担。对象的链接与嵌入(Object Linking and EmbeddedOLE)的诞生把原来应用程序的数据交换提高到"对象交换",这样程序间不但获得数据也同样获得彼此的应用程序对象,并且可以直接使用彼此的数据内容。其实OLEMicrosoft复合文档技术,它的最初版本只是瞄准复合文档,但在后续版本OLE2中,导入了COM。由此可见,COM是应OLE的需求而诞生的,所以虽然COMOLE的基础,但OLE的产生却在COM之前。COM的基本出发点是,让某个软件通过一个通用的机构为另一个软件提供服务。COM是应OLE的需求而诞生,但它的第一个使用者却是OLE2,所以COM与复合文档间并没有多大的关系,实际上,后来COM 就作为与复合文档完全无关的技术,开始被广泛应用。ActiveX是指宽松定义的、基于COM的技术集合,而OLE仍然仅指复合文档。当然,ActiveX最核心的技术还是COMActiveXOLE的最大不同在于,OLE针对的是桌面上应用软件和文件之间的集成,而 ActiveX则以提供进一步的网络应用与用户交互为主。所以从时间的角度讲,OLE是最早出现的,然后是COM ActiveX;从体系结构角度讲,OLEActiveX是建立在COM之上的,所以COM是基础;单从名称角度讲,OLEActiveX是两个商标名称,而COM则是一个纯技术名词。

 

COM标准

按照组件化程序设计的思想,复杂应用程序被设计成一些小的,功能单一的组件模块,这些组件模块可以运行在同一台机器上,也可以运行在不同的机器上。为了实现这样的应用软件,组建程序和组建程序之间需要一些极为细致的规范,只有组件程序遵守了这些共同的规范,系统才能正常运行。为此,OMGMicrosoft分别提出了CORBA(Common Object Request Breaker Architecture)COM(Component Object model)标准,目前CORBA模型主要应用于UNIX操作系统平台上,而COM 则主要应用于Microsoft Windows操作系统平台上。

COM对象是建立在二进制可执行代码级的基础上,而C++等语言中的对象是建立在源代码级基础上的,因此COM对象是语言无关的。这一特性使用不同编程语言开发的组件对象进行交互成为可能。在Microsoft Windows系统平台上,COM技术被应用于系统的各个层次,从底层的COM对象管理到上层的应用程序交互都用到了COM标准。COM既提出了组件之间进行交互的规范,也提供了实现交互的环境, 因为组件对象之间交互的规范不依赖于任何特定的语言,所以COM也可以是不同语言协作开发的一种标准。

COM标准中,一个组件程序也被称为一个模块,它可以是一个动态连接库(DLL), 被称为进程内组件,也可以是一个可执行程序(EXE),被称为进程外组件. COM标准包括规范和实现两大部分,规范部分定义了组件和组件之间通信的机制,这些规范不依赖于任何特定的语言和操作系统,只要按照该规范,任何语言都可以使用; COM标准的实现部分是COM库,COM库为COM规范的具体实现提供了一些核心服务。COM库一般不在应用程序层实现,而在操作系统层次上实现,因此一个操作系统只有一个COM库实现。而且COM库的实现必须依赖于具体的系统平台,尤其是系统底层的一些标准。COM库可以保证所有的组件按照统一的方式进行交互操作,而且它使我们在编写COM应用时,可以不用编写为进行COM通信而必需的大量基础代码,而是直接利用COM库提供的API进行编程,从而大大加快了开发的速度。例如,现在COM库的版本都支持远程组件即分布式COM,我们不用编写任何网络或者RPC(remote procedure call)的代码,就可以实现在网络上进行程序之间的通信。

COM模型中,客户请求服务时,只能通过接口进行。每一个接口都由一个128位的全局唯一标识符(GUID,Globally Unique Identifier)来标识。客户通过GUID获得接口的指针,在通过接口指针,客户就可以调用其相应的成员函数。接口是一组逻辑上相关的函数集合,其函数也被称为接口成员函数。COM对象通过接口成员函数为客户提供各种形式的服务。 一般来说,接口是不变的,只要客户期望的接口在组建对象中还存在,它就可以继续使用该接口所提供的服务。COM对象可以支持多个接口,因此对组件对象的升级可通过增加接口的办法实现,这样得到的新接口可以不影响老接口的使用。 与接口类似,COM对象也用一个128GUID来标识,称为CLSID(class identifier,类标识符或类ID) 只要系统中含有这类COM对象的信息,并包括COM对象所在的模块文件(DLLEXE文件)以及COM对象在代码中的入口点,客户程序就可以由此CLSID来创建COM对象。 客户成功创建对象后,它得到的是一个指向对象某个接口的指针,因为COM对象至少实现一个接口,所以客户就可以调用该接口提供的所有服务。

 

Delphi中使用COM

Delphi中使用COM的步骤为:

       (1)根据类型库文件生成声明单元(xxx_TLB.pas)

       (1)创建需要的COM对象或COM接口

       (2)使用COM对象(接口)的属性和方法完成操作

       (3)释放COM对象(COM接口系统会自动释放)

注:一般程序中只适用COM接口和COM类对象中的一种,接口和对象最好不要混用。

Delphi使用COM前最好调用CoInitialize函数进行初始化,COM组件用完后要调用CoUninitialize函数反初始化。因为在DelphiComObj单元的initialization部分有以下一段代码: 

if not IsLibrary then 

begin 

SaveInitProc := InitProc; 

InitProc := @InitComObj; 

end; 

这段代码判断当前程序是否是一个DLL(通过IsLibrary变量来判断)如果不是的话,则将初始化的过程设为InitComObj函数。而DELPHI执行调用CoInitialize函数的操作正是在这个InitComObj函数中调用的,所以Delphi如果在普通DLL中调用COM组件不初始化的话就会报错。

 

Delphi的消息处理

Windows的消息机制

Windows消息是在Windows和你的应用程序之间以及两个应用程序之间通信的关键因素,即使你不处理,Windows消息仍然会被发送到您的应用程序的表单里。例如,当用户关闭应用程序中的时候,WM_CLOSE消息发送到窗口/表单,表单再关闭(如果你没有编程处理这个消息的话),类似讯问软件服务程序可以通过对Windows消息作出相应响应阻止窗口关闭或者Windows系统关机。一个应用程序要接收窗口消息,必须提供一个消息可以被发送到的窗体。在正常情况下,这窗口是应用程序中的主窗体。

Windows的消息系统是由3个部分组成的:

  • 消息队列。Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取消息,然后分派给某个窗口。

  • 消息循环。通过这个循环机制应用程序从消息队列中检索消息,再把它分派给适当的窗口,然后继续从消息队列中检索下一条消息,再分派给适当的窗口,依次进行。

  • 窗口过程。每个窗口都有一个窗口过程来接收传递给窗口的消息,它的任务就是获取消息然后响应它。窗口过程是一个回调函数;处理了一个消息后,它通常要返回一个值给Windows

一个消息从产生到被一个窗口响应,其中有5个步骤:

1) 系统中发生了某个事件。

2) Windows把这个事件翻译为消息,然后把它放到消息队列中。

3) 应用程序从消息队列中接收到这个消息,把它存放在TMsg记录中。

 4) 应用程序把消息传递给一个适当的窗口的窗口过程。

 5) 窗口过程响应这个消息并进行处理。

步骤34构成了应用程序的消息循环。消息循环往往是Windows应用程序的核心,因为消息循环使一个应用程序能够响应外部的事件。消息循环的任务就是从消息队列中检索消息,然后把消息传递给适当的窗口。如果消息队列中没有消息,Windows就允许其他应用程序处理它们的消息。

Windows消息控制中心一般是三层结构,其顶端就是Windows内核。Windows内核维护着一个消息队列,第二级控制中心从这个消息队列中获取属于自己管辖的消息,后做出处理,有些消息直接处理掉,有些还要发送给下一级窗体(Window)或控件(Control)。第二级控制中心一般是各Windows应用程序的Application对象。第三级控制中心就是Windows窗体对象,每一个窗体都有一个默认的窗体过程,这个过程负责处理各种接收到的消息。

    消息是以固定的结构传送给应用程序的,结构如下:

  Public Type MSG

  hwnd As Long

  message As Long

  wParam As Long

  lParam As Long

  time As Long

  pt As POINTAPI

  End Type

 其中hwnd是窗体的句柄,message是一个消息常量,用来表示消息的类型,wParamlParam都是32位的附加信息,具体表示什么内容,要视消息的类型而定,time是消息发送的时间,pt是消息发送时鼠标所在的位置。Windows消息有以下几种类型:

1、标准Windows消息:这种消息以WM_打头。

2、通知消息:通知消息是针对标准Windows控件的消息。这些控个包括:按钮(Button)、组合框(ComboBox)、编辑框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具条(Toolbar)、菜单(Menu)等。每种消息以不同的字符串打头。

3、自定义消息:用户自己定义消息、给自己发送消息和编写消息处理过程。消息常量值范围为WM_USER 100$7FFFWindows为用户自定义消息保留的。 

 

VCL消息处理机制 

Delphi中,大多数情况下Windows的消息被封装在VCL的事件中,我们只需处理相应的VCL事件就可以了。在Delphi应用程序的源代码中有语句Application.Run,它的作用是启动消息循环,然后调用Application.ProcessMessage,该函数会在应用程序的消息队列中查找一条消息。当在消息队列中检索到一条消息后,触发Application.OnMessage事件。这样在Windows本身对消息处理之前,就会响应OnMessage事件的处理过程,它优于任何消息处理,而且只接收登记的消息,即前面所述的由PostMessage发送的消息。响应Application.OnMessage事件的处理过程必须是TmessageEvent类型,其声明如下: . 

type TMessageEvent = procedure (var Msg: TMsg; var Handled: Boolean) of object; 其中TMsgWindows中定义的消息记录,我们可以这样声明: 

Procedure OnMyMessage(var Msg:TMsg;var Handled:Boolean); 

然后把此方法赋给Application.OnMessage事件: Application.OnMessage :=OnMyMessage; OnMessage事件将捕获发送给应用程序的所有消息,这是一个非常繁忙的事件,因此在处理OnMessage事件的处理过程中设置断点进行消息处理是不明智的。

VCL对象用于接收消息的方法叫MainWndProc。它是定义在Twincontrol类中的静态方法,不能被重载。它不直接处理消息,当消息离开MainWndProc后,消息被传递给对象的WndProc方法,WndProc方法是在Tcontrol类中定义的一个虚拟方法,由它调用Dispatch方法。Dispatch根据传入的Message来寻找相应的处理方法,如果最后找不到,就继续向上到父类中寻找消息处理方法,一直到找到为止,如果找不到则调用DefaulthandlerDefaulthandler方法对消息进行最后的处理,然后把消息传递给WindowsDefWindowProc函数或其他默认的窗口过程。

 

Delphi消息的发送: 

Delphi消息的发送有三种方法

1Tcontrol类的Perform对象方法。可以向任何一个窗体或控件发送消息,只需要知道窗体或控件的实例。其声明如下: 

function Tcontrol.Perform(Msg:Cardinal;Wparam,Lparam:Longint):Longint . 

2WindowsAPI函数SendMessage()和Postmessage()。其声明如下: function SendMessagehWnd: HWND; Msg: UINTwParamWPARAM; lParam: LPARAM):LRESULTstdcall 

function SendMessagehWnd: HWND; Msg: UINTwParam: WPARAM; lParamLPARAM):LRESULTstdcall 

PostMessage函数将消息添加到应用程序的消息队列中去。应用程序的消息循环会从消息队列中提取登记的该消息,再发送到相应的窗口中。 SendMessage函数可以越过消息队列直接向窗口过程发送。SendMessagePostMessage的最大区别在于,前者等待消息处理完,后者则仅仅只是投递到消息队列。所以通常建议的方式是在具有同步需要的时候,采用SendMessage,而这个时候,SendMessage的后面两个参数wParam,lParam就可以用来传送结构数据的指针,从而等到指定的消息处理线程,退出(投递失败),或者处理完成之后可以继续对wParamlParam参数进行相应的处理。根据这样的处理机制,就可以由接收消息的线程对wParamlParam所携带的数据指针的内容进行修改(前提是双两结构透明或一致),从而发送消息者也可以对修改后的内容进行进一步处理。而Perform从本质上和SendMessage相似,它们直接向窗口过程发送。SendMessagePostmessage函数只需要知道窗口的句柄就可以发送消息,所以它们可以向非Delphi窗体发送一条消息,但而Perform必须知道窗体或控件的实例。 

 

原文地址:https://www.cnblogs.com/doit8791/p/2497610.html