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

DLL Wizards
Image of Line Break

So far, we've seen how to write our own Standard and Project Expert, and we've created a nice FileOpen Wizard that integrates in the COMPLIB.DCL or DCLUSR30.DPL file. However, we remember the Application and Dialog Experts that came as a DLL. How do we put an Wizard in a DLL?
First of all, we need to write a DLL and not a unit, which leads to the following code (note that I've used compiler directives to be able to switch from a DCU Wizard to a DLL Wizard based on one compiler options).

{$A+,B-,D-,F-,G+,I-,K+,L-,N-,P-,Q-,R-,S+,T-,V-,W-,X+,Y-}
{$IFDEF DLL}
library FileOpen;
{$M 32768,0}
{$ELSE}
unit FileOpen;
interface
{$ENDIF}

Also, when using the 32-bits versions of Delphi, we need to make sure that we use the ShareMem unit as well (very important!). Now, before we continue, it is important to realise that a DLL is different from a DCU in that it must deal with its own exceptions. You are not allowed to let any exception escape the DLL. Even if you think that a Delphi application will be able to deal with this exception (which it will not), your DLL may not even be servicing a Delphi application, but an application written in an entire other language (remember: everyone may do a LoadLibrary of your DLL, and try to call the APIs inside). So, the number one rule for writing DLL Wizards, or any DLLs at all for that matter, is to handle your own exceptions. For this, we need our own exception handler, which is defined as follows (an example on which this was based can be found in EXPTDEMO.DPR in the C:\DELPHI\DEMOS\EXPERTS directory of Delphi 1.0):

  procedure HandleException;
  begin
    if Assigned(ToolServices) then
      ToolServices.RaiseException(ReleaseException)
  end {HandleException};

Which also results in the use of try-except blocks for every method that gets called by something from the outside (all methods of our Wizards, that is):

  function TFileOpenExpert.GetStyle: TExpertStyle;
  begin
    try
      Result := esStandard
    except
      HandleException
    end
  end {GetStyle};

Note that when using this technique, Delphi will give us warnings (if enabled) that these functions now may have an undefined result. This might indeed occur if an exception is raised before the assignment to Result could take place or succeed. However, in that case it was the assignment itself that raised the exception, so I don't really mind.
The TFileOpenExpert Execute method must use the try-except structure as well, of course:

  procedure TFileOpenExpert.Execute;
  begin
    try
      if (ToolServices = nil) then
        MessageDlg('ToolServices not available!', mtError, [mbOk], 0)
      else
      begin
        if not Assigned(FileOpenForm) then { Create Wizard Form }
          FileOpenForm := TFileOpenForm.Create(Application);
        if (FileOpenForm.ShowModal = idOk) then { Action! }
          ToolServices.OpenProject(FileOpenForm.FileListBox.FileName)
        else
          MessageBeep($FFFF)
      end
    except
      HandleException
    end
  end {Execute};

Finally, a DLL Wizard is added (installed and registered) to Delphi in a somewhat different way than a DCU Wizard. For one, it doesn't get integrated into the Component Palette or Package (so we don't need the register procedure anymore), but it'll stay an independent DLL, and can only communicate with Delphi by using a copy of ToolServices. To get this copy, we must define a function called InitExpert that gets called whenever the DLL Wizard is initialised by Delphi.
InitExpert takes three arguments. The first one is the ToolServices of Delphi itself (remember that Delphi itself is the one to call our InitExpert function). If we need ToolServices later on, we must make a copy of it now! The second argument of InitExpert is the RegisterProc callback function of Delphi, which we need to call with the created instance of our Wizard. The third argument of InitExpert is a reference (variable) argument of type TExpertTerminateProc which we need to assign to our exported clean-up procedure (DoneExpert, in this case). For the FileOpen DLL Wizard, the resulting code is as follows:

{$IFNDEF DLL}
  procedure Register;
  begin
    RegisterLibraryExpert(TFileOpenExpert.Create);
    RegisterLibraryExpert(TProjectOpenExpert.Create)
  end {Register};
{$ELSE}
  procedure DoneExpert; export;
  begin
    if Assigned(FileOpenForm) then FileOpenForm.Free;
    FileOpenForm := nil
  end;

  function InitExpert(ToolServices: TIToolServices;
                      RegisterProc: TExpertRegisterProc;
                  var Terminate: TExpertTerminateProc): Boolean; export;
  begin
    ExptIntf.ToolServices := ToolServices; { Save for our local usage!! }
    if ToolServices <> nil then
      Application.Handle := ToolServices.GetParentHandle;
    Terminate := DoneExpert;
    if (@RegisterProc <> nil) then
      Result := RegisterProc(TFileOpenExpert.Create) AND
                RegisterProc(TProjectOpenExpert.Create)
  end {InitExpert};

