Delphi的Com实现源代码(RTL/VCL)实现分析继续部分(0分)

  • 主题发起人 going_cc
  • 开始时间
G

going_cc

Unregistered / Unconfirmed
GUEST, unregistred user!
发现自动化服务器和控件服务器在事件支持上有些不一样。关于事件支持:
从类型库的角度而言,事件支持就是多了一个接口:
IsolaEvents = dispinterface
['{0EFE4F7E-3177-4063-A844-48D4701EB555}']
end;
而事件激发,就是我们的组件中调用了这个接口的方法。
我们的组件声明如下:
Tsola = class(TAutoObject, IConnectionPointContainer, Isola)
private
{ Private declarations }
FConnectionPoints: TConnectionPoints;
FConnectionPoint: TConnectionPoint;
FEvents: IsolaEvents;
{ note: FEvents maintains a *single* event sink. For access to more
than one event sink, use FConnectionPoint.SinkList, and iterate
through the list of sinks. }
(这里的意思是说FEvents是一个事件连接点,我们可以有多个)
public
procedure Initialize
override;
protected
{ Protected declarations }
property ConnectionPoints: TConnectionPoints read FConnectionPoints
implements IConnectionPointContainer;
procedure EventSinkChanged(const EventSink: IUnknown)
override;
end;
可见实现了IConnectionPointContainer接口。由:
TConnectionPoint = class(TContainedObject, IConnectionPoint)和
TConnectionPoints = class{IConnectionPointContainer}
可以看到,我们的类实现其实两个连接接口都实现了。
TConnectionPoints中的EnumConnectionPoints实现返回了一个IEnumConnectionPoints接口,这个接口是由TEnumConnectionPoints实现的,当然里面的内容就是一个TConnectionPoints。实现中的:enumconn := TEnumConnectionPoints.Create(Self, 0);中用了Self,这是怪怪的,但理由是确实是把自己(其实是成员)交给了这个起管理作用的对象。
FConnectionPoints: TList;这就是TConnectionPoints中存储的实质。
function TConnectionPoints.FindConnectionPoint(const iid: TIID;
out cp: IConnectionPoint): HResult;
var
I: Integer;
ConnectionPoint: TConnectionPoint;
begin
for I := 0 to FConnectionPoints.Count - 1 do
begin
ConnectionPoint := FConnectionPoints;
if IsEqualGUID(ConnectionPoint.FIID, iid) then
begin
cp := ConnectionPoint;
Result := S_OK;
Exit;
end;
end;
Result := CONNECT_E_NOCONNECTION;
end;
这里,我发现out cp: IConnectionPoint的结果是cp := ConnectionPoint;而ConnectionPoint: TConnectionPoint;很明显,这应当是编译器的自动的工作,实际上,这个接口的指针其实就是这个类(关键是一个子类)的指针。这其实是很重要的一个技巧和技术^_^又是基本概念的问题。
AController的意思不明!
function TConnectionPoints.CreateConnectionPoint(const IID: TGUID;
Kind: TConnectionKind
OnConnect: TConnectEvent): TConnectionPoint;
begin
Result := TConnectionPoint.Create(Self, IID, Kind, OnConnect);
end;
这是一个重要函数,其中TConnectionKind是表示所谓的单连接和多连接!!!而TConnectEvent是一个执行函数对象。由下面开始揭示TConnectionPoint:
constructor TConnectionPoint.Create(Container: TConnectionPoints;
const IID: TGUID
Kind: TConnectionKind;
OnConnect: TConnectEvent);
begin
inherited Create(IUnknown(Container.FController));
FContainer := Container;
FContainer.FConnectionPoints.Add(Self);
FSinkList := TList.Create;
FIID := IID;
FKind := Kind;
FOnConnect := OnConnect;
end;
回过来要说一下,一般是用IDispatch作为出接口的。我们通过TConnectionPoints查询一个出接口,如果支持的话,就获得了相关的TConnectionPoint实现的接口的指针。注意我们的实现类的FIID不是这个连接点的,而是与他相连的出接口的。
function TConnectionPoint.Advise(const unkSink: IUnknown;
out dwCookie: Longint): HResult;
begin
if (FKind = ckSingle) and (FSinkList.Count > 0) and
(FSinkList[0] <> nil) then
begin
Result := CONNECT_E_CANNOTCONNECT;
Exit;
end;
try
if Assigned(FOnConnect) then FOnConnect(unkSink, True);
dwCookie := AddSink(unkSink) + 1;
Result := S_OK;
except
Result := HandleException;
end;
end;
其中的unkSink就是我们搞到的出接口指针。这里就是建立连接了。
if Assigned(FOnConnect) then FOnConnect(unkSink, True);这里的意思就是如果定义了FOnConnect的话,就执行这个函数对象。当然是我们的组件调用我们拥有的而有客户实现的unkSink接口的方法。这就是在连接时触发的事件了,在断开时也触发事件。
需要强调的是连接点是只和一个类型的接口关联,但是不是只和一个接口关联,只有是单连接的情况下在这样,可以有多个连接,存储在表中,当然类型都是一样的,指向同样类型接口不同的实例的地址。

