简体   繁体   English

在运行时创建接口实现实例

[英]Creating an interface implementer instance at runtime

First, a little explanation about my situation: 首先,对我的情况做一点解释:

I have a sample interface which is implemented by different classes, and these classes might not always have a shared ancestor: 我有一个由不同类实现的示例接口,这些类可能并不总是具有共享的祖先:

  IMyInterface = interface
    ['{1BD8F7E3-2C8B-4138-841B-28686708DA4D}']
    procedure DoSomething;
  end;

  TMyImpl = class(TInterfacedPersistent, IMyInterface)
    procedure DoSomething;
  end;

  TMyImp2 = class(TInterfacedObject, IMyInterface)
    procedure DoSomething;
  end;

I also have a factory method which is supposed to create an instance of an object which implements my interface. 我还有一个工厂方法,它应该创建一个实现我的接口的对象的实例。 My factory method receives the class name as its parameter: 我的工厂方法接收类名作为其参数:

  function GetImplementation(const AClassName: string): IMyInterface;

I tried two approaches to implement this factory method, the first one was using extended RTTI: 我尝试了两种方法来实现这个工厂方法,第一种是使用扩展RTTI:

var
  ctx : TRttiContext;
  t : TRttiInstanceType;
begin
  t := ctx.FindType(AClassName).AsInstance;
  if Assigned(t) then
    Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface as IMyInterface;
end;

In this approach I am calling the default constructor which is fine in my scenario. 在这种方法中,我调用默认构造函数,这在我的场景中很好。 The problem with this is, at runtime, I get an error telling me the object does not support IMyInterface. 这个问题是,在运行时,我得到一个错误告诉我该对象不支持IMyInterface。 What's more, the created object is not assigned to an interface variable; 更重要的是,创建的对象未分配给接口变量; therefore, it will be leaked. 因此,它将被泄露。 I also tried returning the value using TValue.AsType method, but it gives me Access Violation: 我也尝试使用TValue.AsType方法返回值,但它给了我访问冲突:

function GetImplementation(const AClassName: string): IMyInterface;
var
  ctx : TRttiContext;
  rt : TRttiInstanceType;
  V : TValue;
begin
  rt := ctx.FindType(AClassName).AsInstance;
  if Assigned(rt) then
  begin
    V := rt.GetMethod('Create').Invoke(rt.MetaclassType, []);
    Result := V.AsType<IMyInterface>;
  end;
end;

.

The second approach I tried was using a generic dictionary to hold pairs of , and provide registration, unregistration methods: 我尝试的第二种方法是使用通用字典来保存对,并提供注册,取消注册方法:

  TRepository = class
  private
    FDictionary : TDictionary<string, TClass>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetImplementation(const AClassName: string): IMyInterface;
    procedure RegisterClass(AClass: TClass);
    procedure UnregisterClass(AClass: TClass);
  end;

Here I implemented GetImplementation method as this: 这里我实现了GetImplementation方法:

function TRepository.GetImplementation(const AClassName: string): IMyInterface;
var
  Obj : TObject;
begin
  if FDictionary.ContainsKey(AClassName) then
  begin
    Obj := FDictionary[AClassName].Create;
    Obj.GetInterface(IMyInterface, Result);
  end;
end;

This works fine, and I can call DoSomething method using the returned value of GetImplementation, but it still has the memory-leak problem; 这工作正常,我可以使用GetImplementation的返回值调用DoSomething方法,但它仍然存在内存泄漏问题; Obj which is created here is not assigned to any interface variable; 在此创建的Obj未分配给任何接口变量; therefore, it is not reference-counted, and is leaked. 因此,它不是参考计数,而是泄露。

.

Now, my actual question: 现在,我的实际问题:

So my question is, how can I safely create an instance of a class which implements my interface at runtime? 所以我的问题是,如何安全地创建一个在运行时实现我的接口的类的实例? I saw Delphi Spring Framework, and it provides such functionality in its Spring.Services unit, but it has its own reflection routines and lifetime management models. 我看到了Delphi Spring Framework,它在Spring.Services单元中提供了这样的功能,但它有自己的反射例程和生命周期管理模型。 I am looking for a lightweight solution, not a whole 3rd-party framework to do this for me. 我正在寻找一个轻量级的解决方案,而不是一个完整的第三方框架来为我做这个。

Regards 问候

