今天下午看了Delphi的部分COM实现,做了点笔记^_^(0分)

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

going_cc

Unregistered / Unconfirmed
GUEST, unregistred user!

////////////////////
TactiveXControl的实现是可以理解的。而类型库的ToleControl是ActiveX的包容器,所以,我对于Borland的类型库文件的实现有些不解。
加载类型库的时候,我发现许多的CoClass的内容,而且包括部分的实现,我想读出类型库以后,编译为Delphi可以直接使用的构造是目的吧。
//
所以一边是实现单元,另一边是对外面的视图的封装。
换个角度说,实现的时候不必太计较,而使用的时候有类型库可以用。而且类型库中的一些内容是为了Delphi和开发环境而建立的。ToleServer的工作就是把一个COM 对象包装为一个Tcomponent对象,TOleControl方面,还不太清楚。不过这样的包装有意思,而对于工作的机制,还需要实际分析。

从控件领悟到,部件,尤其是跨套间的,是有自己的主消息循环的应用。而为了让容器获得对于事件的控制,必须由容器提供操作事件的接口,而由控件在事件发生时激发。

ThahaProperties原来是$IFDEF LIVE_SERVER_AT_DESIGN_TIME,真的是没有想到。^_^。


Cohaha = class
class function Create: Ihaha;
class function CreateRemote(const MachineName: string): Ihaha;
end;
作用是提供一个标准的操作方式,十分简单的封装。而设计时的显示,我想,多半是象征的意味。而且在载入类型库的时候,才自动生成了一个OLE Server Proxy class来封装组件对象和接口。当然还为设计时建立了ThahaProperties对象。(从这里可以了解的是,OLE仍然比我们想象的重要的多。

Thaha = class(TOleServer)
private
FIntf: Ihaha;
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
FProps: ThahaProperties;
function GetServerProperties: ThahaProperties;
{$ENDIF}
function GetDefaultInterface: Ihaha;
protected
procedure InitServerData
override;
public
constructor Create(AOwner: TComponent)
override;
destructor Destroy
override;
procedure Connect
override;
procedure ConnectTo(svrIntf: Ihaha);
procedure Disconnect
override;
function Method1: HResult;
property DefaultInterface: Ihaha read GetDefaultInterface;
published
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
property Server: ThahaProperties read GetServerProperties;
{$ENDIF}
end;
这个的作用是明显的OLE Server Proxy class,这里代理对象从定义和祖先上看,是一个TComponent,而且,由Delphi类型库工具构造成一个对COM对象进行管理的TComponent。这样,才编程环境中可以把COM对象当成一个Delphi的TComponent来使用。(不过,TOleServer的特征也是十分明显的)

(TMtsAutoObject需要研究)

可以认为对于常规的组件的封装,也可以叫做类型库的再构造(其实相当于头文件),就到此为止了,这一部分是标准的实现,需要再深入的分析,不过不复杂。
剩下的是ActiveX方面的,我对设计时和使用时的类型库有点奇怪。

TListBoxX是对TListBox的一个包容,而自己继承自TActiveXControl。
继承自TOleControl的TListBoxX是OLE Control Proxy class,这是比较的有意思,不过这个类是继承自TWinControl,是一个可视的TComponent,哈哈,不知道使用端的类型库是怎样生成的。

关于事件,有一个问题:
procedure TListBoxX.InitializeControl;
begin
FDelphiControl := Control as TListBox;
FDelphiControl.OnClick := ClickEvent;
FDelphiControl.OnDblClick := DblClickEvent;
FDelphiControl.OnKeyPress := KeyPressEvent;
end;
这是一种委托,把VCL控件的事件对应于TListBoxX的事件处理函数。而TListBoxX是这样处理事件的:
procedure TListBoxX.DblClickEvent(Sender: TObject);
begin
if FEvents <> nil then FEvents.OnDblClick;
end;
这样的话,就把事件处理函数交给了IListBoxXEvents的成员。很明显,作为dispinterface的IListBoxXEvents,这是一个标准的事件接口模型,这个接口实际上是由客户端的实现的,客户端由dispinterface可以知道服务器(ActiveX)支持的事件。就这样了。(当然内部机制还是需要分析的,服务器组件知道事件发生时调用IListBoxXEvents成员,而IListBoxXEvents是由客户实现的,关键是客户怎样知道要实现怎样的接口)

我没有注册这个控件,在编译以后,我获得的类型库文件和开发时的是一样的。
我在注册以后,由获得类型库的手段得到的类型库和开发时几乎一样,估计就一个资源字符串是不一样的。
到目前为止,我获得的材料是合理而可以理解的,而印象中的事情不是这样的,我想,需要了解一下不是自己编写的控件,(这里还要说明的一点是关于事件的:
property OnClick: TNotifyEvent read FOnClick write FOnClick;
在这里可以得到解释:
FOnClick: TNotifyEvent;
而 TNotifyEvent = procedure(Sender: TObject) of object;
当事件发生的时候,服务器控件执行事件接口的代码。而接口的实现不是一般的实现,而是动态的实现,由TOleControl做了所有的工作,从客户的角度而言,开发环境自动生成了一个叫TListBoxX的普通组件。当然,然后的事件激发机智和一般的情况一样,另人十分有兴趣的是TOleControl是怎样做好IListBoxXEvents的实现的,其实是一个传递,^_^。——大概的过程就是获得接口的函数声明,然后在派生类中引申出来,而且机制方面和一般的组件一样。)

看了ShockwaveFlashObjects_TLB的内容,完全没有什么问题,都的意料以内,下面可以开始就几个方面的实现的本身代码开始分析,首先是类库的构造整理。
(一个问题:
initialization
TActiveFormFactory.Create(
ComServer,
TActiveFormControl,
TActiveFormX,
Class_ActiveFormX,
1,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
end.
还没有解释)

//////////////////////////////////////////////////

function DllGetClassObject(const CLSID, IID: TGUID
var Obj): HResult;
var
Factory: TComObjectFactory;
begin
Factory := ComClassManager.GetFactoryFromClassID(CLSID);
if Factory <> nil then
if Factory.GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE
else
begin
Pointer(Obj) := nil;
Result := CLASS_E_CLASSNOTAVAILABLE;
end;
end;
这里对关心的DLL入口函数说明的很清楚。从ComClassManager获得TComObjectFactory,这样就可以Factory.GetInterface(IID, Obj)了,而得到我们的对象。
(这里有一个补充:
procedure TComClassManager.AddObjectFactory(Factory: TComObjectFactory);
begin
FLock.BeginWrite;
try
Factory.FNext := FFactoryList;
FFactoryList := Factory;
finally
FLock.EndWrite;
end;
end;
这里可以看到,维护FFactoryList的方法是一个不知道是堆还是链的数据结构。

Factory := ComClassManager.GetFactoryFromClassID(CLSID);中的ComClassManager处使用了特别的语法:
function ComClassManager: TComClassManager;
begin
if ComClassManagerVar = nil then
ComClassManagerVar := TComClassManager.Create;
Result := TComClassManager(ComClassManagerVar);
end;
^_^,对Object Pascal越来越佩服了。
下面就要解释TComObjectFactory = class(TObject, IUnknown, IClassFactory, IClassFactory2)的实现了:
Factory.GetInterface(IID, Obj)实际是对function TObject.GetInterface(const IID: TGUID
out Obj): Boolean;的调用,没有想到是在这里实现的。——这里我遇到了自己概念不清楚的地方。DllGetClassObject实际是为了获得ClassObject接口,一个COM组件,就是一个TComObjectFactory实现的接口TComObjectFactory = class(TObject, IUnknown, IClassFactory, IClassFactory2),这样,返回的就是一个接口。使用ComClassManager的原因是一个组件可以实现多个COM对象。
下面的问题是TComObjectFactory的实现,一般的实现中,我们自己要实现类厂对象和接口,这里为各种对象提供各种类厂,这里自然就要想到的是怎么样把一个类厂对象和我们实现的对象绑定起来。(就是用户在使用IClassFactory.CreateInstance的时候可以得到我们的对象)
这里要看到的就是前面提到的:
initialization
TTypedComObjectFactory.Create(ComServer, Tdataslic, Class_dataslic,
ciMultiInstance, tmApartment);
end.
这里就是构造一个TComObjectFactory,让我们看看构造的过程:
constructor TComObjectFactory.Create(ComServer: TComServerObject;
ComClass: TComClass
const ClassID: TGUID
const ClassName,
Description: string
Instancing: TClassInstancing;
ThreadingModel: TThreadingModel);
begin
IsMultiThread := IsMultiThread or (ThreadingModel <> tmSingle);
if ThreadingModel in [tmFree, tmBoth] then
CoInitFlags := COINIT_MULTITHREADED else
if (ThreadingModel = tmApartment) and (CoInitFlags <> COINIT_MULTITHREADED) then
CoInitFlags := COINIT_APARTMENTTHREADED;
ComClassManager.AddObjectFactory(Self);
FComServer := ComServer;
FComClass := ComClass;
FClassID := ClassID;
FClassName := ClassName;
FDescription := Description;
FInstancing := Instancing;
FErrorIID := IUnknown;
FShowErrors := True;
FThreadingModel := ThreadingModel;
FRegister := -1;
end;
首先,ComClassManager.AddObjectFactory(Self);把自己注册到ComClassManager里面。(现在我不太想讨论套间的问题)
FComClass := ComClass;
FClassID := ClassID;
把我们的对象的信息给予了特定的TComObjectFactory。这样:
function TComObjectFactory.CreateInstance(const UnkOuter: IUnknown;
const IID: TGUID
out Obj): HResult;
begin
Result := CreateInstanceLic(UnkOuter, nil, IID, '', Obj);
end;

function TComObjectFactory.CreateInstanceLic(const unkOuter: IUnknown;
const unkReserved: IUnknown
const iid: TIID
const bstrKey: WideString;
out vObject): HResult
stdcall;
var
ComObject: TComObject;
begin
// We can't write to a nil pointer. Duh.
if @vObject = nil then
begin
Result := E_POINTER;
Exit;
end;
// In case of failure, make sure we return at least a nil interface.
Pointer(vObject) := nil;
// Check for licensing.
if FSupportsLicensing and
((bstrKey <> '') and (not ValidateUserLicense(bstrKey))) or
((bstrKey = '') and (not HasMachineLicense)) then
begin
Result := CLASS_E_NOTLICENSED;
Exit;
end;
// We can only aggregate if they are requesting our IUnknown.
if (unkOuter <> nil) and not (IsEqualIID(iid, IUnknown)) then
begin
Result := CLASS_E_NOAGGREGATION;
Exit;
end;
try
ComObject := CreateComObject(UnkOuter);
except
if FShowErrors and (ExceptObject is Exception) then
with Exception(ExceptObject) do
begin
if (Message <> '') and (AnsiLastChar(Message) > '.') then
Message := Message + '.';
MessageBox(0, PChar(Message), PChar(SDAXError), MB_OK or MB_ICONSTOP or
MB_SETFOREGROUND);
end;
Result := E_UNEXPECTED;
Exit;
end;
Result := ComObject.ObjQueryInterface(IID, vObject);
if ComObject.RefCount = 0 then ComObject.Free;
end;
这里的关键是下面几句:
ComObject := CreateComObject(UnkOuter);
Result := ComObject.ObjQueryInterface(IID, vObject);
这样,用户可以通过类厂的接口获得我们所要的接口。
///////////
我们略过了 TTypedComObjectFactory.Create(ComServer, Tdataslic, Class_dataslic, ciMultiInstance, tmApartment);中的ComServer,因为这又是一个复杂的问题:
ComServer在自己的单元中已经实例化和初始化:
initialization
begin
OleAutHandle := SafeLoadLibrary('OLEAUT32.DLL');
ComServer := TComServer.Create;
if not ModuleIsLib then
begin
SaveInitProc := InitProc;
InitProc := @InitComServer;
AddTerminateProc(@AutomationTerminateProc);
end;
end;
发现其实这个单元才是真正的入口单元,感慨是不免的^_^。
procedure TComServer.Initialize;
begin
try
UpdateRegistry(FStartMode <> smUnregServer);
except
on E: EOleRegistrationError do
// User may not have write access to the registry.
// Squelch the exception unless we were explicitly told to register.
if FStartMode = smRegServer then raise;
end;
if FStartMode in [smRegServer, smUnregServer] then Halt;
ComClassManager.ForEachFactory(Self, FactoryRegisterClassObject);
end;
这里做的就是注册而已。
ComClassManager.ForEachFactory(Self, FactoryRegisterClassObject);
难道各位不喜欢这样的句子吗?这个模块不是很复杂,不过需要一个头绪:
用户载入我们的库的时候,TComServer实例化,完成最初的工作,包括载入类型库等——》用户开始调用的时候,四个入口函数,主要是DllGetClassObject实例化了ComClassManager,当然生成了需要的TComObjectFactory,并加入队列并和我们的实现对象绑定。——》这样,用户可以开始进行操作了。
////////////////////////////////////
关于类型库的猜测,一个方面是通过Delphi的定义文件(*.idl实际一直在,一般不显示),当然生成类型库了,二是Delphi对类型库做了很好的封装,一般的组件我们是用:
Codataslic = class
class function Create: Idataslic;
class function CreateRemote(const MachineName: string): Idataslic;
end;
来创建组件,(这个文件中的是基本的说明和封装,资料来自于类型库^_^,当然,这是一个标准的Delphi源文件) 对于支持自动化的,就包装成Delphi控件。我想,在同一个部件的调用另一个COM,应当直接使用还是间接?看来关于套间的知识要补充了。这是弱点。
///
上面说的DLL服务器,EXE服务器是一样的,不过入口不一样了,下面是ComServ中相关的初始化代码:
if not ModuleIsLib then
begin
SaveInitProc := InitProc;
InitProc := @InitComServer;
AddTerminateProc(@AutomationTerminateProc);
end;
这里延伸下去还有许多的问题。比OLE的还要多。而且上面我躲避了套间,聚合等问题。
 
头大不要怪我哦^_^
 
老大你就不能断下行?
你自己看着不头大?
 
请问你看的是哪本参考书?
 
后退
顶部