Image of Navigational Map linked to Home / Contents / Search How to write Delphi Wizards

Wizard Compatibility Issues
Image of Line Break

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


Image of Arrow linked to Next Article Image of Arrow linked to Next Article
Image of Line Break
[HOME] [TABLE OF CONTENTS] [SEARCH]