Glover logo

Writing Pegasus Mail Extensions

 Index

Home Glovers Contact Form

Pegasus Mail

Pegasus Mail Pmail Features

Pmail Extensions

Populist Delphi (1) Delphi (2) Dos Email

Digital Cameras

Digital images Renumber images Slideshows

Databases

Online Databases Access & ASP Access & PHP Mysql & ASP Mysql & PHP

Pegasus Mail Extensions: writing Extensions in Delphi.

Pegasus Mail's support for Extensions is a very powerful feature of this excellent mail client. There is currently a degree of interest in using Borland Delphi to write extensions ie 'addons' or 'plugins' for Pegasus Mail and indeed, I have found it very handy. There are two possible approaches:-

1. Use Delphi in the conventional manner with Forms and Components. This method is fast, but produces large DLL and EXE files (at least 180Kbytes in Delphi 1 and Delphi 2), even for trivial procedures. It does have an advantage over C-coded Extensions in that Resource Workshops/Resource Compilers are not needed: the Delphi visual design system takes over instead;

2. Use Delphi, but avoid Forms, Components, Object Oriented code and instead implement Windows-specific procedures from calls to the Windows API. This leads to compact efficient EXEs and DLLs which are more acceptable when offered from a web site. In effect, this approach is similar to conventional coding, but in Delphi's Pascal language rather than in C/C++ - and it compiles in less than half a second. As with Extensions written in C, a Resource Compiler and preferably Resource workshop is needed.

I prefer the second approach, which was used in developing POPULIST and other Pegasus Extensions. This page shows the steps in developing a simple Composer Extension (see below). However, in order to make comparisons between the two approaches, you may like to examine another page: DExtns.htm which describes how to develop an essentially similar extension using the conventional approach of Delphi 2 with Forms and the Application Object. You may also like to look at a Delphi 2 application of this sort which I have developed. It is a Digest Reader Extension and you can try it out by downloading DR32.zip - about 115Kbytes. More information is given on the Pegasus Mail page.

Acknowledgements

I should like to acknowledge the contributions made by Drow, Martin Ireland and Andre van de Merwe to the subject of Pegasus extensions developed in Delphi. Drow's demonstration Reader and Composer extensions were the first I heard about (in autumn 1996). Martin, through the columns of the Pegasus Mail Extensions Mailing List showed the importance of the correct FORMINIT call convention (export; stdcall;). Martin also provided the translation of WPMFORMS.h and WPMPREFS.h into the *.INC Pascal equivalents. Andre provided a useful demonstration on his Zen website().

My preferred approach to using Delphi to develop generic code for Extensions is rather unconventional but I hope you will find it interesting. My main purpose in writing these two web pages is to draw various threads together, showing two different approaches to the use of Delphi in building Pegasus Extensions so that readers may draw their own conclusions.

Quick demo

To use the extension, download the following files to the directory containing your 32bit WINPMAIL.EXE - assumed to be C:PMAIL by default. First of all, make sure your browser is set to SAVE files rather than display them in the browser window. In Netscape 3 for example, this is done by selecting Options and then the Helpers tab on the dialog. Ensure that .DLL files are saved and not listed - resulting in meaningless stuff. The files needed are:-

Pmtmplt.fff and Pmtmplt.dll (30KBytes)

When these have been saved to C:\PMAIL, start WINPMAIL.EXE and select the Pmtmplt Extension. A dialog window appears with 3 edit controls - for the To:, Subject: and Message body fields of an email - and a button labeled Process. Fill in the 3 fields and click the process button. This will queue the email message for sending, in exactly the same way as Pegasus's regular New Mail editor dialog.

The Pmtmplt.dll file which implements the extension was compiled in Delphi 2.01 from the source file (Pmtmplt.dpr), a resource file (Templt.res). An additional file containing the WM_F_* and WM_FM_* messages (WPMFORMS.INC) is also required. Lastly, the Forms file (Pmtmplt.fff) - see above - was created in a text editor such as notepad.

