Delphi 实现全局接口

Delphi 实现全局接口,delphi,dll,interface,Delphi,Dll,Interface,我目前正在努力解决以下问题: 我需要创建两个不同的DLL,它们的功能完全相同,但它们的数据库不同。这两个数据库完全不同 我的dll应该处理与这些不同数据库的通信。 这样主程序就可以选择要使用的dll 我想确保每个dll都有完全相同的进程/函数/。。。 我在考虑使用接口。 但我不知道如何创建全局接口。dll属于同一个项目组。您要做的是创建一个COM组件项目。为一个DB定义方法和实现。然后创建第二个使用相同接口的COM组件。我相信您是在“小题大做”,认为您需要两个不同的DLL。但是如果您选择我建议的

我目前正在努力解决以下问题:

我需要创建两个不同的DLL,它们的功能完全相同,但它们的数据库不同。这两个数据库完全不同

我的dll应该处理与这些不同数据库的通信。 这样主程序就可以选择要使用的dll

我想确保每个dll都有完全相同的进程/函数/。。。 我在考虑使用接口。
但我不知道如何创建全局接口。dll属于同一个项目组。

您要做的是创建一个COM组件项目。为一个DB定义方法和实现。然后创建第二个使用相同接口的COM组件。

我相信您是在“小题大做”,认为您需要两个不同的DLL。但是如果您选择我建议的最后一个选项,您会发现在2 DLL解决方案和1 DLL解决方案之间切换相当容易

选择1 这是最直接的:

  • 创建一个新单元
  • 添加DLL接口(导出)
  • 在两个项目中都包括该单元

    单位:NTF

    接口

    使用 DllExportImpl

    出口DoX

    实施

    结束

请注意,此单元使用的是
DllExportImpl
,这两个项目中也必须包括该单元。但是,您需要在文件系统中的两个不同位置使用相同名称的两个不同文件。因此,每个DLL项目将有不同的实现

现在,每当您对接口进行更改时,在更新每个
DllExportImpl
单元之前,您的项目都不会编译

我不太喜欢这个解决方案,因为它需要具有相同名称但不同行为的单元。既然您打算将两个DLL都放在同一个项目组中:我应该警告您,我已经体验到IDE被重复的单元名称弄糊涂了

选择2 将导出放置到共享的包含文件中

library DllSharedExportsImpl1;

uses
  DllExportImpl1 in 'DllExportImpl1.pas';

{$I ..\Common\DllExports.inc}
DllExports.inc文件将仅包含导出子句。例如

exports DoX;
这样做的好处是,现在每个DLL可以为不同的实现使用不同的单元名称。如果更改include文件,则在更新其实现单元以适应更改之前,两个项目都不会编译

请注意,这确实会带来一系列问题。方法包括工作:编译器在编译时有效地将包含文件的内容推入单元。因此,IDE的第7行与编译器完全不同。此外,编辑包含文件可能会有点麻烦,因为只能确定包含文件的位置,这使得编辑器支持非常不切实际

选择3 此选项的工作量稍大,但提供了更好的长期可维护性

您可以通过多态对象实现接口来实现这一点。这样,两个DLLProject也将共享实际导出的例程。当每个DLL初始化时,它设置要使用的具体实现

您的DLL接口可能看起来像这样

unit DllExportIntf;

interface

type
  TAbstractImpl = class(TObject)
  public
    procedure DoX; virtual; abstract;
  end;

procedure AssignDllImpl(const ADllImpl: TAbstractImpl);

procedure DoX;
exports DoX;

implementation

var
  GDllImpl: TAbstractImpl;

procedure AssignDllImpl(const ADllImpl: TAbstractImpl);
begin
  if Assigned(GDllImpl) then
  begin
    GDllImpl.Free;
  end;
  GDllImpl := ADllImpl;
end;

procedure DoX;
begin
  GDllImpl.DoX;
end;

end.
library Dll1;

uses
  DllSharedIntf in '..\Common\DllSharedIntf.pas';
初始化DLL时,可以调用:

