Amazon.com Widgets TVirtualInterface: Interfaces without an Implementing Class

TVirtualInterface: Interfaces without an Implementing Class

By Nick at June 10, 2012 05:37
Filed Under: Delphi, Software Development

I would be really disappointed if we spent an hour discussing software development and I didn’t at least once say to you "Program against interfaces, not against implementations".  It’s sort of a mantra for me – a way of life if you will.  It’s a bedrock principle of development as far as I’m concerned. Programing against interfaces and not against implementations is the only way to truly decouple your code.  And if you don’t believe that decoupling your code is important, I’d like you to stop reading right now, get a small ball-peen hammer, and keep hitting yourself in the forehead until you change your mind.

So if you’ve made it this far, you’ll know that implementing interfaces is needed to use them.  You have to put some code behind an interface to make it do anything.  Normally, this is done with an implementing class:

type
  ISprocketProcessor = interface
    procedure ProcessSprockets;
  end;

  TSprocketProcessor = class(TInterfacedObject, ISprocketProcessor)
    procedure ProcessSprockets;
  end;

But what if you could implement an interface without having a specific class?  What if there were a way to implement any interface with a single module of code? What if you could decide at runtime how to implement an interface?  Would I even be asking these questions and writing this if those things weren’t possible?

So, yes, obviously it is.  Delphi XE2 introduces a very cool new class called TVirtualInterface. Its purpose is to allow you to descend from it and respond to any interface dynamically.  If you think about that for a second you’ll realize that this is pretty powerful. For instance, it’s the thing that lets the awesome Delphi Mocks Framework provide a mock implementation of any interface that you pass to it.

Generally, when you implement an interface, you provide a class that implements it in a specific way – i.e. that implementation becomes static at runtime. There are ways – typically via Dependency Injection – that allow you to choose an implementation, but even then you are limited to a specific set of implementations.   TVirtualInterface allows you to dynamically -- i.e. at runtime – determine how an interface will be implemented.  The examples in this article will show a fixed way to implement TVirtualInterface so you can get the basics of how to use TVirtualInterface.  In a future article, we’ll look at more dynamic ways to use this sweet class.

Here’s the public interface for TVirtualInterface:

  { TVirtualInterface: Creates an implementation of an interface at runtime.
    All methods in the Interface are marshaled through a generic stub function
    that raises the OnInvoke event.}
  TVirtualInterface = class(TInterfacedObject, IInterface)
 ...
  public
    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    { Create an instance of TVirtualInterface that implements the methods of
      an interface.  PIID is the PTypeInfo for the Interface that is to be
      implemented. The Interface must have TypeInfo ($M+). Either inherit from
      IInvokable, or enable TypeInfo for the interface. Because this is an
      TInterfacedObject, it is reference counted and it should not be Freed directly.
      }
    constructor Create(PIID: PTypeInfo); overload;
    constructor Create(PIID: PTypeInfo; InvokeEvent: TVirtualInterfaceInvokeEvent); overload;
    destructor Destroy; override;
    { OnInvoke: Event raised when a method of the implemented interface is called.
      Assign a OnInvoke handler to perform some action on invoked methods.}
    property OnInvoke: TVirtualInterfaceInvokeEvent read FOnInvoke write FOnInvoke;
  end;

First, you should notice that TVirtualInterface descends from TInterfacedObject and implements IInterface.  The three methods of IInterface are implemented to allow the class to be properly reference counted like any other interface-implementing class.  Second, the interface that you want to implement with it needs to have TypeInfo enabled for it.  The easiest way to make that happen is to descend your interface from IInvokable.  Otherwise, you’ll need to use the {$M+} switch for your interface. Notice, too, that the comment above the class declaration says what I said early – using TVirtualInterface will let you, well, do anything you want with an interface at runtime.  Cool. 

So, in order to actually make TVirtualInterface do something, you need to create a descendent class and provide two things:  a constructor and an implementation of the DoEvent method.

Here’s an TVirtualInterface descendent that is as simple an example as I could think of:

type
  TSimplestVirtualInterface = class(TVirtualInterface)
    constructor Create(PIID: PTypeInfo);
    procedure DoInvoke(Method: TRttiMethod;  const Args: TArray<TValue>; out Result: TValue);
  end;

constructor TSimplestVirtualInterface.Create(PIID: PTypeInfo);
begin
  inherited Create(PIID, DoInvoke);
end;

procedure TSimplestVirtualInterface.DoInvoke(Method: TRttiMethod;
  const Args: TArray<TValue>; out Result: TValue);
begin
  WriteLn('You called a method on an interface');
end;

The only thing this will do is to write out to the console – no matter what.  You can pretend it implements any interface, and no matter what you call on that interface, it will merely write to the console. 