所以现在:
FConnectionPoints: TConnectionPoints;
FConnectionPoint: TConnectionPoint;
这里的意思就是实现这两个接口。
FEvents: IsolaEvents;
这里的意思就是接收器需要实现的接口。
procedure Tsola.Initialize;
begin
inherited Initialize;
FConnectionPoints := TConnectionPoints.Create(Self);
if AutoFactory.EventTypeInfo <> nil then
FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
AutoFactory.EventIID, ckSingle, EventConnect)
else FConnectionPoint := nil;
end;
中TConnectionPoints.Create(Self);的Self就是前面不知道的那个Owner。然后询问类厂有没有事件的类型的信息,如果有的话就从连接点容器建立连接点。AutoFactory.EventIID是我们的出接口。EventConnect就是连接时触发的事件,在TAutoObject中有声明,这里可以重载。
EventSinkChanged这个函数在父类中也是空的,这里我们实现为:
procedure Tsola.EventSinkChanged(const EventSink: IUnknown);
begin
FEvents := EventSink as IsolaEvents;
end;
现在可以说一下过程,自动化对象的类厂创建的时候从类型库得知有没有事件方面的类型信息,有的话,就建立连接点。从而获得我们需要的出接口,这样就可以使用这个接口的方法,而激发事件了。
/////////////////////////
到此,我们对于TAutoObject的构造有了了解了:
TAutoObject = class(TTypedComObject, IDispatch)
private
FEventSink: IUnknown;
FAutoFactory: TAutoObjectFactory;
protected
{ IDispatch }
function GetIDsOfNames(const IID: TGUID
Names: Pointer;
NameCount, LocaleID: Integer
DispIDs: Pointer): HResult
virtual
stdcall;
function GetTypeInfo(Index, LocaleID: Integer
out TypeInfo): HResult
virtual
stdcall;
function GetTypeInfoCount(out Count: Integer): HResult
virtual
stdcall;
function Invoke(DispID: Integer
const IID: TGUID
LocaleID: Integer;
Flags: Word
var Params
VarResult, ExcepInfo, ArgErr: Pointer): HResult
virtual
stdcall;
{ Other methods }
procedure EventConnect(const Sink: IUnknown
Connecting: Boolean);
procedure EventSinkChanged(const EventSink: IUnknown)
virtual;
property AutoFactory: TAutoObjectFactory read FAutoFactory;
property EventSink: IUnknown read FEventSink write FEventSink;
public
procedure Initialize
override;
end;
这里除了一般的实现接口外,还为事件机制提供了支持。
(这里有给问题:
FEventTypeInfo := GetInterfaceTypeInfo(IMPLTYPEFLAG_FDEFAULT or
IMPLTYPEFLAG_FSOURCE);)
procedure TAutoObject.EventConnect(const Sink: IUnknown;
Connecting: Boolean);
begin
if Connecting then
begin
OleCheck(Sink.QueryInterface(FAutoFactory.FEventIID, FEventSink));
(这里向Sink接口(出接口)查询我们可以提供的出接口,做验证)
EventSinkChanged(TDispatchSilencer.Create(Sink, FAutoFactory.FEventIID));
(而这里为何不直接用FEventSink或者Sink为参数?好象是对是否是IDispatch作判断)
这里的原因是Sink是一个IDispatch接口,对这个接口不能直接使用我们的方法调用。
这是一个代理类TDispatchSilencer,这里的讨论就到了Delphi和COM实现的底层了。
end
else
begin
FEventSink := nil;
EventSinkChanged(nil);
end;
end;
这就是连接和端开时发生的事情了:
不过连接了,就把当前事件接口指针换为连接的这个。
如果断开了,就把当前的事件接口指针换为nil。
/////////////////////////
constructor TDispatchSilencer.Create(ADispatch: IUnknown;
const ADispIntfIID: TGUID);
begin
inherited Create;
DispIntfIID := ADispIntfIID;
OleCheck(ADispatch.QueryInterface(ADispIntfIID, Dispatch));
end;
这就是这个一个代理,对于外部的对象是使用,是通过这个对象操作的。
拥有这个代理的原因是为了绑定或者是其它什么原因呢?
可以认定的是这个对象是包容出接口的。
现在的情况是:连接点是用FIID来标志的,就是出接口的IID。——》用户进行Advise操作的时候,获得客户端对象实例的指针,然后激发FOnConnect事件,不管怎么样,保存这个指针。——》自动化对象中,激发了FOnConnect事件,如果是连接事件,则EventSinkChanged(TDispatchSilencer.Create(Sink, FAutoFactory.FEventIID));就是建立一个TDispatchSilencer来代理Sink,完成变换的函数就是EventSinkChanged,这是顾名思义的。——》本来自动化对象中的FEvents只是一个类型,现在,我们的自动化对象内部就得到了可以操作的事件对象。
///
使用代理对象的原因十分有可能是只有运行时才知道出接口的构造

/////////////////////
下面开始讨论TOleServer,这是自动化组件的Delphi用户端包装,是有别于ActiveX的TOleControl的包装的。
 
顶部