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.
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:
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.