// 摘自 ComObj 单元 constructor TComObjectFactory.Create(ComServer: TComServerObject; ComClass: TComClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing; ThreadingModel: TThreadingModel); begin // ... // 将自己插入到 ComClassManager 的 Factory List 中去 ComClassManager.AddObjectFactory(Self); // ... end;
再看看 ComClassManager 相关实现代码:
// 摘自 ComObj 单元 TComClassManager = class(TObject) private // TComClassManager 维护着一个 TComObjectFactory 链表 FFactoryList: TComObjectFactory; FLock: TMultiReadExclusiveWriteSynchronizer; procedure AddObjectFactory(Factory: TComObjectFactory); // ... end;
procedure TComClassManager.AddObjectFactory(Factory: TComObjectFactory); begin FLock.BeginWrite; try Factory.FNext := FFactoryList; FFactoryList := Factory; finally FLock.EndWrite; end; end;
ComClassManagerVar 维护着服务器中的所有的类工厂的一个链表,每个单一类工厂的实例都是自动初始化,在我们的服务器 Initialization 节你可以看到,并自动将自己添加到 ComClassManager 的链表(FactoryList)中。现在想想,这样的设计是不是非常棒。 请跟随我继续往下走。当客户端要求 DllGetClassObject 返回指定创建的类工厂,在函数内部调用了 TComClassManager 的 GetFactoryFromClassID 方法。该方法遍历 FactoryList 链表,根据 ClassID 找到对应的类工厂,并返回类工厂对象实例。
// 摘自 ComObj 单元 function TComClassManager.GetFactoryFromClassID(const ClassID: TGUID): TComObjectFactory; begin FLock.BeginRead; try Result := FFactoryList; while Result <> nil do begin if IsEqualGUID(Result.ClassID, ClassID) then Exit; Result := Result.FNext; end; finally FLock.EndRead; end; end;
对上面的代码分析我再多说一下,链表 FFactoryList 变量实际就是 TComObjectFactory 类型,TComObjectFactory 创建时就获得了丰富的关于它要创建的相关 COM 对象信息,例如在我们这个范例里,ClassFactory 知道了它要创建的 COM 对象类型是 TSimpleComObject, ClassID 是 Class_SimpleComObject..等等,这些都为类工厂在创建相关类以及一些辅助方法(函数)都提供了极为重要的信息。
// 摘自 ComObj 单元 constructor TComObjectFactory.Create(ComServer: TComServerObject; ComClass: TComClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing; ThreadingModel: TThreadingModel); begin // ... FComServer := ComServer; FComClass := ComClass; FClassID := ClassID; FClassName := ClassName; FDescription := Description; FInstancing := Instancing; FErrorIID := IUnknown; FShowErrors := True; FThreadingModel := ThreadingModel; FRegister := -1; end;
DllGetClassObject 获得正确的类工厂对象之后调用它的 GetInterface 方法,这个方法实际上是继承自 TObject.GetInterface,Delphi 为每一个带有 GUID 的接口设计了一个记录结构 -- TInterfaceEntry 记录,实现 IClassFactory 接口的 TComObjectFactory 对象 VMT 中的 vmtIntfTable 指向一个 TInterfaceTable 记录, 该记录包含有它实现的接口数量(IUnknown、IClassFactory)、相应接口的 TInterfaceEntry 记录等信息,通过查询 IClassFactory 接口相应 TInterfaceEntry 记录中的 IOffset 域获得该接口在 TComObjectFactory 对象实例中的正确位置,并返回指向该位置的 IClassFactory 接口指针[1][3]。
// 摘自 System 单元 function TObject.GetInterface(const IID: TGUID; out Obj): Boolean; var InterfaceEntry: PInterfaceEntry; begin Pointer(Obj) := nil; InterfaceEntry := GetInterfaceEntry(IID); if InterfaceEntry <> nil then begin if InterfaceEntry^.IOffset <> 0 then begin Pointer(Obj) := Pointer(Integer(Self) + InterfaceEntry^.IOffset); if Pointer(Obj) <> nil then IInterface(Obj)._AddRef; end else IInterface(Obj) := InvokeImplGetter(Self, InterfaceEntry^.ImplGetter); end; Result := Pointer(Obj) <> nil; end;
至此,CoGetClassObject 内部调用服务器端的 DllGetClassObject 已经正确获得了负责创建 SimpleCOMObject 对象的 IClassFactory 接口。在获得这个接口后,就可以调用它的方法 CreateInstance 创建 SimpleCOMObject 对象并返回 ISimpleCOMObject 接口,现在你可以对 ISimpleCOMObject 接口任意进行操作了。 让我们再看看 ButtonClick2 中是如何创建 SimpleCOMObject 对象的。 ButtonClick2 是调用 CreateComObject 函数创建 SimpleCOMObject 对象的。 CreateComObject 函数只是对 COM API -- CoCreateInstance 的一个简单包装。为什么要包装它,你可以看一下 CoCreateInstance 的参数就知道为什么了,参数多且复杂,这是 Windows API 的通病,而 VCL 实现却很体贴我们,它传递 CLSID 作为唯一的参数,其实平时应用中我们创建的大部分 COM 对象都是 CLSID 已知,并且对象是驻留在本地或进程内服务器的指定对象。
// 摘自 ComObj 单元 function CreateComObject(const ClassID: TGUID): IUnknown; begin OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result)); end;
CoCreateInstance 也存在于 OLE32.DLL中,其内部也是先调用 CoGetClassObject 函数,返回负责创建 SimpleCOMObject 的IClassFactory 接口,然后也还是调用该接口的 CreateInstance 创建 SimpleCOMObject 并返回该对象的 IUnknown 接口,到这一步,与Button1Click 中创建 SimpleCOMObject 的实现方法区别在于 Button1Click 通过 ClassFactory 的 CreateInstance 直接返回 ISimpleCOMObject 接口而不是它的 IUnknown 接口,其他的并没有什么区别,相对 Button1Click 的方法更直观。在获得了 SimpleCOMObject 的 IUnknown 接口之后,我们并不能立即用此接口去调用 ISimpleCOMObject 的方法,为了和对象通信,必须先将它转换成 ISimpleComObject 接口。那么有读者会问为什么 CreateComObject 不设计成能直接返回需要的接口呢,我想还是为了简化这个函数的使用吧。获得 ISimpleComObject 接口可以通过调用 IUnknown 接口的 QueryInterface 方法查询 SimpleCOMObject 对象是否支持该接口, Delphi 为我们提供了更简单的方法 -- “AS”关键字。先让我们看看 As 在幕后到底为我们做了什么(Debug 状态下的反汇编源码):
Unit1.pas.49: ComInterface := CreateComObject(Class_SimpleComObject) as ISimpleComObject; 0045B2C6 8D55FC lea edx,[ebp-$04] 0045B2C9 A16CD24500 mov eax,[$0045d26c] 0045B2CE E8C9F0FFFF call CreateComObject 0045B2D3 8B55FC mov edx,[ebp-$04] 0045B2D6 8D8314030000 lea eax,[ebx+$00000314] 0045B2DC B93CB34500 mov ecx,$0045b33c 0045B2E1 E87AA9FAFF call @IntfCast
可以看到, AS 被转换成调用 @IntfCast,即 system 单元的 _IntfCast 函数。呵呵,其实就是调用 IUnknown 接口的 QueryInterface 方法。
// 摘自 System 单元 procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID); {$IFDEF PUREPASCAL} // PIC: EBX must be correct before calling QueryInterface var Temp: IInterface; begin if Source = nil then Dest := nil else begin Temp := nil; if Source.QueryInterface(IID, Temp) <> 0 then Error(reIntfCastError) else
上一页 [1] [2] [3] 下一页 [聊天工具]Google Suggest十大妙用 [聊天工具]保驾护航Web迅雷 全新版本给你更多安全 [聊天工具]玩转火狐的Cookie 让火狐狸吃好小甜饼! [聊天工具]P2P下载的好工具 POCO完全攻略 [聊天工具]横扫一切高价话费 Vbuzzer八分钱国际长途任你打 [聊天工具]众人拾柴火焰高 改进迅雷于不经意间 迅雷 [聊天工具]中英文互翻 Google Toolbar4中文版试用手记 [聊天工具]巧用µTorrent 体验国外下载站的乐趣 [聊天工具]可远程搜索桌面—Google Desktop 3全新体验 [聊天工具]腾讯浏览器 TT 之实用技巧荟萃 TT,技巧荟萃
|