views:

164

answers:

7

Hi! Look at this sample:

//----------------------------------------------------------------------------
type

  ISomeInterface = interface
    procedure SomeMethod;
  end;

  // this is wrong, but illustrates that, what i need:
  TSomeClassWhichImplementsSomeInterface = class of ISomeInterface;

var
  gHardCodedPointer: Pointer; // no matter

procedure Dummy(ASomeClassToWorkWith: TSomeClassWhichImplementsSomeInterface);
begin
  // actually, type of ASomeClassToWorkWith is unknown (at least TObject), but it
  // must implement SomeMethod, so i can make something like this:
  ASomeClassToWorkWith(gHardCodedPointer).SomeMethod;
end;

...

type

  TMyClass = class(TInterfacedObject, ISomeInterface)
  end;

...

// TMyClass implements ISomeInterface, so i can pass it into Dummy:
Dummy(TMyClass);
//----------------------------------------------------------------------------

Of course i can inherit TMyClass and use it childs, but I don't need this. I want to use another classes with their own hierarchy, just adding into them implementation of ISomeInterface (because there are no multiple-inheritance avaiable in Object Pascal, like in C++). I know it may be looked crazy, don't ask me why I need this, just say - it is possibly to implement or not. Thanks a lot!

A: 

I think you have to use the interface, not the class:

procedure Dummy(ASomeClassToWorkWith: ISomeInterface); 
begin 
  // actually, type of ASomeClassToWorkWith is unknown (at least TObject), but it 
  // must implement SomeMethod, so i can make something like this: 
  ASomeClassToWorkWith.SomeMethod; 
end; 

You just have to think amout reference counting

If you realy want the object instance you could change the interface like this:

type 
  ISomeInterface = interface 
    procedure SomeMethod; 
    function ImplementedInObject: TObject; 
  end; 


procedure Dummy(ASomeInterfaceToWorkWith: ISomeInterface);
var
  ASomeObjectToWorkWith: TObject;
begin 
  ASomeInterfaceToWorkWith.SomeMethod; 
  ASomeObjectToWorkWith := ASomeInterfaceToWorkWith.ImplementedInObject;
  // Do what is needed with object
end; 

... 

type 
  TMyClass = class(TInterfacedObject, ISomeInterface) 
    function ImplementedInObject: TObject; 
  end; 

function TMyClass.ImplementedInObject: TObject;
begin
  Result := Self;
end;
BennyBechDk
No, in that case i must pass to Dummy an object reference (but i need a class of this object).
SomeOne
Added example for returning the Object implementing the interface.(if you only need the class then change then returntype from TObject to TClass)
BennyBechDk
A: 

Why can't you use the interface reference? But, assuming there is a good reason for that, this might help.

As you have found out, you can't do class of on an interface.

What's more you can't use a variable value to cast anything to anything else. Casting is hardwired telling the compiler that you know the reference you are casting is of a specific type. Trying to do that with a var such as your ASomeClassToWorkWith parameter is going to produce errors as it goes against the very nature of casting.

Code below is not something I'd recommend, but it compiles and I think it does what you want. What it does is use a "dummy" ancestor and employs polymorfism to get the compiler to call the method on the correct type. If you do not mark SomeMethod as virtual, you will get the dummy ancestor's message on both button clicks.

The Instance function in the interface is there to show you a means of getting to the implementing instance of an interface without using RTTI. Just be aware of the caveat of this when using interface delegation: you may not get the instance you are expecting.

type
  TForm1 = class(TForm)
    TSomethingBtn: TButton;
    TMyClassBtn: TButton;
    procedure FormCreate(Sender: TObject);
    procedure TSomethingBtnClick(Sender: TObject);
    procedure TMyClassBtnClick(Sender: TObject);
  private
    { Private declarations }
    FSomething: TObject;
    FMyClass: TObject;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TSomething = class; // forward;
  TSomethingClass = class of TSomething;

  ISomeInterface = interface
    procedure SomeMethod;
    function Instance: TSomething;
  end;

  TSomething = class(TInterfacedObject, ISomeInterface)
    procedure SomeMethod; virtual;
    function Instance: TSomething;
  end;

var
  gHardCodedPointer: Pointer; // no matter

procedure Dummy(aSomething: TSomething);
begin
  // actually, type of ASomeClassToWorkWith is unknown (at least TObject), but it
  // must implement SomeMethod, so i can make something like this:
  aSomething.SomeMethod;
end;

type
  TMyClass = class(TInterfacedObject, ISomeInterface)
    procedure SomeMethod; virtual;
    function Instance: TSomething;
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FSomething := TSomething.Create;
  FMyClass := TMyClass.Create;
end;

{ TMyClass }

function TMyClass.Instance: TSomething;
begin
  Result := TSomething(Self);
end;

procedure TMyClass.SomeMethod;
begin
  ShowMessage('This comes from TMyClass');
end;

{ TSomething }

function TSomething.Instance: TSomething;
begin
  Result := Self;
end;

procedure TSomething.SomeMethod;
begin
  ShowMessage('This comes from the "dummy" ancestor TSomething');
end;

procedure TForm1.TMyClassBtnClick(Sender: TObject);
begin
  // Presume this has been set elsewhere
  gHardCodedPointer := FMyClass;
  Dummy(TSomething(gHardCodedPointer));
end;

procedure TForm1.TSomethingBtnClick(Sender: TObject);
begin
  // Presume this has been set elsewhere
  gHardCodedPointer := FSomething;
  Dummy(TSomething(gHardCodedPointer));
end;
Marjan Venema
A: 

Even if you could, you couldn't typecast the interface with a interface-var anyway.

