2

I found working well code snippet that allows easily create modular Delphi application. This code is working well with Delphi 5 version.

Link to snippet -> http://delphi.cjcsoft.net/viewthread.php?tid=44129

I have big Delphi 5's project build over the years and I started to wonder if it would be possible to load Plugin (*.plg) compiled with newer version of Delphi (in my case XE2).

I replaced ShareMemRep.pas with Borland's ShareMem because XE2 can't compile with ShareMemRep. Delphi 5 version of plugin and client app that is loading plugins written in Delphi 5 working well so far.

I used the same code (except that I changed PAnsiChar to PWideChar where I had to) and compiled plugin and client app with Delphi XE2. But compiled client app can't load neither plugin compiled with Delphi XE2 nor plugin compiled with Delphi 5.

Also Delphi 5 client app can't load plugin compiled with Delphi XE2.

Delphi XE2 client is not loading compiled with Delphi XE2 plugin because (PlugInClass.GetInterface(PlugInGUID, PlugInIntf)) is returning False.

When Delphi XE2 or Delphi 5 client app is loading opposite version of plugin compilation then (PlugInClass.GetInterface(PlugInGUID, PlugInIntf)) causes Access Violation.

What I found from System.pas that some changes where made in bottom of the line but my knowledge is pure about that kind of 'hack bits'.

Have any one some knowledge whether it is possible to load plugin (library) compiled with different version than app that is loading that plugin using code from snippet?

EDIT: My source code: https://bitbucket.org/plum/delphimodularapp

Source code off plugin loading (manager):

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;

Code of Plugin (Library):

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.

And Plugin 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.
sliwinski.lukas
  • 1,412
  • 1
  • 18
  • 28
  • This question is all about your code. It's not specific to plugins. I mean, a plugin is just a library that you load dynamically. All versions of Delphi can do that. So, it looks very much like you need to do some debugging. Cut it right back to basics and start with a do nothing plugin. I imagine that you have some large framework that needs porting to Unicode. You've attempted this and got it wrong. Nobody here can tell you what you did wrong. – David Heffernan Feb 12 '15 at 12:58
  • I created simple project with code only from snippet that I linked. In short compiled library and client app in Delphi 5 works great. Compiled in Delphi XE2 not. I don't understood why so can someone looking into this snipped code answer if it is even possible. I debugged application and for now what I can say that GetInterface can't load plugin compiled with Delphi XE2 (but have the same code as Delphi 5 which is working) – sliwinski.lukas Feb 12 '15 at 13:10
  • We cannot see any of your code – David Heffernan Feb 12 '15 at 13:16
  • Just to be clear, what do you think the likelihood is of me starting from scratch, reading that article, writing the necessary code, porting to Unicode, and ending up with the same code that you have? So, if the error is one made on your part, then if we attempt to recreate what you have done based on your description, we will likely fail. And waste a significant amount of time. On the other hand, if you had posted your code in complete, but cut down, form, you'd have an answer by now. – David Heffernan Feb 12 '15 at 13:25
  • @J... I'm tring to be specific, but what I can tell for is that PlugInClass.GetInterface(PlugInGUID, PlugInIntf) causes AV when I'm trying to load plugin (*.plg) compiled with DelphiXE2. Where PlugInGUID is interface GUID used by my plugin class. – sliwinski.lukas Feb 12 '15 at 14:25

2 Answers2

4

This code is broken due to a fatal design flaw. You cannot pass a Delphi class across a DLL boundary and have it mean something on the other side. That rule is broken when you pass a TComponent to the DLL, and when it returns a TSamplePlugIn.

Your main options:

  1. Switch to runtime packages, or
  2. Use interfaces instead of classes.

You don't appear to be far off. Just stop passing that TComponent. Find another way to manage lifetime. Interfaces already provide reference counting for that. And return an interface rather than a class instance. Then you should be on your way.

You have indeed just followed the lead set by that article. Sadly it appears to have been written by somebody without sufficient expertise. It might have appeared to work in D5, but it was broken there too. You just got away with it somehow.

Note that PChar is PWideChar in Unicode Delphi and PAnsiChar in ANSI Delphi. Using that fact would allow you to write code that works in both.

DLLHandle := LoadLibrary(PChar(AFileName));

The DllMain is spurious. Remove it.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

Actually, that information is incorrect or at least incomplete. If you would have used a com based memory manager in both the application and the plugin dll'(s) you can comfortably pass class instances. An example (old but working) is here: http://thaddy.co.uk/commm.pas

btw: My attempt to write the slowest replacement in the then ongoing memory manager competition. Didn't work, it isn't slow enough ;)

  • There's more than memory allocation from the same heap. After all, we have ShareMem. You can't get past the fact the type identity doesn't work across module boundaries. There are two instances of RTL, which is one too many. This mistake is frequently made. Why don't you try using `is` on an instance from a different module, and see how that works out. -1 – David Heffernan Feb 12 '15 at 18:19
  • I can admit that in code that I posted, I had earlier have also function that was creating and returning TForm. I used this method and form on Host application and that working good (everything compiled in Delphi5). So some how passed TForm from DLL to app was working properly. – sliwinski.lukas Feb 12 '15 at 19:10
  • @sliwinski.lukas Compiling is not the same as working. Even appearing to work at runtime doesn't prove everthing works correctly. Sharing memory manager with, say, `ShareMem`, gets you a long way. But calling non-virtual methods from the other module is a disaster. And type identity doesn't work at all. – David Heffernan Feb 12 '15 at 19:37
  • Whoa, Thaddy de Koning, the KOL guy? – Free Consulting Feb 12 '15 at 21:25
  • David has obviously not put in any effort to try my code. Shame. There are no two, there is only one and because all management pointers are still there, it works a lot better than intermediate dll's like sharemem. David, COM memory management is basically the same idea as sharemem, only implemented at OS level and better. Try it. Try it through a debugger. Full stop. – user3365085 Mar 02 '15 at 18:32