exports
  InitExpert name ExpertEntryPoint;

begin
{$ENDIF}
end.

Note that we call the function RegisterProc twice, once for the Standard FileOpen Wizard, and once for the Project FileOpen Wizard. Of course, we must make sure that the DCU Wizards are 'un-installed' before we try to install this DLL Wizard, otherwise the duplicate GetIDStrings will cause trouble in the internal Delphi Wizard manager part of the IDE (assuming that's what it's called)...

Compiler Output Wizard

Another useful Wizard would be a Compiler Output "grabber" like this:

The Wizard form above is not important, but the implemenation is based on the FileOpen DLL Wizard:

TCompilerOutputExpert
GetStyle:esStandard
GetIDString:DrBob.TCompilerOutputExpert
GetName:Compiler Output Wizard
GetAuthor (win32):Bob Swart (aka Dr.Bob)
GetMenuText:&Compiler Output Wizard...
GetState:[esEnabled]
GetGlyph:0
GetPage (win32): 
GetComment: 

procedure TStandardExpert.Execute;
begin
  try
    with TDCC32OutputForm.Create(Application) do
    try
      ShowModal
    finally
      Free
    end
  except
    HandleException
  end
end {Execute};

The Compiler Output Wizard is actually quite a handy Wizard that could be useful inside the Delphi 2.0x and C++Builder environments as well. So, let's keep this Wizard in mind for the upcoming esAddIn Experts when porting to these environments

Installation

The installation of DLL Wizards is different from the installation of .DCU Wizards. Since the DLL is entirely self-containting, it does not have to be linked with COMPLIB.DCL or DCLUSR30.DPL on a binary level, but rather in a dynamic way (the name Dynamic Link Library should indicate something to that end, right?).
We install the FILEOPEN.DLL Wizard by adding a new entry in the Registry at the Wizards section.

Delphi will read this at startup, and will get its information regarding Wizards that must be loaded from it. To install a DLL Wizard, we need to modify the Wizards section to add a key with as value the path where the DLL Wizard can be found. If the DLL cannot be found, or the registration inside the DLL fails, then Delphi will tell us so.

DLL vs DCU

Now that we've seen DCU Wizards as well as DLL Wizards, it's time to investigate the difference between these two; which one would be more efficient or pleasant to use? For starters, a DCU Wizard is smaller (it doesn't need to have the entire RTL and VCL linked with itself) and has a tight integration with the Component Library/Package (but a DCU Wizard won't be able to dynamically install DLL Wizards, as we'll see later). On the other hand, a DLL Wizard is bigger, less integrated with Delphi itself (we have no GetClass API, as we'll see later) but a DLL Wizard can be installed rather easy by modifying DELPHI.INI or the registry and in fact will be able to be installed dynamically and install other Project/Form DLL Wizards!
In short, we need some more examples to see when a DCU Wizard is the best choice and when a DLL Wizard is the only one that will work. Until this point, all Wizards that we've written could have been a DLL Wizard just as easy as a DCU Wizard.

Packages

Apart from installing DCU Wizards in the DCLUSR30 package, we can of course also use a special package for all our Wizards, like DrBobX.DPL. Just select File | New and pick the "package" type of new item:

We use the short filename "DrBobX.DPK", and the long description "Dr.Bob's Experts & Wizards Package'.

The generated source code for the DrBobX.DPK package file is as follows (note the Generic and Form Wizards that are already contained within):

package DrBobX;
{.$R *.RES}
{$ALIGN ON}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO OFF}
{$SAFEDIVIDE OFF}
{$STACKFRAMES OFF}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST ON}
{$MINENUMSIZE 1}
{$IMAGEBASE $00400000}
{$DESCRIPTION 'Dr.Bob''s Experts & Wizards Package'}
{$DESIGNONLY}
{$IMPLICITBUILD ON}

requires
  vcl30;

contains
  Generic,
  Form;

end.
One of the great advantages of package-Wizards compared to DLL wizards is that a package can be loaded and unloaded on demand, without having to exit Delphi itself!

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