So much for the demonstration. The following sections describe how to create the extension DLL from scratch.

Preliminaries

The first step is to obtain the Extensions Kit prepared by Pegasus Mail's author, David Harris. This is provided as a compressed file: at the time of writing, this is called Forms240.zip and is obtainable from the usual Pegasus Mail Ftp sites. Decompressing the file extracts a number of source files for Tphone, Finger, Ph and others. In addition three other files are extracted, WPMFORMS.TXT, WPMFORMS.h and WPMPREFS.h. The last two are C header files which have kindly been translated by Martin Ireland into Borland Pascal (Delphi-compatible) and are available as:-

wpmforms.inc and wpmprefs.inc

All 3 files need careful study.

Compiling the resource file

As mentioned, a copy of Borland's Resource Workshop is needed - as was the case for development of the C-coded extensions Ph, Finger etc bundled with Pegasus Mail. For 32bit extension development, a 32 bit version of Resource Workshop is desirable. Unfortunately, although supplied with 32bit Borland C compilers, this is not supplied with Delphi 2, but the 16bit version is/was available for Borland Pascal 7 and 1.5 and (in the RAD pack) with Delphi 1. I have the 16bit, but not the 32 bit version, but find resource development quite easy by following these steps:-

1. Use 16bit Resource Workshop and design the dialog, saving it as a .RC file;

2. Compile the .RC file to a .RES file with BRCC32.EXE, which is the 32bit command line resource compiler shipped with Delphi 2.

An example of an Extension in Delphi 2.01

The following listing, which should be cut and pasted to a Delphi project file called Pmtmplt.dpr, shows the Delphi code for a demonstration Extension. As mentioned above, it presents a modeless dialog provided with edit controls for entering the To:, Subject: and Messagebody fields for an email message. When these are filled in, the button marked 'Process' is clicked. This works just like the 'Send' in Pegasus's regular message editor.

Near the end of the listing, the resource (Templt.RC) file defining the modeless dialog. This should be extracted and compiled with the BRCC32.EXE command line Resource Compiler. The resulting Templt.RES file should be placed, together with Pmtmplt.dpr and WPMFORMS.INC in a convenient directory - C:\Scratch for example.

Once these three files are assembled, load Delphi 2.01, load Pmtmplt.dpr and compile the project. Delphi's project options should be set to send the resulting Pmtmplt.dll file to the same diresctory as WINPMAIL.EXE. Note that the compiled Pmtmplt.dll file should be exactly the same as the one described above.

The FFF file

At the end of the listing can be found the lines of parameters for the Pmtmplt.FFF file. Again, these should be cut and pasted into a file of that name, which should be placed in the WINPMAIL.EXE directory.

Pegasus can then be started and the Pmtmplt extension started.

(* An example of a 32-bit Pegasus Mail Extension coded in Delphi 2.01 *)
(* Author: Michael Glover - glover@globalnet.co.uk October 1997       *)
(* The following source code is saved in Pmtmplt.dpr                  *)


{$N+} { Use math coprocessor and WIN87EM.DLL }
{$X+}
{$F+} { far proc calls }
library Pmtmplt;

{$R Templt.res }

uses
{$IFDEF WIN32}
  Windows, Messages, SysUtils;
{$ELSE}
  Wintypes, Winprocs, WpmForms, Strings;
{$ENDIF}

