实施全局接口

时间:2015-08-06 14:13:17

标签: delphi dll interface

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

我需要创建两个不同的DLL,它们完全相同,但正在寻找不同的数据库。这两个DB并不相同。

我的dll应该处理与那些不同数据库的通信。 因此主程序选择他想要使用的dll。

我想确保每个dll具有完全相同的procudes / functions / ... 我在考虑使用接口。 但我无法弄清楚如何创建全局接口。 dll属于同一个项目组。

4 个答案:

答案 0 :(得分:1)

我确实相信你正在制造一个“笨拙的山”,认为你需要2个不同的DLL。但是如果你选择我最后建议的选项,你会发现在2 DLL解决方案和1 DLL解决方案之间切换相当容易。

选项1

这是最直截了当的:

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

    单位DllExportIntf;

    接口

    用途   DllExportImpl;

    导出DoX;

    实施

    端。

请注意,此单元使用DllExportImpl,它也必须包含在两个项目中。但是,您需要在文件系统的2个不同位置使用2个具有相同名称的不同文件。所以每个DLL项目都有不同的实现。

现在,无论何时对界面进行更改,只有在更新了每个DllExportImpl单元后,您的项目才会编译。

我对此解决方案并不特别喜欢需要具有相同名称但行为不同的单位。由于您打算在同一个项目组中同时使用这两个DLL:我应该警告您,我已经让IDE感到困惑,因为重复的单元名称。

选项2

将导出放入共享的包含文件中。

library DllSharedExportsImpl1;

uses
  DllExportImpl1 in 'DllExportImpl1.pas';

{$I ..\Common\DllExports.inc}

DllExports.inc文件只包含您的exports子句。 E.g。

exports DoX;

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

请注意,这确实伴随着一系列问题。方法包括工作:编译器在编译时有效地将包含文件的内容推送到单元中。所以看起来像IDE的第7行与编译器完全不同。编辑包含文件也可能有点令人讨厌,因为上下文只能在包含文件的地方确定,这使编辑器支持变得非常不切实际。

选项3

此选项可以提供更多功能,但可以提供更好的长期可维护性。

您可以通过多态对象实现接口来实现此目的。这样,两个DllProjects也将共享实际导出的例程。当每个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.

初始化DLL时,可以调用:

AssignDllImpl(TDllImpl_1.Create);

这种方法的一个明显优势是,如果您的2个DLL之间有任何公共代码,它可以包含在您的基本实现中。此外,如果您可以以不需要更改TAbstractImpl的方式更改现有方法DLL,则可能只需要重新编译DLL。

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

  

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

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

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

答案 1 :(得分:1)

考虑到所有这些,我相信如果您使用 packages 设计解决方案,而不是DLL,那么您会更成功。一个包是一个DLL,但是富含符号,所以Delphi可以更好地利用它。特别是,包中声明的符号将更容易被您的应用程序加载,具有更高的抽象级别。这是Delphi IDE用于加载组件的内容。

因此,遵循此设计,您必须这样做:

以名为(例如)DBServices.dpk中存在的单位声明您的接口。这是一个这样一个单元的例子:

unit DBService1;

interface

uses
  ....;

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

implementation

end.

所以,上面你创建了一个声明接口的单元。您的应用程序可以在任何地方使用该接口,只需引用应用程序中的包并在其他单元中使用它,您就可以访问声明的符号。

在另一个包的另一个单元中声明该相同接口的实现类,例如,专用于SQLServer(SQLServerServices.dpk):

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.

上面你声明了接口IService1的实现类。现在你有两个包,一个声明接口,另一个实现这些接口。两者都将被您的应用程序使用。如果您有相同接口的更多实现,请添加专用于它们的其他包。

一个重要的事情是:你必须有一个工厂系统。工厂系统是一个过程类,它将从每个包创建并返回应用程序的实现。

因此,就代码而言,在每个服务包(实现接口的服务包)中添加一个名为xxxServiceFactories的单元,如下所示:

unit SQLServerServiceFactories;

interface

uses
  DBService1;

function NewService1: IService1;

implementation

uses
  SQLServerService1;

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

end.

上面的代码声明了一个创建SQL Server实现并将其作为接口返回的函数。现在,如果从返回的接口调用方法,您实际上将为SQL Server调用它的特定实现。

加载包后,您必须以与在DLL工作时相同的方式链接到该功能。获得该函数的指针后,您可以调用它,并且您将在应用程序的代码中使用该接口:

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

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

我在这个答案中描述的模型是我在过去必须处理的类似场景中使用的模型。我提出了有效的一般想法,但你必须了解包和接口的基本原理才能真正掌握这项技术。

对这些问题的全面解释对于答案来说会很长,但我想这对你来说是一个很好的起点!

答案 2 :(得分:0)

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

答案 3 :(得分:0)

关于你的问题更多是关于Delphi的基本原理的可能性,我添加了另一个答案,它可能比第一个答案更有帮助。我的第一个答案集中在获取2个DLL来暴露相同的方法(根据你的问题的主体)。这个重点关注你问题的最后两句:

  

但我无法弄清楚如何创建全局接口。 dll属于同一个项目组。

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

创建新单元时,默认情况下会将其添加到当前项目中。但是,如果要在多个项目之间共享单元,最好将其保存到不同的文件夹,以便很容易看出它是共享的。你的第一个DLLs项目文件看起来应该是这样的。

library Dll1;

uses
  DllSharedIntf in '..\Common\DllSharedIntf.pas';

您可以在DllSharedIntf单元中定义“全局”界面。 E.g。

unit DllSharedIntf;

interface

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

implementation

end.

注意:因为接口类型是在单元的接口部分中声明的,所以它被认为是“全局的”,因为其他单元能够使用它。但这并不会自动将其提供给其他项目。

您现在必须将共享单元添加到其他项目中,以便该项目中的其他单元可以使用它。要做到这一点:

  • 激活Dll2
  • 选择ProjectAdd to Project...
  • 查找DllSharedIntf并添加。

Delphi会自动更新您的项目源文件以包含该单元。

library Dll2;

uses
  DllSharedIntf in '..\Common\DllSharedIntf.pas';

现在,在每个DLL项目中,您可以添加单独的实现单元。例如。对于Dll1:

unit DllImpl1;

interface

uses
  //It's very important to indicate that this unit uses the shared unit.
  //Otherwise you won't be able to access the "global types" declared
  //in the interface-section of that unit.
  DllSharedIntf;

type
  TDllImpl1 = class(TInterfacedObject,
      //Any types defined in the interface-section of any units that
      //this unit **uses**, can be accessed as if they were declared
      //in this unit.
      IDllIntf)
  protected
    //The fact that this class is marked as implementing the IDllIntf
    //means that the compiler will insist on you implementing all 
    //methods defined in that interface-type.
    procedure DoX;
  end;

implementation
  

注意此答案仅涵盖在项目之间共享界面。您仍然需要通过适当的导出来公开DLL的功能。您需要一种类似于other answer的选项3的方法。

摘要

我们通常不会在Delphi中谈论“全局接口”。通常理解,在单元的接口部分中声明的任何内容都是全局可访问的。 (我们确实更多关于全局变量的问题,因为它们有危险;但这是一个完全不同的主题。

在德尔福:

  • 每当您希望一个单元(A)使用另一个单元(B)中定义的功能时,您需要将单元B添加到单元A的使用子句中。
  • 每当您希望项目使用在另一个项目中创建的单元时,您需要将该单元添加到项目中。 (提示:将这些单位放在一个单独的文件夹中是个好主意。

注意:在项目之间共享单元时,项目组实际上是无关紧要的。项目不需要在同一组中共享单元。您需要做的就是确保项目可以访问单元,以便项目中的其他单元可以使用