Pegasus Mail for Windows DDE (Dynamic Data Exchange) Interface
Copyright (c) 1997, David Harris, All Rights Reserved.
----------------------------------------------------------------
Pegasus Mail v2.54 and later incorporates support for Windows DDE to allow
interprocess communication. This document describes the topics supported by
WinPMail and the syntax of the commands sent to them. It is not a tutorial
on DDE, and assumes that you either understand how to use DDE, or are using
an environment (such as Microsoft Word's WordBasic language) that
simplifies the process of using DDE commands.
One of the most powerful features of DDE is that it allows transparent
communication between 16-bit and 32-bit applications: so, by using DDE, you
can communicate with either the 16- or the 32-bit version of WinPMail
without having to be worried about the differences between the two.
Pegasus Mail's DDE interface has been designed to be as simple to use as
possible, and depends on simple strings for commands. It should be possible
to use DDE to interact with Pegasus Mail from almost any environment.
Service and Topic Names
-----------------------
Under DDE, a "service name" is the name of an application that can accept
DDE connections: when you connect to a service application, you specify a
particular set of commands or operations in which you are interested - this
set of commands is known as a "topic". Pegasus Mail uses the service name
"WinPMail" and exports the following topics:
"System" The standard DDE system topic
"Environment" Accepts DDE Requests (transactions of type XTYP_REQUEST)
and returns information about the environment of the
running copy of WinPMail, such as directories and
usernames.
"Message" Accepts DDE Poke commands to create and send e-mail
messages. A DDE Client can either create and manipulate
standard Pegasus Mail message editor windows, filling
in whichever blanks are required, or it can create
messages without a window.
"TCP" Provides access via DDE Poke commands to WinPMail's
TCP/IP-based mail services.
The "Environment" topic
-----------------------
The "Environment" DDE topic allows client applications to obtain
information about the running copy of Pegasus Mail. To use this command,
send a DDE Request command (a transaction of type XTYP_REQUEST) the the
"Environment" topic with the "item" parameter set to the environment item
you wish to retrieve, selected from the following list:
USER - Returns the name of the user running WinPMail
HOMEBOX - Returns the full path to the user's home mail directory
NEWBOX - Returns the full path to the user's new mail directory
MODE - Returns either "Standalone" or "Network"
VERSION - Returns the version of WinPMail, expressed as a four
digit hexadecimal number ("0254" for v2.54) followed
by a space and either "16" or "32" to indicate the
version of WinPMail that is running.
BASEDIR - The directory from which WinPMail was run
TCP - If WinPMail's TCP/IP services are available, returns
the path to the WINSOCK.DLL in use, otherwise returns
the single character "N".
NEWMAIL - Returns the number of mail messages in the user's new
mail folder.
-- Example: -----------------------------------
In WordBasic (MS-Word 7) the following Macro opens a connection to
WinPMail, retrieves the current version, and displays it:
Sub MAIN
channel = DDEInitiate("WinPMail", "Environment")
a$ = DDERequest$(channel, "version")
DDETerminate channel
MsgBox a$
End Sub
A longer example in C is shown as Appendix A: in this document.
The "Message" topic
-------------------
Client applications use the "Message" topic to create and send electronic
mail messages using WinPMail. Once a connection has been established to the
"Message" topic, the client Pokes data at WinPMail (using XTYP_POKE
transactions). The string parameter in the Poke command should be set to
"Message" - other values may be added in future. The data itself contains
the commands and parameters.
The following commands can be Poked as data to WinPMail:
New : <"Message"> or <"Window">
If the parameter is "Window", then WinPMail creates a standard
message editing window, and subsequent message commands will change
the controls and settings in that window. If the parameter is
"Message", then a data structure representing the message is
allocated internally and subsequent message commands will fill in the
fields within that structure.
Defaults : Y
Tells WinPMail to apply the user's regular message setting default
values to the message. This command is only valid when the
"New: message" command has been sent - it has no effect on message
editing windows created using "New: window", since they always use
default values automatically.
To :
[, ...]
Fills in the "To" field for the message or window. The parameter may
be any address WinPMail would normally accept, and you may include
more than one address by separating them with commas. The same rules
apply to the Cc: and Bcc: fields.
Cc : [, ...]
Fills in the "Cc:" (Carbon Copy) field for the message.
Bcc : [, ...]
Fills in the "Bcc:" (Blind Carbon Copy) field for the message.
Subject :
Fills in the subject field of the message.
Reply-to :
Sets the optional reply address for the message.
Copyself : <'Y'> or <'N'>
Selects whether or not a copy-to-self should be made of the message.
Confirm-delivery : <'Y'> or <'N'>
Selects whether or not to request confirmation of delivery
Confirm-reading : <'Y'> or <'N'>
Selects whether or not to request confirmation of reading
Urgent : <'Y'> or <'N'>
Selects whether or not the message should be marked as "urgent"
Encrypted : <,><,>
Chooses an encryption method for the message. "Encryptor-name" is
the tagname of the encryptor you wish to use (use "PM-BUILTIN" to
select Pegasus Mail's built-in encryptor). You can find the tagname
for an encryptor in the "Form tagname" line of the encryptor's .FFF
file. "passphrase" is the password for the message. If it contains
a comma, then it must be quoted using " and ". "flags" is either
1 to encrypt the message, 2 to sign the message, or 3 to encrypt
and sign the message.
Signature :
Chooses the signature you wish to attach to the message. "number"
is 1 - 9, corresponding to the signature sets shown in WinPMail's
Tools | Signatures preferences page, or 0 to disable signatures.
Mime : <'Y'> or <'N'>
Indicates whether or not MIME support should be turned on for
this message. MIME support affects the way attachments and
international characters are handled by WinPMail. We strongly
recommend that you turn MIME on wherever possible.
Attach : <,><,><,>
Attach a file to the message. "path" should be the full path
to the file - do not assume that the current directory in your
client application will be the same as the current directory in
Pegasus Mail. "filename" is the name Pegasus Mail should include
in the message as the real name of the file (you will typically
use this when you have to create a temporary file but want to
send it under the original file's name). If "filename" is "-",
WinPMail will automatically use the filename portion of "path".
"attachment-type" should be textual information describing the
attachment; it must not contain spaces. If you set this field to
a single dash ("-"), WinPMail will use its internal routines to
try to work out the most appropriate type information for the
file. "encoding" is used to select the type of encoding for the
attachment: we strongly recommend that you set this value to 0,
which will allow WinPMail to choose the most appropriate encoding
for you. Possible values are:
0 - Pegasus Mail decides.
1 - No encoding - the file is not altered (local mail only)
2 - ASCII encoding - the file is normalised to CR/LF line
endings and is not otherwise encoded in transit.
3 - UUencoding - the file is uuencoded
4 - BinHex - the file is transformed using the BinHex 4.0 method.
5 - MIME - the file is transformed using the MIME BASE64 method.
File :
Indicates the name of a file containing the text WinPMail should
use as the body of the message. Note that this file is NOT an
attachment - it is expected to be plain text.
Data :
Adds "string" to the body of the message as a single line. Poke
this command repeatedly to build up a message line by line.
WinPMail automatically adds the CR/LF termination to each line.
Poking this command with no parameter will print a blank
line in the message (but the ':' must still be present).
Send
This command sends the message. Note that it has no parameters and
no ':'. It has no effect when "Window" was used as the parameter to
the "New:" command.
Restore
This command brings WinPMail to the top and activates the Message
Editing Window created using the "New: Window" command. Note that it
has no parameters and no ':'. For messages created using the
"New: Message" command, this command will simply bring WinPMail to
the top without taking any other action.
-- Example: -----------------------------------
In WordBasic (MS-Word 7) the following Macro opens a connection to
WinPMail, and sends a message with no window.
Sub MAIN
channel = DDEInitiate("WinPMail", "Message")
DDEPoke channel, "Message", "New: Message"
DDEPoke channel, "Message", "Defaults: Y"
DDEPoke channel, "Message", "To: David"
DDEPoke channel, "Message", "Subject: Test from Word"
DDEPoke channel, "Message", "Data: Hi there!"
DDEPoke channel, "Message", "Send"
DDETerminate channel
End Sub
The "TCP" topic
---------------
The commands in this topic allow clients to control Pegasus Mail's
built-in TCP/IP-based mail protocols. Before using these commands, a
client should usually use the "TCP" request to the "Environment" topic to
determine whether or not TCP/IP services are enabled and available.
Clients interact with this topic by Poking data at it in a similar manner
to that used to access the "Message" topic. The following commands can be
poked at this topic:
Get
Tells Pegasus Mail to download mail using the current TCP/IP
settings defined for the program.
Send
Tells Pegasus Mail to send any messages currently waiting in
the queue to be sent out.
Both
Tells Pegasus Mail to check for new mail, then to send any messages
currently waiting in the queue, in that order.
Restore
Brings Pegasus Mail to the front and gives it focus.
-- Example: -----------------------------------
In WordBasic (MS-Word 7) the following Macro opens a connection to
WinPMail, tells it to download new mail, then brings it to the front:
Sub MAIN
channel = DDEInitiate("WinPMail", "TCP")
DDEPoke channel, "TCP", "Get"
DDEPoke channel, "TCP", "Restore"
DDETerminate channel
End Sub
Pegasus Mail and the Windows Registry
-------------------------------------
Starting with v2.54, WinPMail updates the Windows registry with a certain
amount of information each time it is run. DDE Client Applications can use
this registry information to work out how to find a copy of Pegasus Mail to
run if none is active, and what commandline is appropriate.
The 32-bit version of WinPMail creates the following keys:
HKEY_CURRENT_USER\Software\Pegasus Mail\Version
HKEY_CURRENT_USER\Software\Pegasus Mail\BaseDir
HKEY_CURRENT_USER\Software\Pegasus Mail\Command
The "version" key contains the WinPMail version, expressed in exactly the
same way as the return from the DDE "Environment" topic's "Version"
request. The "BaseDir" key contains the directory where the WinPMail
executable file is located, and the "command" key contains the full
commandline that was used to invoke the most recently-run copy of Pegasus
Mail.
Both the 16- and 32-bit versions of WinPMail create the following keys:
HKEY_CLASSES_ROOT\Software\Pegasus Mail\Version
HKEY_CLASSES_ROOT\Software\Pegasus Mail\BaseDir
HKEY_CLASSES_ROOT\Software\Pegasus Mail\Command
These keys are formatted in exactly the same way as those shown above.
32-bit applications should always attempt to find the HKEY_CURRENT_USER
keys before the HKEY_CLASSES_ROOT keys, since doing so ensures that
multiple user configurations are respected under Windows 95 and NT.
Appendix A: Using DDE from C programs
-------------------------------------
The source code shown here can be used as a model for interacting with
any DDE-aware application. It presents a simple dialog with "Service",
"Topic" and "Command" fields, request selector radio buttons that allow
the user to select between XTYP_EXECUTE, XTYP_REQUEST and XTYP_POKE
transactions, and three buttons - one to open/close a connection, a
"quit" button, and a "Do it" button, that sends the command. If a
connection is established using the "Open" button, then the "Do it"
button will send the command to that connection, otherwise it will
establish a connection, send the command, then close the connection.
This code was written for Borland C++ v4.52 and should be linked using
Borland's BWCC.LIB or BWCC32.LIB in order to present the proper dialog
appearance.
--------------- DDECLI.C --------------------------------------------
#include
#include
#include
#include
#include
DWORD idInst = 0L; // DDE instance identifier
HINSTANCE hInstance;
HCONV mconv;
HSZ ghszServSrv;
HSZ ghszServTop;
#pragma warn -par
#pragma warn -use
HDDEDATA EXPENTRY DDECallback (WORD wType, // transaction type
WORD wFmt, // clipboard format
HCONV hConv, // handle of the conversation
HSZ hsz1, // handle of a string
HSZ hsz2, // handle of a string
HDDEDATA hData, // handle of a global memory object
DWORD dwData1, // transaction-specific data
DWORD dwData2) // transaction-specific data
{
// Nothing need be done here...
return (HDDEDATA) NULL;
}
BOOL SendShellCommand (DWORD idInst, char *service, char *topic, LPSTR lpCommand)
{
HSZ hszServSrv; // Service is "DDESERV"
HSZ hszServTop; // Topic is "MAIL"
HCONV hconv; // handle of conversation
int nLen; // length of command string
HDDEDATA hData; // return value of DdeClientTransaction
DWORD dwResult; // result of transaction
BOOL bResult=FALSE; // TRUE if this function is successful
if (mconv == NULL)
{
// create string handle to service/topic
hszServSrv = DdeCreateStringHandle (idInst, service, CP_WINANSI);
hszServTop = DdeCreateStringHandle (idInst, topic, CP_WINANSI);
// attempt to start conversation with server app
hconv = DdeConnect (idInst, hszServSrv, hszServTop, NULL);
}
else
hconv = mconv;
if (hconv != NULL)
{
// get length of the command string
nLen = lstrlen ((LPSTR) lpCommand);
// send command to server app
hData = DdeClientTransaction (
(LPBYTE) lpCommand, // data to pass
nLen + 1, // length of data
hconv, // handle of conversation
NULL, // handle of name-string
CF_TEXT, // clipboard format
XTYP_EXECUTE, // transaction type
20000, // timeout duration
&dwResult); // points to transaction result
if (hData)
bResult = TRUE;
if (mconv == NULL)
// end conversation
DdeDisconnect (hconv);
}
if (mconv == NULL)
{
// free service/topic string handle
DdeFreeStringHandle(idInst, hszServSrv);
DdeFreeStringHandle(idInst, hszServTop);
}
if (bResult == FALSE)
MessageBox (NULL, "SendShellCommand failed", "DDE Client", MB_OK);
return bResult;
}
BOOL SendPoke (DWORD idInst, char *service, char *topic, LPSTR lpCommand)
{
HSZ hszServSrv; // Service is "DDESERV"
HSZ hszServTop; // Topic is "MAIL"
HCONV hconv; // handle of conversation
int nLen; // length of command string
HDDEDATA hData; // return value of DdeClientTransaction
DWORD dwResult; // result of transaction
BOOL bResult=FALSE; // TRUE if this function is successful
if (mconv == NULL)
{
// create string handle to service/topic
hszServSrv = DdeCreateStringHandle (idInst, service, CP_WINANSI);
hszServTop = DdeCreateStringHandle (idInst, topic, CP_WINANSI);
// attempt to start conversation with server app
hconv = DdeConnect (idInst, hszServSrv, hszServTop, NULL);
}
else
hconv = mconv;
if (hconv != NULL)
{
// get length of the command string
nLen = lstrlen ((LPSTR) lpCommand);
// send command to server app
hData = DdeClientTransaction (
(LPBYTE) lpCommand, // data to pass
nLen + 1, // length of data
hconv, // handle of conversation
hszServTop, // handle of name-string
CF_TEXT, // clipboard format
XTYP_POKE, // transaction type
20000, // timeout duration
&dwResult); // points to transaction result
if (hData)
bResult = TRUE;
if (mconv == NULL)
// end conversation
DdeDisconnect (hconv);
}
if (mconv == NULL)
{
// free service/topic string handle
DdeFreeStringHandle(idInst, hszServSrv);
DdeFreeStringHandle(idInst, hszServTop);
}
if (bResult == FALSE)
MessageBox (NULL, "SendPoke failed", "DDE Client", MB_OK);
return bResult;
}
BOOL SendShellRequest (DWORD idInst, char *service, char *topic, char *cmd)
{
HSZ hszServSrv; // Service is "DDESERV"
HSZ hszServTop; // Topic is "MAIL"
HSZ item;
HCONV hconv; // handle of conversation
int nLen; // length of command string
HDDEDATA hData; // return value of DdeClientTransaction
DWORD dwResult; // result of transaction
BOOL bResult=FALSE; // TRUE if this function is successful
char *str;
DWORD x;
if (mconv == NULL)
{
// create string handle to service/topic
hszServSrv = DdeCreateStringHandle (idInst, service, CP_WINANSI);
hszServTop = DdeCreateStringHandle (idInst, topic, CP_WINANSI);
// attempt to start conversation with server app
hconv = DdeConnect (idInst, hszServSrv, hszServTop, NULL);
}
else
hconv = mconv;
if (hconv != NULL)
{
// send command to server app
item = DdeCreateStringHandle (idInst, cmd, CP_WINANSI);
hData = DdeClientTransaction (
NULL, // data to pass
0, // length of data
hconv, // handle of conversation
item, // handle of name-string
CF_TEXT, // clipboard format
XTYP_REQUEST, // transaction type
20000, // timeout duration
&dwResult); // points to transaction result
if (hData)
{
if ((str = (char *) DdeAccessData (hData, &x)) != NULL)
{
strcpy (cmd, str);
bResult = TRUE;
DdeUnaccessData (hData);
}
DdeFreeDataHandle (hData);
}
DdeFreeStringHandle (idInst, item);
if (mconv == NULL)
// end conversation
DdeDisconnect (hconv);
}
if (mconv == NULL)
{
// free service/topic string handle
DdeFreeStringHandle (idInst, hszServSrv);
DdeFreeStringHandle (idInst, hszServTop);
}
return bResult;
}
int FAR PASCAL _export dummy_proc (HWND hDialog, UINT wMsg,
WPARAM wParam, LPARAM lParam)
{
BOOL fProcessed = TRUE;
DWORD dwResult;
RECT r;
HWND hControl;
char buffer [256], service [80], topic [80];
switch (wMsg)
{
case WM_INITDIALOG :
CheckRadioButton (hDialog, 104, 106, 104);
break;
case WM_SETFOCUS :
SetFocus (GetDlgItem (hDialog, 101));
break;
case WM_COMMAND :
if (GET_WM_COMMAND_ID(wParam, lParam) == IDCANCEL)
{
EndDialog (hDialog, IDCANCEL);
break;
}
if (GET_WM_COMMAND_ID(wParam, lParam) == 120)
{
if (mconv == NULL)
{
GetDlgItemText (hDialog, 101, service, sizeof (service));
GetDlgItemText (hDialog, 102, topic, sizeof (topic));
ghszServSrv = DdeCreateStringHandle (idInst, service, CP_WINANSI);
ghszServTop = DdeCreateStringHandle (idInst, topic, CP_WINANSI);
// attempt to start conversation with server app
mconv = DdeConnect (idInst, ghszServSrv, ghszServTop, NULL);
if (mconv == NULL)
MessageBox (NULL, "Connection failed!", "DDE Client",
MB_OK | MB_ICONEXCLAMATION);
else
SetDlgItemText (hDialog, 120, "Close");
}
else
{
DdeDisconnect (mconv);
mconv = NULL;
DdeFreeStringHandle (idInst, ghszServSrv);
DdeFreeStringHandle (idInst, ghszServTop);
SetDlgItemText (hDialog, 120, "Open");
}
break;
}
if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK)
{
GetDlgItemText (hDialog, 101, service, sizeof (service));
GetDlgItemText (hDialog, 102, topic, sizeof (topic));
GetDlgItemText (hDialog, 103, buffer, sizeof (buffer));
hControl = GetDlgItem (hDialog, 107);
if (IsDlgButtonChecked (hDialog, 105)) // Request
{
if (SendShellRequest (idInst, service, topic, buffer))
{
Edit_ReplaceSel (hControl, "Request successful:\r\n ");
Edit_ReplaceSel (hControl, buffer);
Edit_ReplaceSel (hControl, "\r\n");
}
else
Edit_ReplaceSel (hControl, "Request failed.\r\n");
}
else if (IsDlgButtonChecked (hDialog, 104))
{
if (SendShellCommand (idInst, service, topic, buffer))
Edit_ReplaceSel (hControl, "Command successful.\r\n");
else
Edit_ReplaceSel (hControl, "Command failed.\r\n");
}
else
{
if (SendPoke (idInst, service, topic, buffer))
Edit_ReplaceSel (hControl, "Poke successful.\r\n");
else
Edit_ReplaceSel (hControl, "Poke failed.\r\n");
Edit_SetSel (GetDlgItem (hDialog, 103), 0, 999);
}
}
break;
default:
fProcessed = FALSE;
break;
}
return fProcessed;
}
int PASCAL WinMain (HINSTANCE __hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
HWND hWndFrame;
WNDCLASS wc;
FARPROC DDEProc, dlgProc;
if (hPrevInstance != NULL)
return 0;
hInstance = __hInstance;
// get proc instance for our DDEML callback
DDEProc = MakeProcInstance ((FARPROC) DDECallback, hInstance);
// register this app with the DDEML
if (DdeInitialize (&idInst, // receives instance ID
(PFNCALLBACK) DDEProc, // address of callback function
APPCMD_CLIENTONLY, // this is a client app
0L)) // reserved
{
#ifndef __FLAT__ // FreeProcInstance is obsolete under Win32
FreeProcInstance (DDEProc);
#endif
return FALSE;
}
dlgProc = MakeProcInstance ((FARPROC) dummy_proc, hInstance);
DialogBox (hInstance, "CLIENT", NULL, dlgProc);
DdeUninitialize (idInst);
return 0;
}
--------------- DDECLI.DEF ----------------------------------------
NAME DDECLI
DESCRIPTION 'DDE Test Client'
EXETYPE WINDOWS
DATA MOVEABLE MULTIPLE PRELOAD
CODE MOVEABLE DISCARDABLE
HEAPSIZE 27500
STACKSIZE 16500
--------------- DDECLI.RC -----------------------------------------
CLIENT DIALOG 180, 121, 246, 168
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CLASS "bordlg"
CAPTION "DDE Test Client"
FONT 8, "MS Sans Serif"
{
RTEXT "Service name:", -1, 15, 15, 60, 8
CONTROL "winpmail", 101, "EDIT", ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP, 83, 13, 102, 12
RTEXT "Topic name:", -1, 15, 30, 60, 8
CONTROL "message", 102, "EDIT", ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP, 83, 28, 102, 12
RTEXT "Command/request:", -1, 14, 45, 61, 8
EDITTEXT 103, 83, 43, 102, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
RTEXT "Transaction type:", -1, 15, 65, 60, 8
CONTROL "Execute", 104, "BorRadio", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 85, 64, 47, 10
CONTROL "Request", 105, "BorRadio", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 133, 64, 43, 10
CONTROL "Poke", 106, "BorRadio", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 184, 64, 32, 10
LTEXT "Results / Diagnostics", -1, 12, 82, 78, 8
EDITTEXT 107, 10, 92, 228, 69, ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP
DEFPUSHBUTTON "Do it", IDOK, 192, 12, 43, 14
PUSHBUTTON "Quit", IDCANCEL, 192, 42, 43, 14
CONTROL "", -1, "BorShade", BSS_GROUP | BSS_CAPTION | BSS_LEFT | WS_CHILD | WS_VISIBLE, -5, -7, 257, 180
PUSHBUTTON "Open", 120, 192, 27, 43, 14
}