|
|
1.1 root 1: /*****************************************************************************\
2: *
3: * DDEMO.C
4: *
5: * This file implements a simple DDEML sample application that demonstrates
6: * some of the ways the DDEML APIs can be used.
7: *
8: * Each instance of this application becomes both a DDE client and a DDE
9: * server with any other instances of this application that are found.
10: *
11: * Since it assumes it is talking to itself, this program takes some liberties
12: * to simplify things. For instance, this application does not support the
13: * standard SysTopic topic and does not use any standard formats.
14: *
15: * The basic concepts this application will show you are:
16: *
17: * How to use lists of conversations properly
18: * How to handle links
19: * How to handle simple asynchronous transactions
20: * How to use your own custom formats
21: *
22: \*****************************************************************************/
23: #include <windows.h>
24: #include <ddeml.h>
25: #include <stdlib.h>
26: #include <string.h>
27:
28: HDDEDATA CALLBACK DdeCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hszTopic,
29: HSZ hszItem, HDDEDATA hData, DWORD lData1, DWORD lData2);
30: VOID PaintDemo(HWND hwnd);
31: LONG APIENTRY MainWndProc(HWND hwnd, UINT message, WPARAM wParam,
32: LONG lParam);
33:
34: /*
35: * Define this value to limit how fast data changes. If we just let data
36: * change as fast a possible, we might bog down the system with DDE
37: * messages.
38: */
39: #define BASE_TIMEOUT 100
40:
41: BOOL fActive; // indicates data is changing
42: DWORD idInst = 0; // our DDEML instance object
43: HANDLE hInst; // our instance/module handle
44: HCONVLIST hConvList = 0; // the list of all convs we have open
45: HSZ hszAppName = 0; // the generic hsz for everything
46: HWND hwndMain; // our main window handle
47: TCHAR szT[20]; // static buffer for painting
48: TCHAR szTitle[] = "DDEmo"; // the generic string for everything
49: UINT OurFormat; // our custom registered format
50: int InCount = 0; // static buffer to hold incomming data
51: int cConvs = 0; // number of active conversations
52: int count = 0; // our data
53: int cyText, cxText, cyTitle; // sizes for painting
54:
55: int WINAPI WinMain(
56: HANDLE hInstance,
57: HANDLE hPrevInstance,
58: LPSTR lpCmdLine,
59: INT nCmdShow)
60: {
61: MSG msg;
62: WNDCLASS wc;
63: TEXTMETRIC metrics;
64: HDC hdc;
65:
66: wc.style = 0;
67: wc.lpfnWndProc = MainWndProc;
68: wc.cbClsExtra = 0;
69: wc.cbWndExtra = 0;
70: wc.hInstance = hInstance;
71: wc.hIcon = NULL;
72: wc.hCursor = LoadCursor(NULL, IDC_ARROW);
73: wc.hbrBackground = NULL;
74: wc.lpszMenuName = NULL;
75: wc.lpszClassName = szTitle;
76:
77: if (!RegisterClass(&wc))
78: return(FALSE);
79:
80: /*
81: * Here we tell DDEML what we will be doing.
82: *
83: * 1) We let it know our callback proc address - MakeProcInstance
84: * is called just to be more portable.
85: * 2) Filter-inits - don't accept any WM_DDE_INITIATE messages for
86: * anything but our registered service name.
87: * 3) Don't bother to notify us of confirmed connections
88: * 4) Don't allow connections with ourselves.
89: * 5) Don't bother us with XTYP_POKE transactions.
90: */
91: if (DdeInitialize(&idInst,
92: (PFNCALLBACK)MakeProcInstance((FARPROC)DdeCallback, hInstance),
93: APPCMD_FILTERINITS |
94: CBF_SKIP_CONNECT_CONFIRMS |
95: CBF_FAIL_SELFCONNECTIONS |
96: CBF_FAIL_POKES,
97: 0))
98: return(FALSE);
99:
100: hInst = hInstance;
101: hwndMain = CreateWindow(
102: szTitle,
103: szTitle,
104: WS_CAPTION | WS_BORDER | WS_SYSMENU,
105: CW_USEDEFAULT,
106: CW_USEDEFAULT,
107: 0,
108: 0,
109: NULL,
110: NULL,
111: hInstance,
112: NULL
113: );
114:
115: if (!hwndMain) {
116: DdeUninitialize(idInst);
117: return(FALSE);
118: }
119:
120: hdc = GetDC(hwndMain);
121: GetTextMetrics(hdc, &metrics);
122: cyText = metrics.tmHeight + metrics.tmExternalLeading;
123: cxText = metrics.tmMaxCharWidth * 8;
124: cyTitle = GetSystemMetrics(SM_CYCAPTION);
125: ReleaseDC(hwndMain, hdc);
126:
127: SetWindowPos(hwndMain, 0, 0, 0, cxText, cyText + cyTitle,
128: SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
129: ShowWindow(hwndMain, nCmdShow);
130: UpdateWindow(hwndMain);
131:
132: /*
133: * Initialize all our string handles for lookups later
134: */
135: hszAppName = DdeCreateStringHandle(idInst, szTitle, 0);
136: /*
137: * Register our formats
138: */
139: OurFormat = RegisterClipboardFormat(szTitle);
140: /*
141: * Connect to any other instances of ourselves that may already be
142: * running.
143: */
144: hConvList = DdeConnectList(idInst, hszAppName, hszAppName, hConvList, NULL);
145: /*
146: * Register our service -
147: * This will cause DDEML to notify DDEML clients about the existance
148: * of a new DDE service.
149: */
150: DdeNameService(idInst, hszAppName, 0, DNS_REGISTER);
151:
152: while (GetMessage(&msg, 0, 0, 0)) {
153: TranslateMessage(&msg);
154: DispatchMessage(&msg);
155: }
156:
157: DestroyWindow(hwndMain);
158: UnregisterClass(szTitle, hInstance);
159: return(FALSE);
160: }
161:
162:
163: /*
164: * BroadcastTransaction
165: *
166: * Does the specified transaction on all conversations in hConvList
167: */
168: VOID BroadcastTransaction(
169: PBYTE pSrc,
170: DWORD cbData,
171: UINT fmt,
172: UINT xtyp)
173: {
174: HCONV hConv;
175: DWORD dwResult;
176: int cConvsOrg;
177:
178: cConvsOrg = cConvs;
179: cConvs = 0;
180: if (hConvList) {
181: /*
182: * Enumerate all the conversations within this list - note that
183: * DDEML will only return active conversations. Inactive conversations
184: * are automatically removed.
185: */
186: hConv = DdeQueryNextServer(hConvList, NULL);
187: while (hConv) {
188: /*
189: * Count the active conversations while we're at it.
190: */
191: cConvs++;
192: /*
193: * Spawn an asynchronous transaction - this was chosen because
194: * we have not particular action if an error ocurrs so we just
195: * don't care too much about the results - this technique will
196: * NOT do for XTYP_REQUEST transactions though.
197: */
198: if (DdeClientTransaction(pSrc, cbData, hConv, hszAppName, fmt,
199: xtyp, TIMEOUT_ASYNC, &dwResult)) {
200: /*
201: * We immediately abandon the transaction so we don't get
202: * a bothersome XTYP_XACT_COMPLETE callback which we don't
203: * care about.
204: */
205: DdeAbandonTransaction(idInst, hConv, dwResult);
206: }
207:
208: hConv = DdeQueryNextServer(hConvList, hConv);
209: }
210: }
211: if (cConvs != cConvsOrg) {
212: /*
213: * Oh, the number of active conversations has changed. Time to
214: * repaint!
215: */
216: InvalidateRect(hwndMain, NULL, TRUE);
217: }
218: }
219:
220:
221: /*
222: * MyProcessKey
223: *
224: * We demonstrate the robustness of NT here by forcing a GP anytime the
225: * 'B' key is pressed while this window has the focus. NT should properly
226: * fake termination to all other apps connected to us.
227: */
228: VOID MyProcessKey(
229: TCHAR tchCode,
230: LONG lKeyData)
231: {
232: switch (tchCode) {
233: case TEXT('B'):
234: case TEXT('b'):
235: *((PBYTE)(-1)) = 0; // Cause GP fault!
236: break;
237: }
238: }
239:
240:
241:
242: LONG APIENTRY MainWndProc(
243: HWND hwnd,
244: UINT message,
245: WPARAM wParam,
246: LONG lParam)
247: {
248: RECT rc;
249:
250: switch (message) {
251: case WM_CREATE:
252: /*
253: * initially we are inactive - this reduces some of the message
254: * traffic while we are initializing - but we could start active fine.
255: */
256: fActive = FALSE;
257: break;
258:
259: case WM_RBUTTONDOWN:
260: if (GetKeyState(VK_CONTROL) & 0x8000) {
261: /*
262: * A CTRL R_BUTTON click will cause ALL instances of this app
263: * to become inactive.
264: */
265: BroadcastTransaction("PAUSE", 6, 0, XTYP_EXECUTE);
266: MessageBeep(0);
267: }
268: /*
269: * A R_BUTTON click makes us inactive. Repaint to show state change.
270: * We do a synchronous update in case there is too much DDE message
271: * activity to allow the WM_PAINT messages through. Remember DDE
272: * messages have priority over others!
273: */
274: KillTimer(hwndMain, 1);
275: fActive = FALSE;
276: InvalidateRect(hwnd, NULL, TRUE);
277: UpdateWindow(hwnd);
278: break;
279:
280: case WM_LBUTTONDOWN:
281: if (GetKeyState(VK_CONTROL) & 0x8000) {
282: /*
283: * A CTRL L_BUTTON click will cause ALL instances of this app
284: * to become active.
285: */
286: BroadcastTransaction("RESUME", 7, 0, XTYP_EXECUTE);
287: MessageBeep(0);
288: }
289: /*
290: * An L_BUTTON click makes us active. Repaint to show state change.
291: */
292: SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
293: fActive = TRUE;
294: InvalidateRect(hwnd, NULL, TRUE);
295: UpdateWindow(hwnd);
296: break;
297:
298: case WM_CHAR:
299: MyProcessKey((TCHAR)wParam, lParam);
300: break;
301:
302: case WM_TIMER:
303: /*
304: * We use timers for simplicity. On Win3.1 we could run out of
305: * timers easily but we don't have this worry on NT.
306: *
307: * Each tick, we increment our data and call DdePostAdvise() to
308: * update any links there may be on this data. DDEML makes link
309: * updates on specific items quite easy.
310: */
311: count++;
312: DdePostAdvise(idInst, hszAppName, hszAppName);
313: /*
314: * Invalidate the part of ourselves that shows our data and
315: * synchronously update it in case DDE message activity is blocking
316: * paints.
317: */
318: SetRect(&rc, 0, 0, cxText, cyText);
319: InvalidateRect(hwndMain, &rc, TRUE);
320: UpdateWindow(hwndMain);
321: break;
322:
323: case WM_PAINT:
324: PaintDemo(hwnd);
325: break;
326:
327: case WM_CLOSE:
328: KillTimer(hwnd, 1);
329: /*
330: * We do DDE cleanup here. It is best to do DDE cleanup while
331: * still in the message loop to allow DDEML to recieve messages
332: * while shutting down.
333: */
334: DdeDisconnectList(hConvList);
335: DdeNameService(idInst, 0, 0, DNS_UNREGISTER);
336: DdeFreeStringHandle(idInst, hszAppName);
337: DdeUninitialize(idInst);
338: PostQuitMessage(0);
339: break;
340:
341: default:
342: return (DefWindowProc(hwnd, message, wParam, lParam));
343: }
344: return(0);
345: }
346:
347:
348: VOID PaintDemo(
349: HWND hwnd)
350: {
351: PAINTSTRUCT ps;
352: RECT rc;
353: HCONV hConv;
354: CONVINFO ci;
355: int cConvsOrg = cConvs;
356:
357: BeginPaint(hwnd, &ps);
358: /*
359: * Draw our data on top - Black for active, Grey for inactive.
360: */
361: SetRect(&rc, 0, 0, cxText, cyText);
362: SetBkMode(ps.hdc, TRANSPARENT);
363: SetTextColor(ps.hdc, 0x00FFFFFF); // white text
364: FillRect(ps.hdc, &rc, GetStockObject(fActive ? BLACK_BRUSH : GRAY_BRUSH));
365: DrawText(ps.hdc, itoa(count, szT, 10), -1, &rc, DT_CENTER | DT_VCENTER);
366:
367: /*
368: * Now draw the most recently recieved data from each server we are
369: * connected to.
370: */
371: if (hConvList) {
372: OffsetRect(&rc, 0, cyText);
373: SetTextColor(ps.hdc, 0); // draw black text
374: cConvs = 0;
375: hConv = DdeQueryNextServer(hConvList, NULL);
376: while (hConv) {
377: cConvs++;
378: /*
379: * count how many conversations are active while we're at it.
380: */
381: ci.cb = sizeof(CONVINFO);
382: DdeQueryConvInfo(hConv, QID_SYNC, &ci);
383: FillRect(ps.hdc, &rc, GetStockObject(WHITE_BRUSH)); // white bkgnd
384: DrawText(ps.hdc, itoa(ci.hUser, szT, 10), -1, &rc,
385: DT_CENTER | DT_VCENTER);
386: OffsetRect(&rc, 0, cyText);
387: hConv = DdeQueryNextServer(hConvList, hConv);
388: }
389: }
390: EndPaint(hwnd, &ps);
391: if (cConvsOrg != cConvs) {
392: /*
393: * The number of active conversations changed! Resize to fit.
394: */
395: SetWindowPos(hwndMain, 0, 0, 0, cxText,
396: (cyText * (cConvs + 1)) + cyTitle,
397: SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
398: }
399: }
400:
401:
402:
403: /*
404: * This is the main DDEML callback proc. It handles all interaction with
405: * DDEML that is DDEML originated.
406: */
407: HDDEDATA CALLBACK DdeCallback(
408: WORD wType,
409: WORD wFmt,
410: HCONV hConv,
411: HSZ hszTopic,
412: HSZ hszItem,
413: HDDEDATA hData,
414: DWORD lData1,
415: DWORD lData2)
416: {
417: LPTSTR pszExec;
418:
419: switch (wType) {
420: case XTYP_CONNECT:
421: /*
422: * Only allow connections to us. We can always return TRUE because
423: * the CBF_FILTERINITS bit given to DdeInitialize() told DDEML to
424: * never bother us with connections to any service names other than
425: * what we have registered.
426: *
427: * Note that we do not handle the XTYP_WILD_CONNECT transaction.
428: * This means that no wild-card initiates to us will work.
429: */
430: return(TRUE);
431:
432: case XTYP_ADVREQ:
433: case XTYP_REQUEST:
434: /*
435: * These two transactions are the only ones that require us to
436: * render our data. By using a custom format, we don't have to
437: * convert our count to text form to support CF_TEXT.
438: */
439: return(DdeCreateDataHandle(idInst, (PBYTE)&count, sizeof(count), 0,
440: hszAppName, OurFormat, 0));
441:
442: case XTYP_ADVSTART:
443: /*
444: * Only allow links to our Item in our format.
445: */
446: return(wFmt == OurFormat && hszItem == hszAppName);
447:
448: case XTYP_ADVDATA:
449: /*
450: * Data is comming in. We don't bother with XTYP_POKE transactions,
451: * but if we did, they would go here. Since we only allow links
452: * on our item and our format, we need not check these here.
453: */
454: if (DdeGetData(hData, (PBYTE)&InCount, sizeof(InCount), 0)) {
455: DdeSetUserHandle(hConv, QID_SYNC, InCount);
456: }
457: /*
458: * update ourselves to reflect the new incomming data.
459: */
460: InvalidateRect(hwndMain, NULL, TRUE);
461: /*
462: * This transaction requires a flag return value. We could also
463: * stick other status bits here if needed but its not recommended.
464: */
465: return(DDE_FACK);
466:
467: case XTYP_EXECUTE:
468: /*
469: * Another instance wants us to do something. DdeAccessData()
470: * makes parsing of execute strings easy. Also note, that DDEML
471: * will automatically give us the string in the right form
472: * (UNICODE vs ASCII) depending on which form of DdeInitialize()
473: * we called.
474: */
475: pszExec = DdeAccessData(hData, NULL);
476: if (pszExec) {
477: if (fActive && !stricmp("PAUSE", pszExec)) {
478: KillTimer(hwndMain, 1);
479: fActive = FALSE;
480: InvalidateRect(hwndMain, NULL, TRUE);
481: UpdateWindow(hwndMain);
482: } else if (!fActive && !stricmp("RESUME", pszExec)) {
483: SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL);
484: fActive = TRUE;
485: InvalidateRect(hwndMain, NULL, TRUE);
486: UpdateWindow(hwndMain);
487: }
488: /*
489: * The beep gives good feedback on how fast the execute was.
490: */
491: MessageBeep(0);
492: }
493: break;
494:
495: case XTYP_DISCONNECT:
496: /*
497: * Somebody went away, repaint so we update our cConvs count.
498: */
499: InvalidateRect(hwndMain, NULL, TRUE);
500: break;
501:
502: case XTYP_REGISTER:
503: /*
504: * Since a new server just arrived, lets make sure our links are
505: * up to date. Note that only one link on a
506: * conversation/topic/item/format set will work anyway so we don't
507: * worry about duplicate links.
508: *
509: * Note also that we are using hszItem - which is the InstanceSpecific
510: * name of the server that is registering. This greatly reduces the
511: * number of messages that go flying around.
512: */
513: hConvList = DdeConnectList(idInst, hszItem, hszAppName, hConvList, NULL);
514: BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART);
515: SetWindowPos(hwndMain, 0, 0, 0, cxText,
516: (cyText * (cConvs + 1)) + cyTitle, SWP_NOMOVE | SWP_NOZORDER);
517: UpdateWindow(hwndMain);
518: return(TRUE);
519: }
520: return(0);
521: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.