views:

194

answers:

3

Hi, I'm storing small interfaces from a range of objects into a single TInterfaceList 'store' with the intention of offering list of specific interface types to the end user, so each interface will expose a 'GetName' function but all other methods are unique to that interface type. For example here are two interfaces:

  IBase = interface
    //----------------------------------------
    function GetName : string;
    //----------------------------------------
  end;

  IMeasureTemperature = interface(IBase)
    //------------------------------------
    function MeasureTemperature : double;
    //----------------------------------------
  end;

  IMeasureHumidity = interface(IBase)
    //----------------------------------------
    function MeasureHumidity: double;
    //----------------------------------------
  end;

I put several of these interfaces into a single TInterfaceList and then I'd like to scan the list for a specific interface type (e.g. 'IMeasureTemperature') building another list of pointers to the objects exporting those interfaces. I wish to make no assumptions about the locations of those objects, some may export more than one type of interface. I know I could do this with a class hierarchy using something like:

  If FList[I] is TMeasureTemperature then ..

but I'd like to do something simliar with an interface type, Is this possible?

+5  A: 

Just use Supports, like this:

var
  oMTIntf: IMeasureTemperature;
...
  If Supports(FList[I], IMeasureTemperature, oMTIntf) then .. 
da-soft
@da-soft: Nice suggestion but it fails when a single class exports two different interfaces - 'Supports' returns true in that case for each. I need some way of identifying the actual type of the interface.
Brian Frost
Sorry, seems I missed your key point. Do you need to get object from interface, or to get interface from interface ? If first, then http://www.malcolmgroves.com/blog/?p=500. If second, then as I wrote (I have edited my message).
da-soft
@Brian: Why is that a problem? If one of the two interfaces descends from the other, then check for the descendant class before the parent class. And if they don't, then both results are equally valid.
Mason Wheeler
Err... check for the descendant *interface* before the parent *interface* I mean.
Mason Wheeler
@Brian: if you are working with interfaces, it should not matter if a concrete class implements both interfaces. If it does matter, that might be a sign of a design flaw.
Lieven
By the way, there are also two-argument versions of `Supports` that allow you to omit the `oMTIntf` parameter if you don't need it. They match most closely to Delphi's `is` operator.
Rob Kennedy
What you guys don't get is that he may add the same object's in the interface list twice, once for IMeasureTemperature, and once for IMeasureHumidity. If he scan the list for IMeasureHumidity and use "Supports", he will get 2 hits on the same object. It would be better to implements in 2 different lists, but if his design requires it...
Ken Bourassa
@Ken: That's right. I wanted a solution for uniquely identifying the exact interface type to offer the user, eg all 'MeasureTemperature' types, wherever they may be hosted. The 'Supports' solution (nice as it is) returns a hit on 'MeasureHumidity' if it is hosted on the same class as a 'MeasureTemperature'. I guess the solution is to separate the implementor classes, that would work at the pain of increased calling complexity.
Brian Frost
@Brian: if the requirement is that there should be no duplicate `class instances` in the list, add a ClassType method to IBase and check on that.
Lieven
@Lieven: Yes, I think I'll simply add an ID function to the interface type to identify it. It would be nice if there was an RTTI function to convert IMyInterface to its GUID...
Brian Frost
@Brian: even if it would be possible, it wouldn't do you any good. Both interfaces have different GUID's.
Lieven
+2  A: 

You may use the Supports function in SysUtils, they are pretty safe (unless you try them on unitialized memory) and you only need a target variable of the exact interface type you try to cast to:


procedure DoSomethingInList(AList: IInterfaceList;);
var
  i: Integer;
  liItem: IInterface;
  liMeasureTemp: IMeasureTemperature;
  liMeasureHumi: IMeasureHumidity;
begin
  AList.Lock;
  try
    for i := 0 to AList.Count - 1 do
    begin
      liItem := AList[i];
      if Supports(liItem, IMeasureTemperature, liMeasureTemp) then
        //... liMeasureTemp.MeasureTemperature ...
      else if Supports(liItem, IMeasureHumidity, liMeasureHumi) then
        //... liMeasureHumi.MeasureHumidity ...
      else 
        //...
    end;
  finally
    AList.Unlock;
  end;
end; 
Viktor Svub
+1  A: 

I guess that might satisfy your needs.

function InterfaceRefIsInterface(Intf : IUnknown; ExpectedIntf : TGUID) : Boolean;
var vReference : IUnknown;
begin
  if Supports(Intf, ExpectedIntf, vReference)  then
    Result := Intf = vReference
  else
    Result := False;
end;

I'm not sure how the function will behave when Intf and ExpectedIntf inherits from one another, but this will return TRUE in the case Intf is an exact match of ExpectedIntf.

In your exemple, IMeasureHumidity won't return true on IMeasureTemperature, but I'm not sure how it will react to IBase. According to preliminary testing, it will also return FALSE on IBase.

Ken Bourassa
Except when `ExpectedIntf` is `IUnknown`, there is no guarantee that `Intf` will ever equal `vReference`. The [rules for implementing QueryInterface](http://msdn.microsoft.com/en-us/library/ms686590.aspx) promise that a call to QueryInterface will succeed, but they don't require any particular interface pointer be returned.
Rob Kennedy
From your own link "a call to QueryInterface with IID_IUnknown must always return the same physical pointer value" and "Any interface must be able to query for any other interface on an object". I'm not sure which rule you refer that would make this fail.
Ken Bourassa
@Ken: This works fine and does exactly what I was hoping for, although I'd need to look very hard at it to see what It's doing!There will never be a case where the inherited IBase interface would be passed. Many Thanks.
Brian Frost