AssignDllImpl(TDllImpl_1.Create);
这种方法的一个明显优点是,如果两个DLL之间存在任何公共代码,则可以将其包含在基本实现中。此外,如果您可以以不需要更改
TAbstractImpl
的方式更改现有方法DLL,则可能只需要重新编译DLL

此外,如果需要更改现有的虚拟抽象方法,则必须相应地更新具体实现中的覆盖

警告如果添加新的虚拟抽象方法,您的项目在编译时仍会发出警告,提示您正在使用抽象方法创建对象。但是,您应始终将警告视为错误。如果你这样做,这个警告就不会成为问题


注意:如前所述,使用这种方法,您应该能够相当容易地在单个DLL和2个DLL解决方案之间切换。差异基本上归结为项目中包含哪些单元,以及如何初始化全局变量


还值得一提的是,您甚至可以通过实现一个
句柄
来消除全局变量,以便与每个DLL例程一起使用。(类似于Windows。)请记住,在尝试在DLL和应用程序代码之间传递对象时存在技术问题。这就是为什么您不用传递对象,而是使用“句柄”来传递对象,并在内部封装实际的对象实例。

如果您的问题更多地是关于Delphi的基础知识,那么我添加了另一个答案,这个答案可能比第一个答案对您更有帮助。我的第一个答案集中在让两个DLL公开相同的方法(根据问题的主体)。这一个集中在你问题的最后两句:

但我不知道如何创建全局接口。dll属于同一项目组

基于此,听起来您正在寻找一个选项“将接口标记为全局,以便同一组中的项目可以使用它们”。Delphi不需要特殊功能来实现这一点,因为如果您了解某些基本原则,它就很容易使用

创建新单位时,默认情况下会将其添加到当前项目中。但是,如果您想在多个项目之间共享该单元,最好将其保存到其他文件夹中,以便很容易看到它是共享的。您的第一个DLL项目文件应该如下所示

unit DllExportIntf;

interface

type
  TAbstractImpl = class(TObject)
  public
    procedure DoX; virtual; abstract;
  end;

procedure AssignDllImpl(const ADllImpl: TAbstractImpl);

procedure DoX;
exports DoX;

implementation

var
  GDllImpl: TAbstractImpl;

procedure AssignDllImpl(const ADllImpl: TAbstractImpl);
begin
  if Assigned(GDllImpl) then
  begin
    GDllImpl.Free;
  end;
  GDllImpl := ADllImpl;
end;

procedure DoX;
begin
  GDllImpl.DoX;
end;

end.
library Dll1;

uses
  DllSharedIntf in '..\Common\DllSharedIntf.pas';
您可以在
DllSharedIntf
单元中定义“全局”接口。例如

unit DllSharedIntf;

interface

type
  IDllIntf = interface
    ['{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}']
    procedure DoX;
  end;

implementation

end.
注意:由于接口类型是在单元的接口部分声明的,因此它被视为“全局”,因为其他单元可以使用它。但这是一场灾难
unit DBService1;

interface

uses
  ....;

type
  IService1 = interface
    [....]  // here goes the GUID
    procedure ServiceMethod1;
    procedure ServiceMethod2;
    // and so on...
  end;

implementation

end.
unit SQLServerService1;

interface

uses
  DBService1, ....;

type
  TSQLServerService1 = class(TInterfacedObject, IService1)
  protected // IService1
    procedure ServiceMethod1;
    procedure ServiceMethod2;
    // and so on...
  end;

implementation

procedure TSQLServerService.ServiceMethod1;
begin
  // Specific code for SQL Server
end;

procedure TSQLServerService.ServiceMethod2;
begin
  // Specific code for SQL Server
end;

...

end.
unit SQLServerServiceFactories;

interface

uses
  DBService1;

function NewService1: IService1;

implementation

uses
  SQLServerService1;

function NewService1: IService1;
  Result := TSQLServerService1.Create;
end;

end.
...
var
  service1: IService1;
begin
  service1 := NewService1;

  service1.ServiceMethod1; // here, calling your method!
end;