The constructor takes a single parameter, PIID, which is is the TypeInfo for the interface you want to “implement”.  (That’s why the interface in question must have the {M+} switch – usually via IInvokable.)  Inside the constructor, a call to a different constructor is made.  The PIID parameter is passed in, as is a reference to DoInvoke, which is a method that matches the type TVirtualInterfaceInvokeEvent.  Thus, the constructor is basically saying “Here is the type of information for the interface I’m implementing, and the method you should run whenever a method is called on that interface.”

In our case, the DoInvoke method only does one thing no matter what – writes to the console.

So, for instance, say you declare an interface:

type
  IGoStop = interface(IInvokable)
  ['{3B2171B0-D1C3-4A8C-B09E-ACAC4D625E57}']
    procedure Go;
    procedure Stop(aInteger: integer);
  end;

And then you run the following code in a console application:

   GoStop := TSimplestVirtualInterface.Create(TypeInfo(IGoStop)) as IGoStop;
   GoStop.Go;
   GoStop.Stop(42);

the console output will look like this:

image

 

You see the output twice because the code called two methods.  It doesn’t matter what you call – that output will happen no matter what.  It doesn’t matter what interface you pass in or what method you call, the result will be the same.

But of course that isn’t useful – to be of any use, you need to be able to know what is getting called, what is getting passed in, and respond accordingly.

Well, you can do that, because if you look at the signature of the DoInvoke method, you’ll note that when it gets called by the TVirtualInterface.OnInvoke event, it gets passed the RTTI for the method that was called, an array of TValue that contain the interface itself and all the arguments that the method was passed, as well as an out parameter of type TValue that allows you to return a value if the method being called is a function. 

So how about we simply use the DoInvoke event to report out what all that information that it receives.

procedure TReportingVirtualInterface.DoInvoke(Method: TRttiMethod;
  const Args: TArray<TValue>; out Result: TValue);
var
  Arg: TValue;
  ArgType, ArgName: string;
  TempKind: TTypeKind;
begin
  Write('You called the ', Method.Name, ' method ');
  if Length(Args) > 1 then
  begin
    Writeln('and it has ', Length(Args) - 1,' parameters:');
    for Arg in Args do
    begin
      TempKind := Arg.Kind;
      if TempKind <> tkInterface then
      begin
        ArgName := Arg.ToString;
        ArgType := Arg.TypeInfo.Name;
        Writeln(ArgName, ' which is of the type ', ArgType);
      end;
    end;
  end else
  begin
    Writeln(' and it has no parameters.');
  end;
end;

This code simply looks over and reports out about the Method and Args parameters that get passed in when the interface is invoked.  The very first item in the array is always type information about the interface itself, and the rest are the parameters, in order, as they were passed in.  Here, the code simply reports out their values and types, but of course, you can process them as you please. 

Again, that is interesting information, but it’s just a step towards understanding how TVirtualInferface works.  Let’s create something that actually does what we want it to.

Here’s a basic interface:

type
  IActuallyUseful = interface
  ['{16F01BF0-961F-4461-AEBE-B1ACB8D3F0F4}']
    procedure SayHello;
    function ReverseString(aString: string): string;
    function Multiply(x, y: integer): integer;
  end;

Then, below is the DoInvoke method for a TActuallyUseful class that will actually do what the interface purports to do:

procedure TActuallyUseful.DoInvoke(Method: TRttiMethod;
  const Args: TArray<TValue>; out Result: TValue);
begin
  if UpperCase(Method.Name) = 'SAYHELLO' then
  begin
    WriteLn('Hello World!');
  end else
  begin
    if UpperCase(Method.Name) = 'REVERSESTRING' then
    begin
      Result := ReverseString(Args[1].AsString)
    end else
    begin
      if UpperCase(Method.Name) = 'MULTIPLY' then
      begin
          Result := Args[1].AsInteger * Args[2].AsInteger;
      end else
      begin
        raise Exception.Create('Bad Parameter name was passed in to the DoInvoke method');
      end;
    end;
  end;
end;

This code should be fairly self-explanatory.  It simply checks for the name of the method that was called, and then executes code using the parameter information passed in the Args parameter.  If the call is a function, the Result parameter is used to return a value.   

You should  remember that the initial item in the Args array (that is, the one at the “zero-eth” position) is the interface type itself.  The code also makes assumptions about the number and types of the parameters.  Since the code can only be invoked by the methods declared on IActuallyUseful interface, the code can make safe assumptions about the types and order of the parameters.

Now all of this code above is fairly straight-forward – we are basically simulating an implementing class in our examples.  Nothing is truly dynamic.  The code here merely shows simple examples of how TVirtualInterface works in a fairly static way. You should now be able to see how you could dynamically implement an interface using a TVirtualInterface descendent.

Coding against interfaces is critical for writing good, clean code.  TVirtualInterface is just another powerful tool in your toolbox for doing just that.

blog comments powered by Disqus

My Book

A Pithy Quote for You

"We make men without chests and expect of them virtue and enterprise. We laugh at honor and are shocked to find traitors in our midst. We castrate and then bid the geldings to be fruitful."    –  C. S. Lewis

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.