Delphi - 创建设计良好的插件

Delphi - Creating Well-Designed Plug-In's

我发现运行良好的代码片段可以轻松创建模块化 Delphi 应用程序。此代码适用于 Delphi 5 版本。

Link 到片段 -> http://delphi.cjcsoft.net/viewthread.php?tid=44129

这些年来我有很大的 Delphi 5 项目构建,我开始怀疑是否可以加载用更新版本的 Delphi 编译的插件 (*.plg)(在我的案例 XE2).

我用 Borland 的 ShareMem 替换了 ShareMemRep.pas 因为 XE2 不能用 ShareMemRep 编译。 Delphi 5 版本的插件和正在加载用 Delphi 5 编写的插件的客户端应用程序目前运行良好。

我使用了相同的代码(除了我不得不将 PAnsiChar 更改为 PWideChar)并使用 Delphi XE2 编译了插件和客户端应用程序。但是已编译的客户端应用程序既无法加载使用 Delphi XE2 编译的插件,也无法加载使用 Delphi 5.

编译的插件

此外 Delphi 5 客户端应用程序无法加载使用 Delphi XE2 编译的插件。

Delphi XE2 客户端未加载使用 Delphi XE2 插件编译,因为 (PlugInClass.GetInterface(PlugInGUID, PlugInIntf)) 返回 False

当 Delphi XE2 或 Delphi 5 客户端应用程序正在加载相反版本的插件编译时,(PlugInClass.GetInterface(PlugInGUID, PlugInIntf)) 会导致访问冲突。

我从 System.pas 中发现,在行的底部进行了一些更改,但我对那种 'hack bits' 的了解是纯粹的。

是否有人知道是否可以加载使用不同版本编译的插件(库),而不是使用代码段中的代码加载该插件的应用程序?

编辑: 我的源代码: https://bitbucket.org/plum/delphimodularapp

关闭插件加载的源代码(管理器):

function TPlugInManager.LoadPlugIn(const AFileName: string;
  PlugInGUID: TGUID; out PlugInIntf; ForceCreate: Boolean = False): Boolean;
var
  FileName: string;
  DLLHandle: THandle;
  FuncPtr: TFarProc;
  PlugInProc: TPlugInProc;
  PlugInClass: TPlugInClass;
  PlugInStruct: PPlugInStruct;
begin
  { initialize variables }
  Result := False;
  FileName := AFileName;
  DLLHandle := 0;
  try
    { try to load passed dll }
    // Delphi XE
    //DLLHandle := LoadLibraryW(PWideChar(AFileName));
    // Loading *.plg plugin
    DLLHandle := LoadLibrary(PAnsiChar(AFileName));
    if DLLHandle <> 0 then
    begin
      { get function address of 'RegisterPlugIn' }
      FuncPtr := GetProcAddress(DLLHandle, 'RegisterPlugIn');
      if FuncPtr <> nil then
      begin
        { assign register method }
        @PlugInProc := FuncPtr;

        { create plugin instance }
        PlugInClass := TPlugInClass(PlugInProc(FOwner, ForceCreate));  // creates instance!
        { the only tricky-part: accessing the common interface }
        if Assigned(PlugInClass) then begin
          // On that line I'm getting AV when I'm trying to load
          // plugin compiled with Delphi XE2 by Host application compiled with Delphi5.
          if (PlugInClass.GetInterface(PlugInGUID, PlugInIntf)) then
          begin
            { save plugin properties }
            New(PlugInStruct);
            PlugInStruct.AClass := PlugInClass;
            PlugInStruct.GUID := PlugInGUID;
            PlugInStruct.Handle := DLLHandle;
            PlugInStruct.AInterface := Pointer(PlugInIntf);
            PlugInStruct.FileName := AFileName;
            FPlugIns.Add(PlugInStruct);
            Result := True;
          end;
        end;

        if Result = False then begin
          FreeLibrary(DLLHandle);
        end;
      end;
    end;    // try/finally
  except
    on e: Exception do
    begin
      FLastError := e.Message;
      if DLLHandle <> 0 then
        FreeLibrary(DLLHandle);
    end;
  end;    // try/except
end;

插件代码(库):

library libSamplePlugIn;

uses
  ShareMem,
  Windows,
  Classes,
  uPlugInIntf in 'uPlugInIntf.pas',
  uSamplePlugInIntf in 'uSamplePlugInIntf.pas',
  uSamplePlugInImpl in 'uSamplePlugInImpl.pas';

{$E plg}  //
{$R *.res}

procedure DllMain(Reason: Integer);
begin
  case Reason of
   { our 'dll' will be unloaded immediantly, so free up the shared
     datamodule, if created before! }
    DLL_PROCESS_DETACH:
     { place your code here! }
  end;
end;

function RegisterPlugIn(AOwner: TComponent;
  ForceCreate: Boolean = False): TSamplePlugIn; stdcall;
begin
  Result := TSamplePlugIn.Create(AOwner);
end;

exports RegisterPlugIn;

begin
end.

和插件class:

unit uSamplePlugInImpl;

interface

uses
  uPlugInIntf, uSamplePlugInIntf, Classes;

type
{ TSamplePlugIn }

  TSamplePlugIn = class(TPlugInClass, ICustomPlugIn, ISamplePlugIn)
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetAuthor: string;
    function GetName: string;
    function GetVersion: string;
    function GetDescription: string;
    function Sum(a, b: Integer): Integer;
  end;

implementation

{ TSamplePlugIn }

constructor TSamplePlugIn.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

{ Rest of implementation ...}

function TSamplePlugIn.Sum(a, b: Integer): Integer;
begin
  Result := a + b;
end;

end.

由于致命的设计缺陷,此代码已损坏。您不能在 DLL 边界上传递 Delphi class 并让它在另一边有含义。当您将 TComponent 传递给 DLL,并且当它 return 是 TSamplePlugIn 时,该规则就被打破了。

您的主要选择:

  1. 切换到运行时包,或者
  2. 使用接口而不是 classes。

你似乎离得不远了。只是停止传递 TComponent。寻找另一种管理生命周期的方法。接口已经为此提供了引用计数。 return 是一个接口而不是 class 实例。那么你应该上路了。

您确实刚刚遵循了那篇文章设定的引导。可悲的是,它似乎是由没有足够专业知识的人编写的。它在 D5 中似乎可以工作,但在那里也被破坏了。你只是不知何故侥幸逃脱了。

请注意,PChar 在 Unicode Delphi 中是 PWideChar,在 ANSI Delphi 中是 PAnsiChar。使用该事实将允许您编写适用于两者的代码。

DLLHandle := LoadLibrary(PChar(AFileName));

DllMain 是虚假的。删除它。

实际上,该信息不正确或至少不完整。如果您在应用程序和插件 dll 中都使用了基于 com 的内存管理器,您可以轻松地传递 class 个实例。一个例子(旧但有效)在这里: http://thaddy.co.uk/commm.pas

顺便说一句:我试图在当时正在进行的内存管理器竞赛中写出最慢的替代品。没用,速度不够慢 ;)