Same as with classes when you typecast a pointer to a metaclass, you'll get something of type metaclass (class of), not something of the type that is in metaclass.

With classes you solve this by typecast to the lowest common class in the hierachy. You can do the same with interfaces. ... If they inherit from eachother.

Marco van de Voort
+1  A: 

I think what you are looking for is this:

procedure Dummy; 
var Intf : ISomeInterface;
begin
  if Assigned(gHardCodedPointer) and Supports(gHardCodedPointer,ISomeInterface,Intf) then
    Intf.SomeMethod
end;

If it's not, I have no clue about what you are trying to achieve there...

Ken Bourassa
+1. I think "SomeOne" was looking for a way to enforce at *compile time* that the class implements the given interface, but that's not possible. It needs to be checked at run time, and using `Supports` is indeed the way to do it.
Rob Kennedy
After taking a 2nd look at his post, what he might be looking for is a way to call a method by name (Like through RTTI). A RTTI solution might be closer to what he needs than an Interface solution.
Ken Bourassa
A: 

It seems I see what you want to do. You just have to use what MS and partners implemented in the core of interfaces, use guids. Below is the example, but you should definitely use your own guid with CTRL+SHIFT+G in IDE

  ...

  type
    ITestInterface = interface
     ['{2EA2580F-E5E5-4F3D-AF90-2BBCD65B917B}']
      procedure DoSomething;
    end;

    TTestObject = class(TInterfacedObject, ITestInterface)
      procedure DoSomething;
    end;

    TTestObject2 = class(TInterfacedObject, ITestInterface)
      procedure DoSomething;
    end;

  ...

  procedure TestMethod(Obj: TInterfacedObject);
  var
    Intf: ITestInterface;
  begin
    if (Obj as IUnknown).QueryInterface(ITestInterface, Intf) = S_OK then
      Intf.DoSomething;
  end;

  { TTestObject }

  procedure TTestObject.DoSomething;
  begin
    MessageDlg('This is TTestObject showing something', mtInformation, [mbOk], 0)
  end;

  { TTestObject2 }

  procedure TTestObject2.DoSomething;
  begin
    MessageDlg('This is TTestObject2 showing something', mtInformation, [mbOk], 0)
  end;

  procedure TForm2.Button1Click(Sender: TObject);
  var
    Obj1, Obj2: TInterfacedObject;
  begin
    Obj1:=TTestObject.Create;
    Obj2:=TTestObject2.Create;

    TestMethod(Obj1);
    TestMethod(Obj2);
  end;
Maksee
A: 

The difference when calling code via interface variable or via variable pointing to an instance of a class that implements methods of the same interface is that different virtual method tables (VMT) are used, i.e. in a VMTs of an interface there will be only interface methods (plus AddRef, Release and QI, of course), in a VMT of a class there will be all virtual methods of that class. That means that your code

ASomeClassToWorkWith(gHardCodedPointer).SomeMethod;

will be compiled to call TSomeClassWhichImplementsSomeInterface.SomeMethod directly instead of virtual method in VMT of ISomeInterface through interface pointer.

Even more, since interfaces cannot declare class methods and class attributes, an interface type is not a object (while class is an object), therefore "class of interface" does not make any sence.

You can add intermediate abstract class and declare you "class of interface" as class of the intermediate class:

type
  TInterfacedObjectWithISomeInterface = class(TInterfacedObject, ISomeInterface)
    procedure SomeMethod; virtual; abstract;
  end;

  TSomeClassWhichImplementsSomeInterface = class of TInterfacedObjectWithISomeInterface;

procedure Dummy(ASomeClassToWorkWith: TSomeClassWhichImplementsSomeInterface);

...

type

  TMyClass = class(TInterfacedObjectWithISomeInterface)
    procedure SomeMethod; override;
  end;
andrius
A: 

You can declare metaclasses, but you cannot define them in terms of what interfaces the base class implements. Interface implementation can only be checked at run time.

You can pass your Dummy function a metaclass, but you cannot use that metaclass to type-cast your plain pointer to a more specific type. Type-casting is a compile-time operation, but the actual value of the metaclass parameter isn't known until run time. The best you can do is type-cast it to the metaclass's base class. Then you can call all the methods that are defined in that base class.

But it seems you don't actually care what the base class is, as long as the class implements your interface. In that case, you can ignore the metaclass parameter. Type-cast your pointer to be a TObject (or, better yet, declare gHardCodedPointer to be a TObject in the first place), and then use the Supports function to get the interface reference.

var
  SupportsInterface: Boolean;
  Some: ISomeInterface;
begin
  SupportsInterface := Supports(TObject(gHardCodedPointer), ISomeInterface, Some);
  Assert(SupportsInterface, 'Programmer stored bad class instance in gHardCodedPointer');
  Some.SomeMethod;
end;

If you really care about the metaclass parameter, you can add some enforcement for it, too. You can check whether the given class implements your interface, and you can check whether the object in gHardCodedPointer is an instance of that class:

Assert(ASomeClassToWorkWith.GetInterfaceEntry(ISomeInterface) <> nil);
Assert(TObject(gHardCodedPointer).InheritsFrom(ASomeClassToWorkWith));

But notice that you don't need to check either of those results to be able to call SomeMethod on gHardCodedPointer. They don't really matter.

By the way, the only hard-coded pointer value you can hope to have in Delphi is nil. All other pointer values are addresses that are very hard to predict at compile time because the compiler, the linker, and the loader all determine where everything really goes in memory. I suggest you come up with some other name for that variable that more accurately describes what it really holds.

Rob Kennedy