A DDE CLIENT PROGRAM

Now that we've examined a DDE server program that you can use with Microsoft Excel, let's examine a DDE client program that uses DDEPOP as a server. This program is called SHOWPOP and is shown in Figure 17-2.

SHOWPOP.MAK

#-----------------------

# SHOWPOP.MAK make file

#-----------------------

showpop.exe : showpop.obj showpop.def

link showpop, /align:16, NUL, /nod slibcew libw, showpop

rc showpop.exe

showpop.obj : showpop.c

cl -c -Gsw -Ow -W2 -Zp showpop.c

SHOWPOP.C

/*----------------------------------------

SHOWPOP.C -- DDE Client using DDEPOP

(c) Charles Petzold, 1990

----------------------------------------*/

#include <windows.h>

#include <dde.h>

#include <stdlib.h>

#include <string.h>

struct

{

char *szAbb ;

char *szState ;

long lPop ;

}

pop [] = {

"AL", "Alabama", 0, "AK", "Alaska", 0,

"AZ", "Arizona", 0, "AR", "Arkansas", 0,

"CA", "California", 0, "CO", "Colorado", 0,

"CT", "Connecticut", 0, "DE", "Delaware", 0,

"DC", "Dist. of Columbia", 0, "FL", "Florida", 0,

"GA", "Georgia", 0, "HI", "Hawaii", 0,

"ID", "Idaho", 0, "IL", "Illinois", 0,

"IN", "Indiana", 0, "IA", "Iowa", 0,

"KS", "Kansas", 0, "KY", "Kentucky", 0,

"LA", "Louisiana", 0, "ME", "Maine", 0,

"MD", "Maryland", 0, "MA", "Massachusetts", 0,

"MI", "Michigan", 0, "MN", "Minnesota", 0,

"MS", "Mississippi", 0, "MO", "Missouri", 0,

"MT", "Montana", 0, "NE", "Nebraska", 0,

"NV", "Nevada", 0, "NH", "New Hampshire", 0,

"NJ", "New Jersey", 0, "NM", "New Mexico", 0,

"NY", "New York", 0, "NC", "North Carolina", 0,

"ND", "North Dakota", 0, "OH", "Ohio", 0,

"OK", "Oklahoma", 0, "OR", "Oregon", 0,

"PA", "Pennsylvania", 0, "RI", "Rhode Island", 0,

"SC", "South Carolina", 0, "SD", "South Dakota", 0,

"TN", "Tennessee", 0, "TX", "Texas", 0,

"UT", "Utah", 0, "VT", "Vermont", 0,

"VA", "Virginia", 0, "WA", "Washington", 0,

"WV", "West Virginia", 0, "WI", "Wisconsin", 0,

"WY", "Wyoming", 0, "US", "United States Total", 0

} ;

#define NUM_STATES (sizeof (pop) / sizeof (pop [0]))

#define WM_USER_INITIATE (WM_USER + 1)

#define DDE_TIMEOUT 3000

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char szAppName [] = "ShowPop" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

if (!hPrevInstance)

{

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (hInstance, szAppName) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

RegisterClass (&wndclass) ;

}

hwnd = CreateWindow (szAppName, "DDE Client - US Population",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

SendMessage (hwnd, WM_USER_INITIATE, 0, 0L) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)

