How to write Delphi Wizards
Wizard Compatibility Issues
Delphi Wizards and Expert that use ToolServices must include the ShareMem unit as first unit in their main project uses clause.
This is due to the fact that a special DELPHIMM.DLL Memory Manager DLL must be loaded and installed as the Wizard's memory manager before anything related to "memory" has happened.
This includes working with any class, Long Strings or other heap related operations.
So far so good.
However, C++Builder also uses a memory manager for the same purposes: called BCBMM.DLL.
These DLLs are not 100% compatible, so if we have a esStandard Wizard laoding in C++Builder, asking for ToolServices.ProjectName and DELPHIMM.DLL is loaded (instead of BCBMM.DLL) then we get a lot of Access Violations.
The same is true when we try it the other way around.
So, we must somehow override the ShareMem unit, find out - when our Wizard is loading - which IDE is loading the Wizard: Delphi or C++Builder, and manually (i.e. explicitly) load the required DLL and install it as our new memory manager by hand.
There is one problem left: how do we detect the right IDE?
Remember that we cannot use any class or Long String operation, since we're installing the new memory manager (and this is something that can only be done once each session).
The solution is to use the
function GetCommandLine: PChar;define in kernel32.dll. SInce this function returns the command-line stored in a PChar, we can just walk through this command-line and look for 'BCB'. If we found 'BCB', then we can conclude that we're loaded by Borland C++Builder. Otherwise, we just believe it's Delphi:
unit ShareMem;
{ (c) 1997 by Bob Swart (aka Dr.Bob - http://home.pi.net/~drbob/ }
interface
const
Delphi: Boolean = True; { can be used outside the unit as well }
Version: Integer = 2; { C++Builder = 2 }
SysGetMem: function(Size: Integer): Pointer = nil;
SysFreeMem: function(P: Pointer): Integer = nil;
SysReallocMem: function(P: Pointer; Size: Integer): Pointer = nil;
GetHeapStatus: function: THeapStatus = nil;
GetAllocMemCount: function: Integer = nil;
GetAllocMemSize: function: Integer = nil;
DumpBlocks: procedure = nil;
implementation
uses
Windows;
const
Handle: THandle = 0;
const
SharedMemoryManager: TMemoryManager = (
GetMem: nil;
FreeMem: nil;
ReallocMem: nil);
function GetCommandLine: PChar; stdcall;
external 'kernel32.dll' name 'GetCommandLineA';
var
P: PChar;
i: Integer;
initialization
P := GetCommandLine;
i := 0;
repeat
Inc(i)
until (P[i] = #0) or
((P[i] = 'B') and (P[i+1] = 'C') and (P[i+2] = 'B'));
Delphi := P[i] = #0;
if not Delphi then
begin
Handle := LoadLibrary('BCBMM.DLL');
{ if Handle = 0 then
MessageBox(HWnd(0),'Error: could not load BCBMM.DLL',
nil,MB_OK or MB_ICONHAND); }
@DumpBlocks := GetProcAddress(Handle, 'DumpBlocks');
@SysGetMem := GetProcAddress(Handle, '@System@SysGetMem$qqri');
@SysFreeMem := GetProcAddress(Handle, '@System@SysFreeMem$qqrpv');
@SysReallocMem := GetProcAddress(Handle, '@System@SysReallocMem$qqrpvi');
end
else { Delphi }
begin
if (FindWindow('TApplication', 'Delphi 2.0') > 0) then Version := 2;
if (FindWindow('TApplication', 'Delphi 3') > 0) then Version := 3;
Handle := LoadLibrary('DELPHIMM.DLL');
{ if Handle = 0 then
MessageBox(HWnd(0),'Error: could not load DELPHIMM.DLL',
nil,MB_OK or MB_ICONHAND); }
@SysGetMem := GetProcAddress(Handle, 'SysGetMem');
@SysFreeMem := GetProcAddress(Handle, 'SysFreeMem');
@SysReallocMem := GetProcAddress(Handle, 'SysReallocMem');
end;
@GetHeapStatus := GetProcAddress(Handle, 'GetHeapStatus');
@GetAllocMemCount := GetProcAddress(Handle, 'GetAllocMemCount');
@GetAllocMemSize := GetProcAddress(Handle, 'GetAllocMemSize');
SharedMemoryManager.GetMem := @SysGetMem;
SharedMemoryManager.FreeMem := @SysFreeMem;
SharedMemoryManager.ReallocMem := @SysReallocMem;
SetMemoryManager(SharedMemoryManager);
finalization
FreeLibrary(Handle)
end.
With this new ShareMem unit, we can compile a Wizard with Delphi, and use it in the C++Builder IDE without any problems!
Note that in case we're loaded by Delphi, we even try to find out the version number of Delphi, i.e. whether we're loaded by Delphi 2.0x or Delphi 3.
This is important for the following portability issue regarding Delphi/C++Builder Wizards: the version of the TInterface class.
For Delphi 2.0x and C++Builder, TInterface.GetVersion returns 2.
For Delphi 3, however, TInterface.GetVersion returns 3.
And if we just compile a Wizard with Delphi 3, we can't run it in Delphi 2 and vice versa.
Reminds me a bit of the InitExpert export declaration, remember?
Only this time it may be a bit harder to fix...
The only way I could make thing work, was by hacking into VIRTINTF.PAS, and making sure TInterface.GetVersion returns the version number that ShareMem detected when it was looking for the Delphi or C++Builder IDE:
unit VirtIntf;
interface
type
TInterface = class
private
FRefCount: Longint;
public
constructor Create;
procedure Free;
function AddRef: Longint; virtual; stdcall;
function Release: Longint; virtual; stdcall;
function GetVersion: Integer; virtual; stdcall;
end;
{ TIStream - This provides a pure virtual interface to a physical stream }
TIStream = class(TInterface)
public
function Read(var Buffer; Count: Longint): Longint; virtual; stdcall; abstract;
function Write(const Buffer; Count: Longint): Longint; virtual; stdcall; abstract;
function Seek(Offset: Longint; Origin: Word): Longint; virtual; stdcall; abstract;
function GetModifyTime: Longint; virtual; stdcall; abstract;
procedure SetModifyTime(Time: Longint); virtual; stdcall; abstract;
procedure Flush; virtual; stdcall; abstract;
end;
function ReleaseException: string;
implementation
uses
SysUtils, ShareMem;
{ TInterface }
constructor TInterface.Create;
begin
inherited Create;
FRefCount := 1;
end;
procedure TInterface.Free;
begin
if Self <> nil then Release;
end;
function TInterface.AddRef: Longint;
begin
Inc(FRefCount);
Result := FRefCount;
end;
function TInterface.Release: Longint;
begin
Dec(FRefCount);
Result := FRefCount;
if FRefCount = 0 then Destroy;
end;
function TInterface.GetVersion: Integer;
begin
Result := ShareMem.Version
end;
{ Exception handling }
function ReleaseException: string;
begin
Result := Exception(ExceptObject).Message;
end;
end.
This works only when either Delphi 2.0x or Delphi 3 is loaded. When both are loaded, then the reported GetVersion will be 3. So, when Delphi 3 is loaded, it's not possible to load Delphi 2.0x as well, since any Wizard using the above units will detect the Delphi IDE, but with the "3" version, which will result in a verion-failure when trying to load the Wizards. Oh well, who wants to use Delphi 3 and Delphi 2.0x at the same time anyway?
FWIW, with the new SHAREMEM.PAS and VIRTINTF.PAS I've compiled my Collection of Delphi/C++Builder Wizards - using Delphi 3 - into a single binary-compatible DRBOB.DLL (of roughly 800 Kbytes).