{$I WPMFORMS.INC }  {Include Martin Ireland's translation of WPMFORMS.h }

const
  szFormDlgName       = 'TEMPLATE' ; { Name of dialog resource template.}
  szFormDlgClassName  = 'Tmplt';     { Class name for the form dialog.}


var
 hLibInstance: THandle; { set in LibMain, used throughout the DLL }
 MsgPanel103: hWnd;
 ToPanel106: hWnd;
 SubjPanel107: hWnd; {Process Btn ID = 139; ClearBtn ID = 941 }
 hWindow: hWnd;

 Const last_focus : hWnd = 0;

{$IFDEF WIN32 }

 Function FORMINIT (version: Word; variant: integer; hParent: hWnd;
   data: PChar; VAR hDialog: hWnd; Pcallback: PChar): word; stdcall; export;
{$ELSE }
 Function FORMINIT (version: Word; variant: integer; hParent: hWnd;
   data: PChar; VAR hDialog: hWnd; Pcallback: PChar): word;  export;
{$ENDIF }

 var
    wc: TWndClass;
    r : TRect;
    szBuf: array[0..80] of char;
    i: Integer;
    tm: array[0..7] of Byte;

   BEGIN
    FORMINIT:= 0;  {default }
  {  First, check to see if the version of the form manager is
     one we can work with. This check is pretty arbitrary - you
     should probably assume that you may not work with any version
     where the major version number is higher than the original
     version you targeted. }

  if ((version and $FF00) > $100) then Exit { return 0};

   { Now check the variant number; }
   { Only provides a COMPOSER format. }

   if variant <> 0 then Exit {return 0};

   hDialog:= CreateDialog (hLibInstance, szFormDlgName,
       hParent, nil);

  if (hDialog = 0) then
     begin
       MessageBeep(0);
       FORMINIT := 0;
       Exit;
     end;

   {  Some default values can be passed in the "data" parameter}
   if ((data <> nil) and (data[0] <> #0)) then
     SetDlgItemText (hDialog, 103, data); { Default host }

    FORMINIT:= 1;
   END; {FORMINIT}


Procedure SendEMAIL(hParent: hWnd);

Var I: Integer;
    Buf: array[0..2048] of char;
  {The buffer size is limited to 2048 here in order to suit both Delphi 1, BP7 AND }
  {Delphi 2 for this example. If it is too large, 16-bit applications' stacks will }
  {overflow. For Delphi2 only, much larger buffers can be supported.}

begin
   SendMessage (hParent, WM_F_SETSTATUS, 3, Longint( PChar('Specifying Message.') ));
   if SendMessage (hParent, WM_F_NEWMESSAGE, 0, 0) = 0 then
      begin
        MessageBox(0 , ' WM_F_NEWMESSAGE has failed', 'Message',mb_OK);
        Exit;
      end;
     SendMessage (hParent, WM_F_SETDEFAULTS, 0, 0);
     SendMessage(ToPanel106, WM_GETTEXT, 128, LongInt(@Buf));
     SendMessage (hParent, WM_F_TO, 0, Longint(@Buf) );
     SendMessage(SubjPanel107, WM_GETTEXT, 128, LongInt(@Buf));
     SendMessage (hParent, WM_F_SUBJECT, 0, Longint(@Buf) );
     SendMessage(MsgPanel103, WM_GETTEXT, 2048, LongInt(@Buf));
     SendMessage (hParent, WM_F_MESSAGETEXT, 0, Longint(@Buf) );
     SendMessage (hParent, WM_F_SENDMESSAGE, 0, 0);
end; { SendEMAIL }

{$IFDEF WIN32 }
function FormProc(hWindow: HWnd; Message: Word; WParam: WPARAM;
  LParam: Longint): LongInt; stdcall; export;
{$ELSE }
function FormProc(hWindow: HWnd; Message: Word; WParam: WORD;
  LParam: Longint): LongInt; export;
{$ENDIF }

const
  fCallDefProc: Bool = TRUE;
  FirstFocus: Bool = TRUE;

type PtPtr = ^TPoint;


var host: array[0..80] of char;
    Return: Longint;
    c : PtPtr;

begin
  fCallDefProc:= TRUE;
  Formproc := 0;   { Preset function result }
  case Message of
    WM_FM_WHEREYAWANNIT:
    begin
       c := Pointer(LParam);
       c^.x := 0;
       c^.y := 0;
       FormProc:= 1;
       fCallDefProc:= FALSE;
    end;
    wm_FM_INIT:
      begin
         MsgPanel103  := GetDlgItem(hWindow, 103);
         ToPanel106   := GetDlgItem(hWindow, 106);
         SubjPanel107 := GetDlgItem(hWindow, 107);
      end;
    wm_FM_INITFOCUS:
      begin
        SetFocus (GetDlgItem (hWindow, 106));
         If FirstFocus then
          begin
           SendMessage(MsgPanel103, WM_SETTEXT, 0,
             LongInt(PChar('Example of a Composer Extension in Delphi 2')));
          end;
        FirstFocus:= FALSE;
      end;

    WM_FM_RESTOREFOCUS :
         {  There's a glitch in the way the Windows MDI manager }
         {  (which we know from trapping EN_SETFOCUS messages). }

         begin
           if Bool(last_focus) then SetFocus (last_focus);
         end;
    WM_fm_SIZE:
         begin
           MoveWindow (hWindow, 0, 0,
            LOWORD (lParam), HIWORD (lParam), TRUE); {dlg box}
         end;
    wm_Destroy: begin end;

    WM_COMMAND:
     Begin {WM_COMMAND}
       fCallDefProc:= FALSE;
       if HiWord(lParam) = EN_SETFOCUS then
         begin
           last_focus := LoWord(lParam);
           exit;
         end;
       case wParam of
    139:  {'Process' button }
         begin
          { MessageBox(0 , 'In WM_COMMAND', 'Message',mb_OK); }
           SendEmail (GetParent (hWindow));
         end;
       End; {case wParam}

     END {WM_COMMAND} ;
  end; { case message }

    if fCallDefProc then
      FormProc := DefDlgProc(HWindow, Message, WParam, LParam);

end;

exports
  FormProc,
  FORMINIT;

var
  TMClass: TWndClass;   { Control's window class }
  Chain: Pointer;     { For hooking into exit chain }
  J: Integer;

{$S-}  { Turn off stack checking for DLL exit procedures }
procedure TemplExitProc; far;
begin
  UnregisterClass(szFormDlgClassName, hLibInstance);
  ExitProc := Chain;  { Continue exit procedure chain }
end;

begin
  Chain := ExitProc;           { Preserve current exit path }
  ExitProc := @TemplExitProc;  { Link new procedure into chain }
  hLibInstance:= System.hInstance;
  with TMClass do
  begin
    cbClsExtra    := 0;
    cbWndExtra    := DLGWINDOWEXTRA {extra Bytes - 30 of them};
    hbrBackground := Hbrush(COLOR_WINDOW + 1);
    hIcon         := 0;
    hInstance     := hLibInstance;
    hCursor       := LoadCursor(0, idc_Arrow);
    lpfnWndProc   := TFarProc(@FormProc);
    lpszClassName :=  szFormDlgClassName;
    lpszMenuName  := nil;
  end;
  J:= Integer (RegisterClass(TMClass) );
  {MBox(J, 'RegisterClass'); }
end.

(*  Listing of the Resource file , Templt.RC *)
(*-------------------------------------------*)
(* This may be compiled to .RES format with BRCC32.EXE *)

 (*
   ------start of Templt.RC file---------
TEMPLATE DIALOG 6, 5, 302, 200
STYLE WS_CHILD | WS_DLGFRAME
CLASS "Tmplt"
FONT 10, "System"
BEGIN
        CONTROL "", 106, "EDIT", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | 
              WS_VISIBLE | WS_BORDER | WS_TABSTOP, 55, 8, 111, 12
        CONTROL "", 103, "EDIT", ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | 
            ES_AUTOHSCROLL | ES_WANTRETURN | WS_CHILD
            | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP, 9, 67, 281, 129
        PUSHBUTTON "&Process", 139, 177, 13, 30, 14, WS_CHILD | WS_VISIBLE | 
            WS_TABSTOP
        CONTROL "", 107, "EDIT", ES_LEFT | ES_AUTOHSCROLL | 
            WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 55, 22, 111, 12
        PUSHBUTTON "Clear", 941, 264, 56, 24, 8, WS_CHILD | WS_VISIBLE | WS_TABSTOP
        LTEXT "Message", -1, 201, 56, 32, 8, WS_CHILD | WS_VISIBLE | WS_GROUP
        LTEXT "Subject:", -1, 22, 24, 27, 8, WS_CHILD | WS_VISIBLE | WS_GROUP
        LTEXT "To:", -1, 30, 10, 16, 8, WS_CHILD | WS_VISIBLE | WS_GROUP
END

   ------end of Templt.RC file---------
   ------start of Pmtmplt.FFF file---------
;  Mnemonic     Value   Meaning
;  ----------------------------------------------------------------
;  WPM_STARTUP    1     Load the extension when WinPMail starts up
;  WPM_NOLIST     2     Do not show in the "Extensions" window list
;  WPM_HIDDEN     4     Hide the parent MDI window on loading
;  WPM_LOGGING    8     Extension wants to receive logging events
;  WPM_ONEONLY   16     Only allow one running instance at any time
;  WPM_FIRSTRUN  32     Autoload extension on first-ever WinPMail run
;  WPM_USES_TCP  64     Extension requires TCP/IP services to run
;
;  Various flags can be combined by adding (or ORing) the values
;  together - so to have a hidden extension which loads at startup,
;  is interested in logging events, and does not appear in the
;  "Extensions" window, you would use 1 + 2 + 4 + 8 = 15
;

Form name = "PMTMPLT"

32-bit model = 1
 Form DLL = ~a\PMTMPLT.DLL
Form type = COMPOSER
Form flags = 64
Form tagname = "MH-POPULIST"

;  You can enter the default finger server host in "Form Data"

Form data = ""
End
   ------end of Pmtmplt.FFF file---------
*)

Notes on the coding

Conditional DEFINE statements have been inserted in the code to provide both for a16bit extension for use with 16bit versions of Pegasus and compiled with Delphi 1, as well as the 32bit version compiled as described with Delphi 2.01. Note that different Templt.RES files are needed for each version, 16bit (compiled with BRCC.EXE) and 32bit (compiled as described with BRCC32.EXE). The 16bit versions can be compiled with the Resource Workshop shipped with Borland Pascal for Windows version 7 or the Delphi 1 RAD pack.

It is important to be meticulous with the names of the Dialog Resource Template (szFormDlgName) and the Dialog Class (szFormDlgClassName). These names turn up at several places in the code - in FORMINIT and in defining the class attributes in the TMClass dialog class.

Note that the IDs for Pegasus Extensions messages (eg WM_F_NEWMESSAGE) are defined in the WPMFORMS.INC include file (see above).

For some reason, the WM_FM_INITFOCUS message is received twice during an extension's startup stage and can cause strange effects. For this reason, the boolean variable Firstfocus is defined.

Also, it is noticed that exported functions are all stdcass for 32bit (Delphi 2) extensions, but default to PASCAL for 16bit (Delphi1) extensions. David Harris' notes in WPMFORMS.TXT describe clearly how the FORMINIT implements the communication between the extension and Pegasus Mail proper. The remaining coding is conventional windows programming. If this is unfamiliar, then refer to Charles Petzold's Programming Windows. This is the best book on the subject by far, even though it is written from a C programming perspective.

Conclusion

In this page I have argued, with the help of an example extension, that compact and efficient extensions can be made with the Delphi development system. Compactness can only be achieved by avoiding Forms and OOP constructs. I have not found anything which can be done in C/C++ which cannot be done in Delphi; Delphi is very fast - Pmtmplt.DLL with its 1700 lines of code including WPMFORMS.INC, takes less than 1/4 second to compile on a P166 machine with NT4 W/s.

I have found that with a bit of practice, it is almost as quick to develop these compact DLLs and EXEs as it is to use Delphi the way Anders Heijlsberg designed. After all, this approach is certainly needed for writing Delphi Components.

Lastly, I do believe that developers should use the language system they feel most comfortable with. The main reason for chosing Delphi (and previously Turbo Pascal) is that I cannot understand C programs I wrote 5 years ago, whilst Pascal programs written 10 years ago seem perfectly comprehensible.