Factory calls return nil instead of a service

Issue #26 resolved
Philipp Schäfer created an issue

Regarding latest commit in dev branch, tested in Delphi XE3 55b721319ed409024360d4960c68ac04e9d8fde8

If you call a newly introduced factory it calls the implementation constructor, however, it returns nil. It might be related to the VTable hack and interference counting.

The following test program demonstrates the issue:

program SpringFactoriesOfficial;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Spring.Container,
  Spring.Container.Common;

type
  ITestInterface = interface
    ['{A2BC7208-5933-41AC-A56E-5F7D1268F8B0}']
    procedure DoSomething;
  end;

  ITestDependency = interface
    ['{25C6D7D5-C806-4A2D-8FE5-58FFCB21798C}']
    procedure DoSomething;
  end;

  ITestFactory = interface( IFactory<ITestDependency, ITestInterface> )
  end;

  TTestInterfaceImpl = class(TInterfacedObject, ITestInterface)
  strict private
    FTestDependency: ITestDependency;
    procedure DoSomething;
  public
    constructor Create(ATestDependency: ITestDependency);
  end;

  TTestDependencyImpl = class(TInterfacedObject, ITestDependency)
  strict private
    procedure DoSomething;
  end;

procedure Main;
var
  Container: TContainer;
  Factory: ITestFactory;
  Tester: ITestInterface;
  Dependency: ITestDependency;
begin
  Container := TContainer.Create;
  with Container do begin
    // Register
    RegisterType<ITestInterface, TTestInterfaceImpl>;
    RegisterType<ITestDependency, TTestDependencyImpl>;
    RegisterType<ITestFactory>
      .AsFactory;

    // Build
    Build;

    // Resolve
    Dependency := Resolve<ITestDependency>;
    Factory := Resolve<ITestFactory>();

    // Testing:
    Tester := Factory(Dependency);
    // output shows: constructor is called, however, result is of Factory(...) is nil

    // Exception: AV because Tester is nil
    Tester.DoSomething;    
  end;
end;


{ TTestInterfaceImpl }

constructor TTestInterfaceImpl.Create(ATestDependency: ITestDependency);
begin
  FTestDependency := ATestDependency;

  if not Assigned(FTestDependency) then
    raise Exception.Create('Missing dependency: ATestDependency');

  Writeln('TestInterface created!');
end;

procedure TTestInterfaceImpl.DoSomething;
begin
  Writeln('Calling dependend interface:');
  FTestDependency.DoSomething;
end;

{ TTestDependencyImpl }

procedure TTestDependencyImpl.DoSomething;
begin
  Writeln('- TestDependency: Hello world! -');
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

I did not dig deeper into the working principle of the VTable hack and why it is required. (Stefan, you only mentioned that you needed it in the google groups discussion).

As I did my own implementation of TVirtualInterface factories I first had issue with the assignement of the virtual interface as service type without the "as" operator (as type info is only available at runtime). If this is why the cast into an empty interface with modified VTables is done you might also consider the following implementation as an alternative:

type
  TFactoryVirtualInterface = class(TVirtualInterface)
  ...
   ///  <summary>
   ///    Provides a way to query the object as a given interface.
   ///  </summary>
   ///  <typeparam name="I">
   ///    the interface one wants to get served
   ///  </typeparam>
   function CastAs<I: IInterface>: I;
   ...
  end;

implementation

function TFactoryVirtualInterface.CastAs<I>: I;
begin
  QueryInterface(GetTypeData(TypeInfo(I)).GUID, Result);
end;

Comments (1)

  1. Log in to comment