The first case using the RTTI give you a access violation because the TRttiContext.FindType(AClassName) cannot find the Rtti info for the classes which are not registered or used explicity in the app. 使用RTTI的第一种情况会给您一个访问冲突,因为TRttiContext.FindType(AClassName)无法找到未在应用中注册或明确使用的类的Rtti信息。

So you can change your code to 所以你可以改变你的代码

function GetImplementation(AClass: TClass): IMyInterface;
var
  ctx : TRttiContext;
  t : TRttiInstanceType;
begin
  t := ctx.GetType(AClass).AsInstance;
  if Assigned(t) then
    Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface As IMyInterface;
end;

and call in this way 并以这种方式打电话

AClass:=GetImplementation(TMyImp2);

Now if you want to use the Class name to invoke the class, using a list (like your TRepository class) to register the classes is a valid aproach. 现在,如果您想使用类名来调用该类,则使用列表(如您的TRepository类)来注册类是一种有效的方法。 about the memory leak i'm pretty sure which is caused because the TMyImpl class is derived from the TInterfacedPersistent which not implement reference counting directly like the TInterfacedObject . 关于内存泄漏我敢肯定,因为这是造成TMyImpl类是从派生TInterfacedPersistent它没有实现直接引用类的计数TInterfacedObject

This implementation of the the TRepository must works ok. TRepository这种实现必须正常。

constructor TRepository.Create;
begin
  FDictionary:=TDictionary<string,TClass>.Create;
end;

destructor TRepository.Destroy;
begin
  FDictionary.Free;
  inherited;
end;

function TRepository.GetImplementation(const AClassName: string): IMyInterface;
var
  Obj : TObject;
begin
  if FDictionary.ContainsKey(AClassName) then
  begin
    Obj := FDictionary[AClassName].Create;
    Obj.GetInterface(IMyInterface, Result);
  end;
end;

{
or using the RTTI
var
  ctx : TRttiContext;
  t : TRttiInstanceType;
begin
  t := ctx.GetType(FDictionary[AClassName]).AsInstance;
  if Assigned(t) then
    Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface As IMyInterface;
end;
}

procedure TRepository.RegisterClass(AClass: TClass);
begin
  FDictionary.Add(AClass.ClassName,AClass);
end;

procedure TRepository.UnregisterClass(AClass: TClass);
begin
  FDictionary.Remove(AClass.ClassName);
end;

I think I would opt for the second option, mainly because I prefer to avoid RTTI unless it is the only possible solution to a problem. 我想我会选择第二种选择,主要是因为我更愿意避免使用RTTI,除非它是问题的唯一可行解决方案。

But in both your proposed options you state that 但是在你提出的两个选项中都说明了这一点

the object which is created here is not assigned to any interface variable 此处创建的对象未分配给任何接口变量

That's simply not true. 这根本不是真的。 In both cases you assign to Result which has type IMyInterface . 在这两种情况下,您都将分配给具有IMyInterface类型的Result If you have a memory leak, it is caused by some other code, not by this code. 如果您有内存泄漏,它是由其他一些代码引起的,而不是由此代码引起的。

And @RRUZ has found the cause of the leak – namely using TInterfacedPersistent which does not implement reference counted lifetime management. 并且@RRUZ找到了泄漏的原因 - 即使用TInterfacedPersistent ,它不实现引用计数生命周期管理。 Your code won't leak for TInterfacedObject . 您的代码不会泄漏TInterfacedObject

For what it is worth, I would assign directly to the interface variable rather than via an object reference, but that is just a matter of stylistic preference. 对于它的价值,我会直接分配给接口变量而不是通过对象引用,但这只是风格偏好的问题。

if FDictionary.TryGetValue(AClassName, MyClass) then
  Result := MyClass.Create as IMyInterface;

You can do it using extended RTTI and TObject's GetInterface method: 你可以使用扩展的RTTI和TObject的GetInterface方法来做到这一点:

function GetImplementation(const AClassName: string): IMyInterface;
var
  ctx: TRttiContext;
  t : TRttiInstanceType;
  obj: TObject;
begin
  Result := nil;
  t := ctx.FindType(AClassName).AsInstance;
  if Assigned(t) then begin
    obj := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsObject;
    obj.GetInterface(IMyInterface, Result)
  end;
end;

It won't work if the object overrides QueryInterface to do custom processing, but both TInterfacedPersistent and TInterfacedObject rely on GetInterface. 如果对象重写QueryInterface以进行自定义处理,它将无法工作,但TInterfacedPersistentTInterfacedObject都依赖于GetInterface。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM