IMetal


 Led Zeppelin, Deep Purple, Metallica ...

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..