Pegasus Mail for Windows DDE (Dynamic Data Exchange) Interface Copyright (c) 1997-1999, David Harris, All Rights Reserved. ---------------------------------------------------------------- Revision history: * WinPMail v3.11: added IDENTITY and VOLATILE keywords to MESSAGE topic. * WinPMail v3.12: added RICHTEXT keyword to MESSAGE topic, and STATUS keyword to ENVIRONMENT topic 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. STATUS - Returns the status of the last mail delivery operation -- 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). Identity : Sets the identity Pegasus Mail should use when processing this message. For more information on Identities, see the Pegasus Mail help file. You can specify any valid identity name as the parameter to this command. The default is the user's current identity. Volatile : <'Y'> or <'N'> (Default is 'Y') (Only meaningful when using Pegasus Mail's built-in SMTP mailer). Setting this flag to 'Y' tells Pegasus Mail that the message should be written straight into Final Form in the queue when it is sent. If you want your message to be reloadable from Pegasus Mail's "Review queued mail" option, you should set this to 'N'. If your message contains attachments that you plan to delete once the message has been sent, you must not set this field to 'N'. Richtext : <'Y'> or <'N'> (Default is 'N') Set the "rich text" flag in the message. 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 }