{

static BOOL fDoingInitiate = TRUE ;

static char szServerApp [] = "DdePop",

szTopic [] = "US_Population" ;

static HWND hwndServer = NULL ;

static short cxChar, cyChar ;

ATOM aApp, aTop, aItem ;

char szBuffer [24], szPopulation [16], szItem [16] ;

DDEACK DdeAck ;

DDEDATA FAR *lpDdeData ;

DDEADVISE FAR *lpDdeAdvise ;

DWORD dwTime ;

GLOBALHANDLE hDdeAdvise, hDdeData ;

HDC hdc ;

MSG msg ;

PAINTSTRUCT ps ;

short i, x, y ;

TEXTMETRIC tm ;

WORD wStatus, cfFormat ;

switch (message)

{

case WM_CREATE :

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

cxChar = tm.tmAveCharWidth ;

cyChar = tm.tmHeight + tm.tmExternalLeading ;

ReleaseDC (hwnd, hdc) ;

return 0 ;

case WM_USER_INITIATE :

// Broadcast WM_DDE_INITIATE message

aApp = GlobalAddAtom (szServerApp) ;

aTop = GlobalAddAtom (szTopic) ;

SendMessage (0xFFFF, WM_DDE_INITIATE, hwnd,

MAKELONG (aApp, aTop)) ;

// If no response, try loading DDEPOP first

if (hwndServer == NULL)

{

WinExec (szServerApp, SW_SHOWMINNOACTIVE) ;

SendMessage (0xFFFF, WM_DDE_INITIATE, hwnd,

MAKELONG (aApp, aTop)) ;

}

// Delete the atoms

GlobalDeleteAtom (aApp) ;

GlobalDeleteAtom (aTop) ;

fDoingInitiate = FALSE ;

// If still no response, display message box

if (hwndServer == NULL)

{

MessageBox (hwnd, "Cannot connect with DDEPOP.EXE!",

szAppName, MB_ICONEXCLAMATION | MB_OK) ;

return 0 ;

}

// Post WM_DDE_ADVISE messages

for (i = 0 ; i < NUM_STATES ; i++)

{

hDdeAdvise = GlobalAlloc (GHND | GMEM_DDESHARE,

sizeof (DDEADVISE)) ;

lpDdeAdvise = (DDEADVISE FAR *) GlobalLock (hDdeAdvise) ;

lpDdeAdvise->fAckReq = TRUE ;

lpDdeAdvise->fDeferUpd = FALSE ;

lpDdeAdvise->cfFormat = CF_TEXT ;

GlobalUnlock (hDdeAdvise) ;

aItem = GlobalAddAtom (pop[i].szAbb) ;

if (!PostMessage (hwndServer, WM_DDE_ADVISE, hwnd,

MAKELONG (hDdeAdvise, aItem)))

{

GlobalFree (hDdeAdvise) ;

GlobalDeleteAtom (aItem) ;

break ;

}

DdeAck.fAck = FALSE ;

dwTime = GetCurrentTime () ;

while (GetCurrentTime () - dwTime < DDE_TIMEOUT)

{

if (PeekMessage (&msg, hwnd, WM_DDE_ACK,

WM_DDE_ACK, PM_REMOVE))

{

GlobalDeleteAtom (HIWORD (msg.lParam)) ;

DdeAck = * (DDEACK *) & LOWORD (msg.lParam) ;

if (DdeAck.fAck == FALSE)

GlobalFree (hDdeAdvise) ;

break ;

}

}

if (DdeAck.fAck == FALSE)

break ;

while (PeekMessage (&msg, hwnd, WM_DDE_FIRST,

WM_DDE_LAST, PM_REMOVE))

{

DispatchMessage (&msg) ;

}

}

if (i < NUM_STATES)

{

MessageBox (hwnd, "Failure on WM_DDE_ADVISE!",

szAppName, MB_ICONEXCLAMATION | MB_OK) ;

}

return 0 ;

case WM_DDE_ACK :

// In response to WM_DDE_INITIATE, save server window

if (fDoingInitiate)

{

hwndServer = wParam ;

GlobalDeleteAtom (LOWORD (lParam)) ;

GlobalDeleteAtom (HIWORD (lParam)) ;

}

return 0 ;

case WM_DDE_DATA :

// wParam -- sending window handle

// LOWORD (lParam) -- DDEDATA memory handle

// HIWORD (lParam) -- item atom

hDdeData = LOWORD (lParam) ;

lpDdeData = (DDEDATA FAR *) GlobalLock (hDdeData) ;

aItem = HIWORD (lParam) ;

// Initialize DdeAck structure

DdeAck.bAppReturnCode = 0 ;

DdeAck.reserved = 0 ;

DdeAck.fBusy = FALSE ;

DdeAck.fAck = FALSE ;

// Check for matching format and data item

if (lpDdeData->cfFormat == CF_TEXT)

{

GlobalGetAtomName (aItem, szItem, sizeof (szItem)) ;

for (i = 0 ; i < NUM_STATES ; i++)

if (strcmp (szItem, pop[i].szAbb) == 0)

break ;

if (i < NUM_STATES)

{

lstrcpy (szPopulation, lpDdeData->Value) ;

pop[i].lPop = atol (szPopulation) ;

InvalidateRect (hwnd, NULL, FALSE) ;

DdeAck.fAck = TRUE ;

}

}

// Acknowledge if necessary

if (lpDdeData->fAckReq == TRUE)

{

wStatus = * (WORD *) & DdeAck ;

if (!PostMessage (wParam, WM_DDE_ACK, hwnd,

MAKELONG (wStatus, aItem)))

{

GlobalDeleteAtom (aItem) ;

GlobalUnlock (hDdeData) ;

GlobalFree (hDdeData) ;

return 0 ;

}

}

else

{

GlobalDeleteAtom (aItem) ;

}

// Clean up

if (lpDdeData->fRelease == TRUE || DdeAck.fAck == FALSE)

{

GlobalUnlock (hDdeData) ;

GlobalFree (hDdeData) ;

}

else

{

GlobalUnlock (hDdeData) ;

}

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

for (i = 0 ; i < NUM_STATES ; i++)

{

if (i < (NUM_STATES + 1) / 2)

{

x = cxChar ;

y = i * cyChar ;

}

else

{

x = 44 * cxChar ;

y = (i - (NUM_STATES + 1) / 2) * cyChar ;

}

TextOut (hdc, x, y, szBuffer,

wsprintf (szBuffer, "%-20s",

(LPSTR) pop[i].szState)) ;

x += 36 * cxChar ;

SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

TextOut (hdc, x, y, szBuffer,

wsprintf (szBuffer, "%10ld", pop[i].lPop)) ;

SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

}

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DDE_TERMINATE :

// Respond with another WM_DDE_TERMINATE message

PostMessage (hwndServer, WM_DDE_TERMINATE, hwnd, 0L) ;

hwndServer = NULL ;

return 0 ;

case WM_CLOSE :

if (hwndServer == NULL)

break ;

// Post WM_DDE_UNADVISE message

PostMessage (hwndServer, WM_DDE_UNADVISE, hwnd,

MAKELONG (CF_TEXT, NULL)) ;

dwTime = GetCurrentTime () ;

while (GetCurrentTime () - dwTime < DDE_TIMEOUT)

{

if (PeekMessage (&msg, hwnd, WM_DDE_ACK,

WM_DDE_ACK, PM_REMOVE))

break ;

}

// Post WM_DDE_TERMINATE message

PostMessage (hwndServer, WM_DDE_TERMINATE, hwnd, 0L) ;

dwTime = GetCurrentTime () ;

while (GetCurrentTime () - dwTime < DDE_TIMEOUT)

{

if (PeekMessage (&msg, hwnd, WM_DDE_TERMINATE,

WM_DDE_TERMINATE, PM_REMOVE))

break ;

}

break ; // for default processing

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

SHOWPOP.DEF

;------------------------------------

; SHOWPOP.DEF module definition file

;------------------------------------

NAME SHOWPOP

DESCRIPTION 'DDE Population Client (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

This program displays the names of the states in its window with the updated populations obtained from DDEPOP using the WM_DDE_ADVISE facility. You'll note that SHOWPOP contains a structure called pop just like DDEPOP, but this version contains the two-letter state abbreviations, the state names, and a field called lPop (initialized with zeros) that will contain the updated populations obtained from DDEPOP.

SHOWPOP carries on only one DDE conversation, so it only needs one window for this conversation, and it uses WndProc for this purpose.