COM学习笔记 Delphi COM深入编程书签
所有的GUID存在注册表 HKEY_CLASSES_ROOT / CLSID下面,而GUID的ProgID键指定了可读的名,该名可以用于自动化目的的接口 在delphi中,所有的接口都是由Iunkonwn直接或间接继承 接口中所有的方法都是public的 接口不能声明变量 接口声明的所有函数和过程,都是virtual的 CoCreateGUID产生一个新的GUID QueryInterface的作用:如果当前的这个类支持(继承自)IID代表的接口,那么新生成一个obj变量。 如果想销毁一个接口,只需将他置为nil TinterfacedObject类自动实现了Iunknown接口所有的函数,自动实现了引用计数, as转换符将自动调用AddRef和Release所以传递派生自TinterfacedObject的类的时候要小心自动计数导致销毁: 假设intf为派生自TinterfacedObject的类的实例 procedure DoSth(intf: IformattedNumber) //将自动引发计数, 导致intf出函数后,自动销毁 procedur DoSth(var intf: IformattedNumber)//ok procedur DoSth(const intf: IformattedNumber)//ok 获取接口指针的三个方法 1 直接分配 var MyInt: TFormattedInteger; MyNumber:IformattedNumber begin MyInt := TFormattedInteger.Create(12); MyNumber:= MyInt; end; 或者if MyInt.GetInterface(IformattedNumber, MyNumber) then begin end 2 as转换符 MyInt := TFormattedInteger.Create(12); MyNumber:= MyInt as IformattedNumber;//如果不成功将触发exception 3自己重写引用计数 每一个com对象都有一个相关的类厂,该类厂负责创建在服务器中实现的com对象 进程内服务器输出的四个标准函数 DllRegisterServer: windows用他来注册com对象 DllUnRegisterServer: DllGetClassObject负责提供给com一个类工厂 DllCanUnloadNow:com负责调用此函数来看是否可以从内存中卸载com服务器 进程内服务器的线程模式 1 单一的:单线程com对象实际上没有线程支持,所有对com服务器的访问都是windows顺序执行的 2 公寓线程:公寓线程的com对象只可以处理来自创建他们的线程的请求,一个服务器可以创建多个com对象,每一个com对象从不同的线程中创建 3自由的:去掉公寓线程的限制,多个线程可以在任何给定的com对象上同时运行 4 both:同时公寓线程和自由线程 注册com服务器 RegSvr32:其实是访问DllRegisterServer函数 卸载com服务器 RegSvr32 -u 客户端创建com对象实例 调用CreateComObject函数,并使用as转换成需要的接口 CreateComObject内部所做的工作: 1内部调用CoCreateInstance 2 CoCreateInstance内部负责创建com对象的类厂的实例,然后使用类厂创建对象,创建完com对象之后,类厂就被销毁 创建进程内com服务器步骤 1 File – New – ActiveX Library 2 新生成一个接口 3 File - New - COM Object 选择派生自新作的接口 4 编译出dll 并且注册 扩张已经发布接口的方法 新见一个接口,使得派生类同时派生自新旧两个接口 服务端: INewFace = Interface(IOldFace) //新增一些方法 TAbstract = Class(TComObject,IOldFace,INewFace) 实现公共算法 TNewClass= Class(TAbstract) 实现一些核心算法 客户端 将CreateComObject(××) as IOldFace 改为CreateComObject(TNewClass) as INewFace 则原来的调动可以不变,还可以使用新的方法 注册进程外服务器 只需运行该服务器,把/regserver放在命令行中。Delphi将注册服务器和com对象,要撤销注册服务器,使用命令行/unregserver 进程外服务器的实例化 1 单实例:每个应用程序只允许一个com对象的实例,每个需要com对象实例的应用程序将产生com服务器的单独拷贝。 第一个要求访问服务器的客户程序将启动一个服务器的实例,当第二个客户需要访问服务器时,将导致启动另一个独立的服务器 2 多实例:是指com Server可以创建一个cm对象的多个拷贝,当客户程序请求com对象的一个实例时,由当前运行的服务器创建com对象的一个实例 3 内部实例:用于不被客户应用程序使用的com对象,能创建这种com对象的应用程序只有包含此com对象的com服务器 调度(Marshaling Data):windows通过一个叫做调度的进程在应用程序和进程外com服务器之间移动数据。Windows不能调度数组或记录 , 如果需要调度数组或记录,可以有两个方法 方法1 为数据实现IMarshal接口 方法2 通过variant数组 创建DCOM服务器 1 File – New – Application 2 File – New – Automation Object 创建DCOM客户端 首先 在客户计算机上注册DCOM服务器的类型库,最简单的方法如下: TregSvr DComServer.tlb 1 File – New – Application 2 Project – Import Type Library 如果新建的DCOMServer不在服务器列表当中,选择ADD按钮,并且导入DCOMServer.tlb类型库,Delphi将自动生成DCOMServer_TLB.pas 3 创建实例并且使用 var FServer: ISimpleDCOMServer; begin FServer := CoSimpleDCOMServer.CreateRemote(ecServer.Text); if FServer.AreYouThere then ShowMessage('I am here.') else ShowMessage('Where''d I go?'); end; Variant数组和结构之间的转换 function RecordToVariant(Part TPartRecord):OleVariant var P:Pointer; begin Result:=VarArrayCreate( [0 , sizieof(TPartRecord)] , varByte); P:=VarArrayLock( Result); //将varaint]数组转换为delphi数组 Move( Part , P^ , sizeof(Part) ); VarArrayUnlock( Result ); end; function VariantToRecord( V:OleVariant) : TPartRecord; var P:Pointer; begin P:=VarArrayLock(V); Move(P^ , Result,sizeof(TPartRecord)); VarArrayUnlock(V); End VarArrayLowBound VarArrayHighBound 用sql语句输出记录的行号 1 oracle直接就有rowid 2 SQL Server select identity(int,1,1) ID,a into #temp from b select * from #temp drop table #temp 列表的重新绘制 TListView.OnCustomDrawItem 可以重新绘制每一个列表选项的样式 , 比如如下代码 ,焦点上的节点变成蓝色且加下划线(OwnerDraw 已经设置为false) procedure TfList.LV1CustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean); var cData: TMAM; begin //是否获取的显示 If Item = nil then Exit; cData :=TMAM(Item.Data); If cData = nil then Exit; with (Sender as TListView).Canvas do begin //Hot属性 If cdsHot in State then Font.Style :=Font.Style + [fsUnderLine] else Font.Style :=Font.Style - [fsUnderLine]; //字体颜色 // If cData.DBLocked then //已经加锁 // Font.Color := OCXFONTCOLOR_LOCKED If cdsHot in State then Font.Color := clBlue else //没有加锁 Font.Color := OCXFONTCOLOR_UNLOCKED; end; end;
基本概念 COM:COM定义了一组api和一个二进制标准,让来自于不同语言,不同平台的彼此独立的对象相互进行通信 调度(Marshaling):实现跨进程边界甚至跨机器边界的函数调用 OLE:在应用程序之间共享的一大块数据称为一个OLE对象,能够包含OLE对象的应用程序称为OLE容器,而允许自己的数据被包含到其他应用程序中的应用程序则称为OLE服务器 复合文档:一个包含一个或者多个OLE对象的文档称为复合文档。 线程模式:每个COM对象都是在一个特定的线程模式下工作的,线程模式决定了一个对象在多线程环境下被操纵的方式,当注册一个COM服务器时,,应该为服务器所包含的每一个COM对象制定他们支持的线程模式。 Single:整个服务器工作在单线程下 Apartment:也称为单线程单元(STA)。每个com对象的多个实例运行在各自独立的线程当中。 Free:也称为多线程单元(MTA)。COM对象必须保护自己的实例数据,避免多线程访问引起冲突。 Both:同时支持Apartment和Free两种线程模式 GUID一共128位 IID:接口的ID CLSID:类的ID 引用计数 在delphi中,接口的引用计数是自动的,当接口被赋位nil时,自动调用Release AS转换 并不是一种真正意义上的强制转换,而是对QueryInterface的内部调用,如果转换不成功,as将触发异常,而且接口只和直接支持这个接口的类赋值兼容 比如 Ifoo—Ibar—TbarClass Var IF:Ifoo; TB:TbarClass; Begin TB:=TbarClass.Create; IF:=TB //将出现编译错误 End; OleCheck函数:将Hresult错误转换为异常 In-process COM服务器:是dll函数,dll函数与调用它的应用程序在同一个进程内 CoFreeUnUsedLies:卸载不再需要使用的COM服务器dll从内存中卸载 CreateComObejct内部调用API CoCreateInstance API CoCreateInstance 首先搜索注册表,载入dll到内存,调用dll的DllGetClassObject获得类工厂的接口,调用类工厂的CreateInstance创建COM对象的实例 Out-of-process COM服务器:是可执行的,用来被其他的应用程序用以创建COM对象 客户端也是调用CreateComObejct,但是此时CreateComObejct调用 CoGetClassObject在注册表中找到信息后,调用CreateProcess调用相关的应用程序,然后服务器调用CoRegisterClassObject注册类工厂,返回类工厂指针,指向com内部的对象表,从而建立一个com对象实例 注意:服务器是在进程外,存根和生成的com object实例都在服务器中,只有代理在进程内部//???????? DCOM服务器: 提供了一种访问网络上其他机器的COM对象的手段,CreateRemoteObject调用CoCreateInstancEx(),从而创建远程对象 其实就是增强版本的进程外服务器 自动化 自动化是 应用程序(exe)或者链接库(dll)显露可编程对象给其他应用程序的手段。是语言无关的。 自动化服务器 显露可编程对象的应用程序或者dll成为自动化服务器 自动化控制器 访问和控制自动化服务器的应用程序 自动化对象在本质上是一种实现Idispatch接口的COM对象 类型库:保存在TLB文件中,或者作为资源链接在服务器应用程序或dll中 后期捆绑:通过Idispatch的invoke方法调用的 前期捆绑:直接调用 支持双重接口的自动化对象:可以invoke来调用方法,也可以通过Idispatch的派生接口调用方法 创建自动化服务器的步骤 1 创建一个应用程序exe或者dll 2 创建一个自动化对象到项目中 Automaton Object 3 通过类型库给自动化对象添加属性和方法 4 实现自动化对象的方法 SafeCall 类型库编辑器中输入方法的默认方式 1 SafeCall可以捕捉所有的异常情况,所有声明为safecall的函数都被包含在try except中 2 方法将返回一个HResult值 创建自动化控制器的步骤 1 注册自动化服务器 2 导入TLB 选择import type library 选择自动化服务器的exe或者dll文件,自动生成××××_TLB.pas 3 连接自动化服务器 连接自动化服务器的三种方式 1接口 2调度接口 3 Variant 1 通过接口访问 var I:IUnitAuto begin I:=CoUnitAuto.Create end; 2 通过variant var V:Variant begin V:=CreateOleObject(‘UnitServ.UnitAuto’) end 3 通过派遣接口 var D1:IUnitAutoDisp begin D1:=CoUnitAuto.Create as IUnitAutoDisp end 通过Variant调用自动化服务器的方法func的实现步骤 A Delphi将这个方法的名字func传递到IDispatch.GetIDsOfNames B GetIDsOfNames返回代表此方法名的整型ID C Delphi通过第二步返回的ID来调用Invoke方法 自动化为每一个接口实现了一个派遣接口(Dispinterfaces),使用派遣接口的时候,可以把前两个步骤去掉,自动化服务器不用调用GetIDsOfNames,而且服务器也不必响应所述方法的dispid。运行时,客户程序预先决定的dispid简单调用invoke 派遣接口只是为了方便客户而设定的,读者实际上并没有在服务器上实现派遣接口,是服务器实现了接口 FIntf: = CoAutoTest . Create ( ); FdispIntf := CreateComObject ( Class_AtuoTest) as IautoTestDisp Fvar := CreateOleObject (‘Srv.AutoTest’ ) (释放Variant变量的方式为 Fvar : = UnAssigned ) 用接口来控制自动化服务器比使用调度接口和variant来控制要好,建议尽量使用接口方式 variant调用在运行期间,先调用GetIDSOfName将方法名字转换为调度号,然后才能执行用InVoke来执行这个方法 调度接口性能介于两者之间。因为它的方法调度号在编译时已经知道 注册自动化服务器的方法 方法一 Delphi –Run—Register Activex – Server 方法二 Regsvr32 . exe 方法三 使用delphi的工具 TregSvr . exe COM事件 IconnectinPoint代表了事件接口 需要向客户端提供事件接口的服务器必须实现IconnectionPointContainer接口,在COM中,连接点 (connection point) 描述了一个实体, 该实体提供了对事件接口的访问。如果客户端要判断一个服务器是否支持事件,就必须用QueryInterFace函数查询IconnectionPointContainer接口, 如果这个接口存在,那么服务器支持事件 ,IconnectionPointContainer。EnumConnectionPoints遍历所有服务器支持的事件接口, IconnectionPointContainer。FindConnctionPoint得到一个指定得事件接口 IconnectionPointContainer IconnectinPoint 客户程序1 客户程序2 客户程序3 delphi中的InterfaceConnect源码 procedure InterfaceConnect(const Source: IUnknown; const IID: TIID; const Sink: IUnknown; var Connection: Longint); var CPC: IConnectionPointContainer; CP: IConnectionPoint; begin Connection := 0; if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then if Succeeded(CPC.FindConnectionPoint(IID, CP)) then CP.Advise(Sink, Connection); end; 接收器(sink) :包含在客户程序中的输出接口的实现称为接收器 源(source):客户端触发事件的服务器对象称为源 在客户端,如果对象实例已经在运行,那么如果要使各客户端都连到这个实例上,还要做一个小 的调整,这要通过调用COM API函数G e t A c t i v e O b j e c t来完成,如下所示 Variant和OleVariant的区别是前者支持所有的类型,而后者只支持在自动化中兼容的类型 创建ActiveX控件的步骤 1 新建一个ActiveX Control 因为ActiveX控件可以运行在web浏览器上,所以调用web浏览器上的函数和接口将非常有意义,web浏览器上的函数和接口大部分在UrlMon单元中 HlinkNavigateString HlinkGoBack(punk:IUnknown) HlinkGoFrrward(punk:IUnknown) 当需要使用ActiveX 控件的Iunkonwn接口时,对于ActiveX控件可以传递Control As Iunknown给这个参数;对于ActiveXForm来说,可以传递Iunknown(VclComObject)给这个参数 cd ../ dos命令,跳到当前目录的上一层 SendMessage()函数支持WM_SETREDRAW消息。使用这个消息,你的代码可以决定是否一个窗口应该被重新绘制。传递True (等价于API的1)允许窗口重新绘制,或者False (0)来阻止绘制 sendmessage(handle,wm_setredraw,0,0); begin SendMessage(LV1.Handle,WM_SETREDRAW,0,0); try LV1.Items.Clear; finally SendMessage(LV1.Handle,WM_SETREDRAW,1,0); end; end 使用命令行参数执行一个exe程序 function CmdOpen(Const AppExe,File1:String):Boolean; var iRtn:integer; sCmd:string; begin If File1<>'' then sCmd:=AppExe+' "'+File1+'"' else sCmd:=AppExe; iRtn:=WinExec(PChar(sCmd),SW_SHOWDEFAULT); Result:=(iRtn>31); end; 在线程函数中,如下代码,比较好 repeat Application.ProcessMessage//迫使程序处理别的消息 使得界面有响应 until function ShellOpen(hWnd :Integer; Const Value:String):Boolean; var iRtn:integer; begin Result:=False; if FileExists(Value) then begin iRtn:=ShellExecute(hWnd,'open',PChar(Value),nil,nil,SW_SHOWDEFAULT); Result:=(iRtn>32); if iRtn=SE_ERR_NOASSOC then MessageBox(hWnd,'没有对应的Windows应用程序打开!','信息',MB_ICONINFORMATION+MB_OK); end; end; 类型库:提供了完全说明com服务器的无确定的编程语言的方法,通常可以作为一种资源集成到com服务器中,也可作为。TLB文件被分发 支持类型库的com对象将由TTypedComObject类派生 创建Activex Form步骤 1 创建Activex Library 2 创建 Activex Form |
|