I hope you can stand up to another page...
Here i want to play with funny features of OP language, something related to the prefix "meta".
We can start from "reflection", supported by a large set of runtime type information, generated by the compiler. It is not as complete as Java, but it's enough for a lot of things. The IDE itself makes a broad use of these information to provide "visual" programming.
For many things there's an easy access: TObject class implements RTTI inquiry
A first group is composed by:
and can be used to "stringify" type and browse thru class inheritance
a second group works with interface support
a third group is more "structural", and it requires {$M+} compiler
option for additional type generation for "published" class section
({$M+} option is inheritable, and it is required only for base classes
like TPersistent )
Except from FieldAddress (that needs to point to instance data), all these
methods are declared "class" and work not only with objects but
also with Delphi "metaclasses".
Another two methods are a little more "meta" than the others:
For an object ClassType return its class "type" as a "value" of TClass "metaclass" type (mm....)
ClassInfo applied to a class returns a pointer to RTTI for the class.
If you remember virtual method call:
AnObjectReference => AnObjectInstance (at offset 0) => AClassVMT
you may see a "value" of a "metaclass" type, as a pointer to the VMT of the class (type) that it refer to:
AMetaClassValue => AClassVMT
But a ClassVMT pointer isn't important just for virtual methods, behind VMT there's a lot of class information with pointers to compiler generated tables. TObject implementation uses this data for its magic.
Additional RTTI information can be reached via ClassInfo pointer, but i don't know this topic, so i can only suggest you to read "Delphi in Nutshell" ("best seller") book.
But a nice ClassInfo features is that you can reach type data with objects and metaclasses and not just with "literal" types in "TypeInfo" function.
but i don't want to be too serious, so here a sample of totally useless code ...
type
{M+}
TBase = class(TObject)
protected
function AdjustMethod(m: TMethod): TMethod;
end;
{M-}
TAObject = class;
TAEqual = function(AObject: TAObject): Boolean of object;
...
TAObject = class(TBase)
private
FAField: Integer;
public
constructor Create(A: Integer); overload;
function CallEqual(e: TAEqual; AObject: TAObject): Boolean;
...
published
function SlicedIsEqual(AObject: TAObject): Boolean; virtual;
...
end;
TBObject = class(TAObject)
private
FBField: Integer;
public
constructor Create(A,B: Integer); overload;
published
function SlicedIsEqual(AObject: TBObject): Boolean; reintroduce; // unsafe type ...
...
end;
...
function TBase.AdjustMethod(m: TMethod): TMethod;
var
methodName: ShortString;
begin
methodName := TObject(m.Data).ClassType.MethodName(m.Code); // unhides hidden
m.Code := TObject(m.Data).ClassType.MethodAddress(methodName); // derived method ...
result := m;
end;
function TAObject.CallEqual(e: TAEqual; AObject: TAObject): Boolean;
begin
e := TAEqual(AdjustMethod(TMethod(e)));
result := e(AObject);
end;
function TAObject.SlicedIsEqual(AObject: TAObject): Boolean;
begin
result := (FAField = AObject.FAField);
end;
function TBObject.SlicedIsEqual(AObject: TBObject): Boolean;
begin
result := (inherited SlicedIsEqual(AObject)) and
(FBField = AObject.FBField);
end;
...
procedure TestUnHide;
var
a1,a2: TAObject;
b1: TBObject;
begin
b1 := TBObject.Create;
b1.BField := 1;
a1 := b1;
a2 := a1.CallClone(a1.SlicedClone);
a2.Dump;
if (a1.CallEqual(a1.SlicedIsEqual,a2)) then WriteLn('Equal ...')
else WriteLn('not Equal ...');
a2 := a1.CallTransform(a1.SlicedTranform,a2);
a2.Dump;
if (a1.CallEqual(a1.SlicedIsEqual,a2)) then WriteLn('Equal ...')
else WriteLn('not Equal ...');
a1.Free;
a2.Free;
end;
this code isn't just useless, it's also type unsafe, because it un-hides method with different signatures ...
you may find a much clever use of TMethod type in Delphi sources ...
this one is still useless, but it is a bit more interesting, working with "virtual constructors" ...
after all, not every OOP language is able to do that (he! he! he!)
type
TA = class(TObject)
private
FValue: Integer;
protected
procedure SetValue(const Value: Integer); virtual;
public
procedure Show;
property Value: Integer read FValue write SetValue;
class function MakeCopy(A: TA): TA; //not virtual
constructor Create; //not virtual
constructor CreateFrom(A: TA); virtual;
end;
TB = class(TA)
public
constructor Create; virtual;
constructor CreateFrom(B: TB); reintroduce; overload; virtual;
end;
TC = class(TB)
public
constructor Create; override;
constructor CreateFrom(C: TA); overload; override;
constructor CreateFrom(C: TB); override;
constructor CreateFrom(C: TC); reintroduce; overload;
end;
TAClass = class of TA;
TBClass = class of TB;
TCClass = class of TC;
implementation
{ TA }
class function TA.MakeCopy(A: TA): TA;
begin
result := CreateFrom(A);
end;
constructor TA.Create;
begin
WriteLn('TA.Create');
inherited; //TObject;
Value := Value + 1;
end;
constructor TA.CreateFrom(A: TA);
begin
WriteLn('TA.CreateFrom:',A.ClassName);
FValue := A.Value + 1000;
end;
procedure TA.SetValue(const Value: Integer);
begin
FValue := Value;
end;
procedure TA.Show;
begin
WriteLn('Class:',ClassName);
WriteLn('Value:',Value);
WriteLn('__________________________');
end;
{ TB }
constructor TB.Create;
begin
WriteLn('TB.Create');
inherited;
Value := Value + 10;
end;
constructor TB.CreateFrom(B: TB);
begin
WriteLn('TB.CreateFrom:',B.ClassName);
inherited CreateFrom(B);
FValue := Value + 10000;
end;
{ TC }
constructor TC.Create;
begin
WriteLn('TC.Create');
inherited;
Value := Value + 100;
end;
constructor TC.CreateFrom(C: TC);
begin
WriteLn('TC.CreateFrom:',C.ClassName);
inherited CreateFrom(C);
FValue := Value + 100000;
end;
constructor TC.CreateFrom(C: TB);
begin
WriteLn('TC.CreateFrom (override):',C.ClassName);
if C is TC then
CreateFrom(TC(C))
else
inherited CreateFrom(C);
FValue := Value + 5000000;
end;
constructor TC.CreateFrom(C: TA);
begin
WriteLn('TC.CreateFrom (override - A):',C.ClassName);
if C is TB then
CreateFrom(TB(C))
else
inherited CreateFrom(C);
FValue := Value + 70000000;
end;
test code...
procedure TestCreateFrom;
var
AClass: TAClass;
BClass: TBClass;
CClass: TCClass;
o: TA;
a: TA;
b: TB;
c: TC;
begin
c := TC.Create; b := c; a := c;
AClass := TAClass(a.ClassType);
BClass := TBClass(a.ClassType);
CClass := TCClass(a.ClassType);
WriteLn('===AClass.MakeCopy======================');
o := AClass.MakeCopy(a); o.Show; o.Free;
o := AClass.MakeCopy(b); o.Show; o.Free;
o := AClass.MakeCopy(c); o.Show; o.Free;
SignUti.Pause;
WriteLn('===BClass.MakeCopy======================');
o := BClass.MakeCopy(a); o.Show; o.Free;
o := BClass.MakeCopy(b); o.Show; o.Free;
o := BClass.MakeCopy(c); o.Show; o.Free;
SignUti.Pause;
WriteLn('===CClass.MakeCopy======================');
o := CClass.MakeCopy(a); o.Show; o.Free;
o := CClass.MakeCopy(b); o.Show; o.Free;
o := CClass.MakeCopy(c); o.Show; o.Free;
SignUti.Pause;
WriteLn('=================================');
c.Free;
end;
and output...
===AClass.MakeCopy====================== TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ ===BClass.MakeCopy====================== TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ ===CClass.MakeCopy====================== TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________ TC.CreateFrom (override - A):TC TC.CreateFrom (override):TC TC.CreateFrom:TC TB.CreateFrom:TC TA.CreateFrom:TC Class:TC Value:75111111 __________________________
you can notice here, how the implementation of (virtual) constructors with calls to derived version before inherited one, let derived class "route" construction path depending on the "true" dynamic type of arguments ...
I confess that i found the name "metaclass" quite strange at first ...
Other languages adopt a more strict "everything is an object" approach, and while every object "is-a" instance of its class, classes in turns are "true" objects inherited from a TObject.
The special metaclass "TClass" could be in the "is-a-(instance-of)" relation root (short-circuited: TClass isa TClass).
In particular, we could have
Sending a "new" message to an "ordinary" class would return an object instance of its type, while a "meta" class "new" method (with a list of classes to inherit from as parameter...) would return a new class type. This class has behavior (class methods) defined in its metaclass, while the objects created by the class inherit behavior (object methods) from the list of classes given as parameters...
This could seem more uniform, and metaclass types could be extended in a orthogonal direction respect to the object they instantiate...
Maybe a dynamic type system is required here, and static "checking" (and speed) don't mix well with this model ....
Yes, having classes as instance of a Com metaclass type could seem a nice thing, but what happens if you need also a SOAP support? You may need multiple (meta)-implementation inheritance...
And class type values? What would mean class value upcasting ? If object of a TFoo class are a kind of objects of TBar type, would it be legal to "cast" a value of TFoo class reference into a TBar reference ?
Quite complex...
Delphi adopts a (very) much simpler solution:
In Delphi, there is only one "hierarchy" of classes and, while different, objects and metaclasses have corresponding ancestors in inheritance tree. Class virtual method table (VMT) is shared by object and class virtual methods and, with single inheritance, upcast resolves in a simple interpretation of a class pointer to a pointer of an higher type (with a "virtual" slicing of VMT). Constructors are the only place where you find a form of type "variance", in fact you may see a (virtual) constructor as a class (and object sometimes) function, returning a new instance of most derived type, but it is completely safe to consider this returned type as a higher type.
But what about COM, XXX, YYY metaclass inheritance?
Being able to treat class types as "first-class" data (with type safety provided by inheritance...), Delphi, wisely, stays away from multiple (implementation) inheritance, and following its "principles" introduce a (strange) form of delegation.
If you look at the generated support code for a simple Com object, you may notice that some code was added in the initialization section of implementing unit.
Something similar to
initialization
begin
TComObjectFactory.Create(ComServ.ComServer, TFirst, Class_First,
'First','First',ciMultiInstance, tmApartment);
TComObjectFactory.Create(ComServ.ComServer, TSecond, Class_Second,
'Second','Second',ciMultiInstance, tmApartment);
TComObjectFactory.Create(ComServ.ComServer, TThird, Class_Third,
'Third','Third',ciMultiInstance, tmApartment);
end;
an instance of T...ObjectFactory is created with a class type and a GUID as a parameter, why?
A Com factory object "implements" com support for your class but it "is-not-a" your class, but equally important your class is not a kind of T...ObjectFactory class (metaclass in the "everything" model ...).
But what is a class for? It must be able to "create" instances of its type, and T...ObjectFactory is a little too generic for that ... so it has to "delegate" to its metaclass field (that stores your class type) for object creation.
Briefly, your class gets Com support by delegating (via metaclass...) another object that delegates back to your class object creation.
Factories are stored (or better, they self store on creation) in a T...Server singleton object, also created in initalization...
"Initialization" is indeed a "meta" feature of Delphi. Here you can build your type repositories, with help of type values and RTTI, considering the well defined initialization call order. You can also have a "meta-compiler hook" provided by InitProc variable, where you can chain a procedure to execute at the end of all initialization sections of you units.
And what if now you need also Corba support... It would be as easy as adding another T...Factory in your initialization ...
No need for multiple inheritance (that was turned upside-down...)
and interfaces ???
classes does not supports interfaces (what GetInterface should do?) but you can write a little delegation ...
interface
type
IMyObject = interface
['{DDF6CE40-55D1-11D5-B57E-00AA00ACFD08}']
procedure AMethod;
end;
IMyObjectClass = interface
['{DDF6CE41-55D1-11D5-B57E-00AA00ACFD08}']
function CreateInstance: IMyObject;
end;
type
TMyObjectBase = class(TInterfacedObject)
public
class function CreateInstance: IMyObject; virtual; abstract;
end;
TMyObjectBaseClass = class of TMyObjectBase;
TMyDelegator = class(TInterfacedObject,IMyObjectClass)
private
FMyObjectClass: TMyObjectBaseClass;
function CreateInstance: IMyObject;
constructor Create(AMyObjectClass: TMyObjectBaseClass);
public
class function CreateInterfaced(AMyObjectClass: TMyObjectBaseClass): IMyObjectClass;
end;
type
TFooObject = class(TMyObjectBase,IMyObject)
private
procedure AMethod;
public
class function CreateInstance: IMyObject; override;
end;
TBarObject = class(TMyObjectBase,IMyObject)
private
procedure AMethod;
public
class function CreateInstance: IMyObject; override;
end;
procedure Test;
implementation
{ TMyDelegator }
class function TMyDelegator.CreateInterfaced(
AMyObjectClass: TMyObjectBaseClass): IMyObjectClass;
begin
result := Create(AMyObjectClass); //hides Free method ...
end;
constructor TMyDelegator.Create(AMyObjectClass: TMyObjectBaseClass);
begin
inherited Create;
FMyObjectClass := AMyObjectClass;
end;
function TMyDelegator.CreateInstance: IMyObject;
begin
result := FMyObjectClass.CreateInstance;
end;
{ TFooObject }
procedure TFooObject.AMethod;
begin
WriteLn(ClassName,'.AMethod');
end;
class function TFooObject.CreateInstance: IMyObject;
begin
result := Create;
end;
{ TBarObject }
procedure TBarObject.AMethod;
begin
WriteLn(ClassName,'.AMethod');
end;
class function TBarObject.CreateInstance: IMyObject;
begin
result := Create;
end;
///////////////////////////////////////////////////////////////////////////////
{ Test }
procedure Test;
var
a: IMyObjectClass;
b: IMyObjectClass;
x: IMyObject;
y: IMyObject;
begin
a := TMyDelegator.CreateInterfaced(TFooObject);
b := TMyDelegator.CreateInterfaced(TBarObject);
x := a.CreateInstance;
y := b.CreateInstance;
x.AMethod;
y.AMethod;
end;
end.
(Delphi classes have a Rock soul...)
more fun?
interfaces and events ( "OP Style" only...)
...
unit SignSauce;
interface
type
TGetStrEvent = function: string of object;
TBindEvent = function(Seq: Integer; Value: string; LazyValue: TGetStrEvent): string of object;
TCallEvent = function(Seq: Integer): string of object;
IBinder = interface
['{4F3B8451-4F00-11D5-B579-00AA00ACFD08}']
function GetEvent: TCallEvent;
property Event: TCallEvent read GetEvent;
end;
TBinder = class(TInterfacedObject,IBinder)
private
FEvent: TBindEvent;
FValue: string;
FLazyValue: TGetStrEvent;
function Callable(Seq: Integer): string;
function GetEvent: TCallEvent;
public
destructor Destroy; override;
class function Bind(Event: TBindEvent; Value: string; LazyValue: TGetStrEvent): IBinder;
end;
TValue = class(TObject)
private
FValue: string;
FLazyValue: string;
function GetValue: string;
function GetLazyValue: string;
public
constructor Create(AValue,ALazyValue: string);
property Value: string read GetValue write FValue;
property LazyValue: string read GetLazyValue write FLazyValue;
end;
TFoo = class(TObject)
public
function AMethod(Seq: Integer; Value: string; LazyValue: TGetStrEvent): string;
end;
TBar = class(TObject)
public
function BMethod(Seq: Integer; Value: string; LazyValue: TGetStrEvent): string;
function CMethod(Seq: Integer; Value: string; LazyValue: TGetStrEvent): string;
end;
procedure Test;
implementation
uses
SysUtils,SignUti;
{ TBinder }
class function TBinder.Bind(Event: TBindEvent; Value: string;
LazyValue: TGetStrEvent): IBinder;
var
o: TBinder;
begin
o := TBinder.Create;
o.FEvent := Event;
o.FValue := Value;
o.FLazyValue := LazyValue;
result := o;
end;
function TBinder.Callable(Seq: Integer): string;
begin
result := FEvent(Seq,FValue,FLazyValue);
end;
destructor TBinder.Destroy;
begin
WriteLn('Bye');
inherited;
end;
function TBinder.GetEvent: TCallEvent;
begin
result := Callable;
end;
{ TValue }
constructor TValue.Create(AValue, ALazyValue: string);
begin
FValue := AValue;
FLazyValue := ALazyValue;
end;
function TValue.GetLazyValue: string;
begin
WriteLn('TValue.GetLazyValue:',FLazyValue);
Result := FLazyValue;
end;
function TValue.GetValue: string;
begin
WriteLn('TValue.GetValue:',FValue);
Result := FValue;
end;
{ TFoo }
function TFoo.AMethod(Seq: Integer; Value: string;
LazyValue: TGetStrEvent): string;
begin
WriteLn('TFoo.AMethod:',IntToStr(Seq));
result := ClassName + ':'+Value+'-'+LazyValue;
end;
{ TBar }
function TBar.BMethod(Seq: Integer; Value: string;
LazyValue: TGetStrEvent): string;
begin
WriteLn('TBar.BMethod:',IntToStr(Seq));
result := ClassName + ':'+Value+'-'+LazyValue;
end;
function TBar.CMethod(Seq: Integer; Value: string;
LazyValue: TGetStrEvent): string;
begin
WriteLn('TBar.CMethod:',IntToStr(Seq));
result := ClassName + ':'+Value+'-'+LazyValue;
end;
Test
///////////////////////////////////////////////////////////////////////////////
{ Test }
procedure TestCall(ABinder: IBinder; Seq: Integer);
var
s: string;
begin
WriteLn('');
s := ABinder.Event(Seq);
WriteLn('result:',s);
WriteLn('');
end;
procedure TestSauce;
var
XValue: TValue;
YValue: TValue;
AFoo: TFoo;
ABar: TBar;
A1: IBinder;
A2: IBinder;
A3: IBinder;
A4: IBinder;
begin
XValue := TValue.Create('X','*X*');
YValue := TValue.Create('Y','*Y*');
AFoo := TFoo.Create;
ABar := TBar.Create;
WriteLn('===Bind=============================');
A1 := TBinder.Bind(AFoo.AMethod,XValue.Value,XValue.GetLazyValue);
A2 := TBinder.Bind(AFoo.AMethod,YValue.Value,YValue.GetLazyValue);
A3 := TBinder.Bind(ABar.BMethod,XValue.Value,XValue.GetLazyValue);
A4 := TBinder.Bind(ABar.CMethod,YValue.Value,YValue.GetLazyValue);
WriteLn('===Call=============================');
TestCall(A1,1);
TestCall(A2,2);
TestCall(A3,3);
TestCall(A4,4);
WriteLn('===Free=============================');
ABar.Free;
AFoo.Free;
YValue.Free;
XValue.Free;
end;
Output...
===Bind============================= TValue.GetValue:X TValue.GetValue:Y TValue.GetValue:X TValue.GetValue:Y ===Call============================= TFoo.AMethod:1 TValue.GetLazyValue:*X* result:TFoo:X-*X* TFoo.AMethod:2 TValue.GetLazyValue:*Y* result:TFoo:Y-*Y* TBar.BMethod:3 TValue.GetLazyValue:*X* result:TBar:X-*X* TBar.CMethod:4 TValue.GetLazyValue:*Y* result:TBar:Y-*Y* ===Free============================= Bye Bye Bye Bye
i find this quite funny, because implementing classes aren't implementing anything, parameter are stored in a reference counted object, that must know only method signature and property getter can be passed instead of property value.
"DNI: a Delphi Native Interface ?"
one reason of success of "is" and "as" operators is in their length!
but they don't work for metaclasses
well, not exactly...
procedure TestIsClass(AClass: TClass);
var
XClass: TBClass;
begin
{
if AClass is TB then // is operator not applicable ...
begin
XClass := AClass as TB; // as operator not applicable ...
WriteLn(XClass.ClassName);
end;
}
{
if AClass is TBClass then // is operator not applicable ...
begin
XClass := AClass as TBClass; // as operator not applicable ...
WriteLn(XClass.ClassName);
end;
}
{
if AClass.InheritsFrom(TB) then
begin
XClass := TB(AClass); // Incompatible Types TBClass and TB
WriteLn(XClass.ClassName);
end;
}
if AClass.InheritsFrom(TB) then
begin
XClass := TBClass(AClass);
WriteLn(XClass.ClassName,' is a kind of ',TB.ClassName);
end
else
WriteLn(AClass.ClassName,' isn''t a kind of ',TB.ClassName);
end;
procedure TestMetaClass;
var
a: TA;
b: TB;
c: TC;
begin
WriteLn('=== Class Test================');
TestIsClass(TA);
TestIsClass(TB);
TestIsClass(TC);
a := TA.Create; b := TB.Create; c := TC.Create;
WriteLn('=== Object Test================');
TestIsClass(a.ClassType); // a is a TA
TestIsClass(b.ClassType);
TestIsClass(c.ClassType);
a.Free; b.Free; c.Free;
end;
and they don't work with interfaces ...
here it is a little more complex ...
the "as" operator is indeed a "wrapper" around "QueryInterface" function, raising a "Interface not supported" exception when the query fails.
you have an alternative way to get an interface ...
the "supports" (overloaded) function
"Supports" comes in two versions, one is for "querying" an interface for another one, the other is for "getting" an interface from an object
and a "is" operator ?
maybe it's better to consider two problems separately:
there are also conflicting "type" values for interfaces:
Suppose we adopt TGUID (Delphi already provides an __uuidof intrinsic (implicit) functionality for type literals)
where we can hope to find type information from a pointer? maybe from its offset ...
another problem is to reach RTTI data with a GUID key
AFAIK, there is no way to do that ... so let's build a repository ...
Could this be enough for Jim Coplien's problem ?.
IReal = interface(IComplex) TComplex = class(TReal,IComplex) // private interface inheritance needed ???
Anyway...
unit MirNative;
interface
uses
TypInfo,SysUtils,Classes;
type {public}
INativeObject = interface
['{98A21171-52D2-11D5-B57D-00AA00ACFD08}']
function _IID(ISelf: INativeObject): TGUID;
end;
IIntfType = interface(INativeObject)
['{98A21172-52D2-11D5-B57D-00AA00ACFD08}']
function IID: TGUID;
function InterfaceUnit: string;
function InterfaceName: string;
function InheritsFrom(const IID: TGUID): Boolean;
end;
IRepository = interface(INativeObject)
['{98A21173-52D2-11D5-B57D-00AA00ACFD08}']
procedure RegisterInterface(IntfTypePtr: PTypeInfo);
function GetIType(const IID: TGUID): IIntfType;
end;
function IIDOF(ISelf: INativeObject): TGUID;
function IType(ISelf: INativeObject): IIntfType;
function IRep: IRepository;
type {protected}
TNativeObject = class(TInterfacedObject,INativeObject)
protected
function _IID(ISelf: INativeObject): TGUID;
class function GetInterfaceEntryByOffSet(
Offset: Integer): PInterfaceEntry;
end;
TIntfType = class(TNativeObject,IIntfType)
private
FInfo: PTypeInfo;
FData: PTypeData;
constructor Create(IntfTypePtr: PTypeInfo);
protected
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function IID: TGUID;
function InterfaceUnit: string;
function InterfaceName: string;
function InheritsFrom(const IID: TGUID): Boolean;
end;
TRepository = class(TNativeObject,IRepository)
private
FList: TStringList; // a RAD container ...
procedure SortList;
protected
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
constructor Create;
destructor Destroy; override;
procedure RegisterInterface(IntfTypePtr: PTypeInfo);
function GetIType(const IID: TGUID): IIntfType;
end;
implementation
uses
ComObj, // for GuidToString ...
ActiveX; // for IsEqualGuid ...
{ TNativeObject }
function TNativeObject._IID(ISelf: INativeObject): TGUID;
var
Offset: Integer;
InterfaceEntry: PInterfaceEntry;
begin
OffSet := integer(ISelf) - integer(Self);
InterfaceEntry := GetInterfaceEntryByOffSet(Offset);
if InterfaceEntry <> nil then
result := InterfaceEntry^.IID
else
raise Exception.Create('never ...');
end;
class function TNativeObject.GetInterfaceEntryByOffSet(Offset: Integer): PInterfaceEntry;
asm
PUSH EBX
MOV EBX,EAX
@@1: MOV EAX,[EBX].vmtIntfTable
TEST EAX,EAX
JE @@4
MOV ECX,[EAX].TInterfaceTable.EntryCount
ADD EAX,4
@@2: CMP EDX,[EAX].TInterfaceEntry.IOffset.Integer[0]
JE @@5
@@3: ADD EAX,type TInterfaceEntry
DEC ECX
JNE @@2
@@4: MOV EBX,[EBX].vmtParent
TEST EBX,EBX
JE @@4a
MOV EBX,[EBX]
JMP @@1
@@4a: XOR EAX,EAX
@@5: POP EBX
end;
{ TIntfType }
constructor TIntfType.Create(IntfTypePtr: PTypeInfo);
begin
FInfo := IntfTypePtr;
FData := GetTypeData(FInfo);
end;
function TIntfType.IID: TGUID;
begin
result := FData^.Guid;
end;
function TIntfType.InterfaceName: string;
begin
result := string(FInfo^.Name);
end;
function TIntfType.InterfaceUnit: string;
begin
result := string(FData^.IntfUnit);
end;
function TIntfType.InheritsFrom(const IID: TGUID): Boolean;
var
pInfo: PTypeInfo;
pData: PTypeData;
begin
result := false;
pInfo := FInfo;
while pInfo <> nil do
begin
pData := GetTypeData(pInfo);
if IsEqualGuid(pData.Guid,IID) then
begin
result := true;
break;
end;
pInfo := nil;
if pData.IntfParent <> nil then
pInfo := pData.IntfParent^
end;
end;
function TIntfType._AddRef: Integer;
begin
result := -1;
end;
function TIntfType._Release: Integer;
begin
result := -1;
end;
{ TRepository }
constructor TRepository.Create;
begin
FList := TStringList.Create;
end;
destructor TRepository.Destroy;
var
i: Integer;
begin
for i := 0 to FList.Count -1 do
FList.Objects[i].Free;
FList.Free;
inherited;
end;
function TRepository.GetIType(const IID: TGUID): IIntfType;
var
i: Integer;
begin
i := FList.IndexOf(GuidToString(IID));
if i <> -1 then
result := TIntfType(FList.Objects[i])
else
result := nil;
end;
procedure TRepository.RegisterInterface(IntfTypePtr: PTypeInfo);
var
o: TIntfType;
begin
o := TIntfType.Create(IntfTypePtr);
FList.AddObject(GuidToString(o.IID),o);
end;
procedure TRepository.SortList;
begin
FList.Sorted := true;
end;
function TRepository._AddRef: Integer;
begin
result := -1;
end;
function TRepository._Release: Integer;
begin
result := -1;
end;
function IIDOF(ISelf: INativeObject): TGUID;
begin
result := ISelf._IID(ISelf);
end;
function IType(ISelf: INativeObject): IIntftype;
begin
result := IRep.GetIType(IIDOF(ISelf));
end;
var
IntfRepository: TRepository;
function IRep: IRepository;
begin
if IntfRepository = nil then
IntfRepository := TRepository.Create;
result := IntfRepository;
end;
var
SaveInitProc: Pointer;
procedure InitRepository;
begin
if SaveInitProc <> nil then TProcedure(SaveInitProc);
if Assigned(IntfRepository) then
IntfRepository.SortList;
end;
initialization
begin
SaveInitProc := InitProc;
InitProc := @InitRepository;
IRep.RegisterInterface(TypeInfo(INativeObject));
IRep.RegisterInterface(TypeInfo(IIntfType));
IRep.RegisterInterface(TypeInfo(IRepository));
end;
finalization
begin
if Assigned(IntfRepository) then
IntfRepository.Free;
end;
end.
...
unit MirTest;
interface
uses
TypInfo,SysUtils,MirNative;
type
IBase = interface(INativeObject)
['{98A21174-52D2-11D5-B57D-00AA00ACFD08}']
end;
IADerived = interface(IBase)
['{98A21175-52D2-11D5-B57D-00AA00ACFD08}']
procedure AMethod;
end;
IBDerived = interface(IBase)
['{98A21176-52D2-11D5-B57D-00AA00ACFD08}']
procedure BMethod;
end;
ICDerived = interface(IADerived)
['{98A21177-52D2-11D5-B57D-00AA00ACFD08}']
procedure CMethod;
end;
type
TAObject = class(TNativeObject,IADerived)
public
procedure AMethod;
end;
TBObject = class(TNativeObject,IBDerived)
public
procedure BMethod;
end;
TCObject = class(TNativeObject,ICDerived)
public
procedure AMethod;
procedure CMethod;
end;
procedure Test;
implementation
uses
ActiveX,ComObj,MirUti;
{ TAObject }
procedure TAObject.AMethod;
begin
WriteLn('--- ',ClassName,'.AMethod');
end;
{ TBObject }
procedure TBObject.BMethod;
begin
WriteLn('--- ',ClassName,'.BMethod');
end;
{ TCObject }
procedure TCObject.AMethod;
begin
WriteLn('--- ',ClassName,'.AMethod');
end;
procedure TCObject.CMethod;
begin
WriteLn('--- ',ClassName,'.CMethod');
end;
initialization
begin
IRep.RegisterInterface(TypeInfo(IBase));
IRep.RegisterInterface(TypeInfo(IADerived));
IRep.RegisterInterface(TypeInfo(IBDerived));
IRep.RegisterInterface(TypeInfo(ICDerived));
end;
end.
...
///////////////////////////////////////////////////////////////////////////////
{ Test }
procedure TestBase(b: IBase);
var
IID: TGUID;
InterfaceType: IIntfType;
begin
IID := IIDOF(b);
WriteLn('... IID OF (b: IBase):',GuidToString(IID));
InterfaceType := IType(b);
WriteLn('... Is A:',Format('%s.%s',
[InterfaceType.InterfaceUnit, InterfaceType.InterfaceName]));
if InterfaceType.InheritsFrom(IADerived) then
begin
WriteLn('... A Kind Of: IADerived');
IADerived(b).AMethod;
end;
if InterfaceType.InheritsFrom(IBDerived) then
begin
WriteLn('... A Kind Of: IBDerived');
IBDerived(b).BMethod;
end;
if InterfaceType.InheritsFrom(ICDerived) then
begin
WriteLn('... A Kind Of: ICDerived');
ICDerived(b).AMethod;
ICDerived(b).CMethod;
end;
WriteLn('');
end;
procedure Test;
var
a: IADerived;
b: IBDerived;
c: ICDerived;
x: IBase;
y: IBase;
z: IBase;
begin
a := TAObject.Create;
b := TBObject.Create;
c := TCObject.Create;
x := TAObject.Create as IADerived;
y := TBObject.Create as IBDerived;
z := TCObject.Create as ICDerived;
WriteLn('=== a TAObject ==================');
TestBase(a);
WriteLn('=== a TBObject ==================');
TestBase(b);
WriteLn('=== a TCObject ==================');
TestBase(c);
Pause;
WriteLn('=== a TAObject ==================');
TestBase(x);
WriteLn('=== a TBObject ==================');
TestBase(y);
WriteLn('=== a TCObject ==================');
TestBase(z);
Pause;
end;
...
=== a TAObject ==================
... IID OF (b: IBase):{98A21175-52D2-11D5-B57D-00AA00ACFD08}
... Is A:MirTest.IADerived
... A Kind Of: IADerived
--- TAObject.AMethod
=== a TBObject ==================
... IID OF (b: IBase):{98A21176-52D2-11D5-B57D-00AA00ACFD08}
... Is A:MirTest.IBDerived
... A Kind Of: IBDerived
--- TBObject.BMethod
=== a TCObject ==================
... IID OF (b: IBase):{98A21177-52D2-11D5-B57D-00AA00ACFD08}
... Is A:MirTest.ICDerived
... A Kind Of: IADerived
--- TCObject.AMethod
... A Kind Of: ICDerived
--- TCObject.AMethod
--- TCObject.CMethod
...
What is the big problem with this approach ?
There is the big assumption that behind an interface always lays a Delphi object.
But this is contrary to "IStar" philosophy ...
Then, after a trip on one of the coolest place on the net i figure out why ...
Again, mixing OOP and M$ is always a mess!
From: "Deepak Shenoy"Newsgroups: borland.public.delphi.non-technical References: <3beec056$1_2@dnews> Subject: Re: Interfaces and 'is' operator... Date: Mon, 12 Nov 2001 10:35:47 +0530 Xref: dnews borland.public.delphi.non-technical:264012 > Also, as of Delphi 5, GetInterfaceEntry doesn't support the full interface > ancestry. If interface 'c' descends from interface 'b', and 'b' descends > from 'a', querying a class that implements 'c' doesn't know that interface > 'a' is supported. This needs to be addressed as well. This was something Danny Thorpe talked about during his session in BorCon 2001. He said that COM expects you to DENY ancestor interfaces not directly implemented by an object - which includes ancestor interfaces. In this example if object O implements interface C like O = class( TInterfacedObject, C) then a QI for B *should* fail. To get around this, one must declare the class as O=class(TInterfacedObject, C, B) - then it would work. The issue was, IIRC, the problem with how Microsoft handled classfactory queries. Microsoft code queries FIRST for IClassFactory, if that fails it queries for IClassFactory2. Which is stupid, but we can't change that - so any class that implements ICLassFactory2 should DENY IClassFactory otherwise it'll never get queried for IClassFactory2. (Context: IClassFactory2 is derived from IClassFactory) The Borland implementation gives you flexibility - since there is denial of undeclared interface (read: any interface not directly specified in the classes declaraation) you can choose to deny or accept whatever you want. Think about it - this way we can solve the Microsoft issue AND wherever we want, we specify ancestor interfaces to avoid denial. I think this approach also saves space in the Interface table.... There's one point though. The fact ancestor interfaces are denied if not declared does not mean that the methods in such ancestor interfaces can be left unimplemented - the compiler REQUIRES that you implement all methods. The Interface denial is very necessary to get around the Microsoft ClassFactory problem, and potentially problems in other such requests. -- Deepak Shenoy Agni Software http://www.agnisoft.com
| Next | Up.. |