DDE.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/

/*
* This module serves to demonstrate one way a sophisticated DDE server
* that uses enumerable topics and items might be implemented. It takes
* full advantage of appowned data handles (when fAppowned is set) to
* minimize the need for repeated rendering of data when shared with
* multiple clients.
*
* The server supports full system topic information plus help and non
* system topic item enumeration for the benefit of browsing clients
* that are wondering what's around.
*
* This server can be made secure by altering the conversation context
* filter.
*
* This server can appear to support alternate codepages and languages
* by altering the conversation context filter. On Windows this is
* pretty much moot since there is not yet a clearly defined way of
* doing international communication and because the atom manager restricts
* what topic and item strings can be used on the system.
*/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "server.h"
#include "huge.h"

/*
* This function verifies that the incomming conversation context fits the
* server's context filter's requirements.
*/
BOOL ValidateContext(
PCONVCONTEXT pCC)
{
// make sure our CCFilter allows it...mock security, language support
// old DDE app client case...pCC == NULL
if (pCC == NULL &&
CCFilter.dwSecurity == 0 && // were nonsecure
CCFilter.iCodePage == CP_WINANSI && // were normal cp
CCFilter.qos.ImpersonationLevel == SecurityImpersonation
) {
return(TRUE);
}

if (pCC &&
pCC->wFlags == CCFilter.wFlags && // no special flags needed
pCC->iCodePage == CCFilter.iCodePage && // codepages match
pCC->dwSecurity == CCFilter.dwSecurity && // security passes
pCC->qos.ImpersonationLevel >= CCFilter.qos.ImpersonationLevel) {
// dont care about language and country.
return(TRUE);
}
return(FALSE); // disallow no match
}


/***************************** Public Function ****************************\
*
* This function is called by the DDE manager DLL and passes control onto
* the apropriate function pointed to by the global topic and item arrays.
* It handles all DDE interaction generated by external events.
*
\***************************************************************************/
HDDEDATA CALLBACK DdeCallback(
UINT wType,
UINT wFmt,
HCONV hConv,
HSZ hszTopic,
HSZ hszItem,
HDDEDATA hData,
DWORD lData1,
DWORD lData2)
{
WORD i, j;
register ITEMLIST *pItemList;
WORD cItems, iFmt;
HDDEDATA hDataRet;

/*
* Block this callback if its blockable and we are supposed to.
*/
if (fBlockNextCB && !(wType & XTYPF_NOBLOCK)) {
fBlockNextCB = FALSE;
fAllEnabled = FALSE;
return(CBR_BLOCK);
}

/*
* Block this callback if its associated with a conversation and we
* are supposed to.
*/
if (fTermNextCB && hConv) {
fTermNextCB = FALSE;
DdeDisconnect(hConv);
wType = XTYP_DISCONNECT;
}

/*
* Keep a count of connections
*/
if (wType == XTYP_CONNECT_CONFIRM) {
cServers++;
InvalidateRect(hwndServer, &rcConnCount, TRUE);
return(0);
}
if (wType == XTYP_DISCONNECT) {
cServers--;
InvalidateRect(hwndServer, &rcConnCount, TRUE);
return(0);
}


/*
* only allow transactions on the formats we support if they have a format.
*/
if (wFmt) {
for (iFmt = 0; iFmt < CFORMATS; iFmt++) {
if ((ATOM)wFmt == aFormats[iFmt].atom)
break;
}
if (iFmt == CFORMATS)
return(0); // illegal format - ignore now.
}

/*
* Executes are allowed only on the system topic. This is a general
* convention, not a requirement.
*
* Any executes received result in the execute text being shown in
* the server client area. No real action is taken.
*/
if (wType == XTYP_EXECUTE) {
if (hszTopic == topicList[0].hszTopic) { // must be on system topic
// Format is assumed to be CF_TEXT.
DdeGetData(hData, (LPBYTE)szExec, MAX_EXEC, 0);
szExec[MAX_EXEC - 1] = TEXT('\0');
InvalidateRect(hwndServer, &rcExec, TRUE);
hDataRet = (HDDEDATA)TRUE;
goto ReturnSpot;
}
pszComment = TEXT("Execute received on non-system topic - ignored");
InvalidateRect(hwndServer, &rcComment, TRUE);
return(0);
}

/*
* Process wild initiates here
*/
if (wType == XTYP_WILDCONNECT) {
HSZ ahsz[(CTOPICS + 1) * 2];
/*
* He wants a hsz list of all our available app/topic pairs
* that conform to hszTopic and hszItem(App).
*/

if (!ValidateContext((PCONVCONTEXT)lData1)) {
return(FALSE);
}

if (hszItem != hszAppName && hszItem != 0) {
// we only support the hszAppName service
return(0);
}

// scan the topic table and create hsz pairs
j = 0;
for (i = 0; i < CTOPICS; i++) {
if (hszTopic == 0 || hszTopic == topicList[i].hszTopic) {
ahsz[j++] = hszAppName;
ahsz[j++] = topicList[i].hszTopic;
}
}

// cap off the list with 0s
ahsz[j++] = ahsz[j++] = 0L;

// send it back
return(DdeCreateDataHandle(idInst, (LPBYTE)&ahsz[0], sizeof(HSZ) * j, 0L, 0, wFmt, 0));
}

/*
* Check our hsz tables and send to the apropriate proc. to process.
* We use DdeCmpStringHandles() which is the portable case-insensitive
* method of comparing string handles. (this is a macro on windows so
* there is no real speed hit.) On WINDOWS, HSZs are case-insensitive
* anyway, but this may not be the case on other platforms.
*/
for (i = 0; i < CTOPICS; i++) {
if (DdeCmpStringHandles(topicList[i].hszTopic, hszTopic) == 0) {

/*
* connections must be on a topic we support.
*/
if (wType == XTYP_CONNECT) {
return((HDDEDATA)ValidateContext((PCONVCONTEXT)lData1));
}

pItemList = topicList[i].pItemList;
cItems = topicList[i].cItems;
for (j = 0; j < cItems; j++) {
if (DdeCmpStringHandles(pItemList[j].hszItem, hszItem) == 0) {
XFERINFO xi;

/*
* If Win32s - security isn't supported
*/
if(GetVersion() & 0x80000000) {
/*
* Make call to worker function here...
*/
xi.wType = wType;
xi.wFmt = wFmt;
xi.hConv = hConv;
xi.hszTopic = hszTopic;
xi.hszItem = hszItem;
xi.hData = hData;
xi.lData1 = lData1;
xi.lData2 = lData2;
hDataRet = (*pItemList[j].npfnCallback)(&xi, iFmt);
} else {
/*
* Windows NT - impersonate client here
*/
if (DdeImpersonateClient(hConv)) {
/*
* Make call to worker function here...
*/
xi.wType = wType;
xi.wFmt = wFmt;
xi.hConv = hConv;
xi.hszTopic = hszTopic;
xi.hszItem = hszItem;
xi.hData = hData;
xi.lData1 = lData1;
xi.lData2 = lData2;
hDataRet = (*pItemList[j].npfnCallback)(&xi, iFmt);
RevertToSelf();
} else {
pszComment = TEXT("Impersonation failed.");
InvalidateRect(hwndServer, &rcComment, TRUE);
hDataRet = 0;
}
}

ReturnSpot:
/*
* The table functions return a boolean or data.
* It gets translated here.
*/
switch (wType & XCLASS_MASK) {
case XCLASS_DATA:
return(hDataRet);
break;
case XCLASS_FLAGS:
return(HDDEDATA)(hDataRet ? DDE_FACK : DDE_FNOTPROCESSED);
break;
case XCLASS_BOOL:
return((HDDEDATA)TRUE);
default: // XCLASS_NOTIFICATION
return(0);
break;
}
break;
}
}
break;
}
}

/*
* anything else fails - DDEML is designed so that a 0 return is ALWAYS ok.
*/
return(0);
}





/***************************** Private Function ****************************\
* This passes out a standard tab-delimited list of topic names for this
* application.
*
* This support is required for other apps to be able to
* find out about us. This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA TopicListXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
WORD cbAlloc, i;
LPTSTR pszTopicList;
HDDEDATA hData;

if (pXferInfo->wType == XTYP_ADVSTART)
return((HDDEDATA)TRUE);

if (pXferInfo->wType != XTYP_REQUEST &&
pXferInfo->wType != XTYP_ADVREQ)
return(0);
/*
* construct the list of topics we have
*/
cbAlloc = 0;
for (i = 0; i < CTOPICS; i++)
cbAlloc += (_tcslen(topicList[i].pszTopic) + 1) * sizeof(TCHAR); // 1 for tab

// allocate a data handle big enough for the list.
hData = DdeCreateDataHandle(idInst, NULL, 0, cbAlloc, pXferInfo->hszItem,
pXferInfo->wFmt, 0);
pszTopicList = (LPTSTR)DdeAccessData(hData, NULL);
if (pszTopicList) {
for (i = 0; i < CTOPICS; i++) {
_tcscpy(pszTopicList, topicList[i].pszTopic);
pszTopicList += _tcslen(topicList[i].pszTopic);
*pszTopicList++ = TEXT('\t');
}
*--pszTopicList = TEXT('\0');
DdeUnaccessData(hData);
return(hData);
}
return(0);
}




/***************************** Private Function ****************************\
* This passes out a standard tab-delimited list of item names for the
* specified topic.
*
* This support is required for other apps to be able to
* find out about us. This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA ItemListXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
WORD cbAlloc, i, iItem, cItems;
ITEMLIST *pItemList = 0;
LPTSTR pszItemList;
HDDEDATA hData;

if (pXferInfo->wType == XTYP_ADVSTART)
return((HDDEDATA)TRUE);

if (pXferInfo->wType != XTYP_REQUEST &&
pXferInfo->wType != XTYP_ADVREQ)
return(0);
/*
* construct the list of items we support for this topic - this supports
* more than the minimum standard which would support SysItems only on
* the system topic.
*/

// locate the requested topic item table
for (i = 0; i < CTOPICS; i++) {
if (pXferInfo->hszTopic == topicList[i].hszTopic) {
pItemList = topicList[i].pItemList;
cItems = topicList[i].cItems;
break;
}
}

if (!pItemList)
return(0); // item not found

cbAlloc = 0;
for (iItem = 0; iItem < cItems; iItem++)
cbAlloc += (_tcslen(pItemList[iItem].pszItem) + 1) * sizeof(TCHAR); // 1 for tab

// allocate a data handle big enough for the list.
hData = DdeCreateDataHandle(idInst, NULL, 0, cbAlloc, pXferInfo->hszItem,
pXferInfo->wFmt, 0);
pszItemList = (LPTSTR)DdeAccessData(hData, NULL);
if (pszItemList) {
for (i = 0; i < cItems; i++) {
_tcscpy(pszItemList, pItemList[i].pszItem);
pszItemList += _tcslen(pItemList[i].pszItem);
*pszItemList++ = TEXT('\t');
}
*--pszItemList = TEXT('\0');
DdeUnaccessData(hData);
return(hData);
}
return(0);
}





/***************************** Private Function ****************************\
* Gives out a 0 terminated array of dde format numbers supported by this app.
*
* This support is required for other apps to be able to
* find out about us. This kind of support should be in every DDE
* application.
*
\***************************************************************************/
HDDEDATA sysFormatsXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
INT i, cb;
LPTSTR psz, pszT;
HDDEDATA hData;

if (pXferInfo->wType == XTYP_ADVSTART)
return((HDDEDATA)TRUE);

if (pXferInfo->wType != XTYP_REQUEST &&
pXferInfo->wType != XTYP_ADVREQ)
return(0);

for (i = 0, cb = 0; i < CFORMATS; i++)
cb += (_tcslen(aFormats[i].sz) + 1) * sizeof(TCHAR);

hData = DdeCreateDataHandle(idInst, NULL, (DWORD)cb,
0L, pXferInfo->hszItem, pXferInfo->wFmt, 0);
psz = pszT = (LPTSTR)DdeAccessData(hData, NULL);
for (i = 0; i < CFORMATS; i++) {
_tcscpy(pszT, aFormats[i].sz);
pszT += _tcslen(pszT);
*pszT++ = TEXT('\t');
}
*(--pszT) = TEXT('\0');
DdeUnaccessData(hData);
return(hData);
}



/*
* This is a runaway item. Each time it is requested, it changes.
* pokes just make it change again.
*/
HDDEDATA TestRandomXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
TCHAR szT[10]; // SS==DS!
LPTSTR pszData;
HDDEDATA hData;
WORD i;

switch (pXferInfo->wType) {
case XTYP_POKE:
// we expect an ascii number to replace the current seed.
pszComment = TEXT("Rand poke received.");
InvalidateRect(hwndServer, &rcComment, TRUE);
InvalidateRect(hwndServer, &rcRand, TRUE);
if (DdeGetData(pXferInfo->hData, (PBYTE)szT, 10, 0)) {
szT[9] = TEXT('\0'); // just incase we overran.
_stscanf(szT, TEXT("%d"), &seed);
for (i = 0; i < CFORMATS; i++) {
if (hDataRand[i])
DdeFreeDataHandle(hDataRand[i]);
hDataRand[i] = 0;
}
DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
return((HDDEDATA)1);
}
break;

case XTYP_REQUEST:
pszComment = TEXT("Rand data requested.");
InvalidateRect(hwndServer, &rcComment, TRUE);
case XTYP_ADVREQ:
Delay(RenderDelay, FALSE);
if (!hDataRand[iFmt]) {
hDataRand[iFmt] = DdeCreateDataHandle(idInst, NULL, 0, 10,
pXferInfo->hszItem, (UINT)pXferInfo->wFmt,
fAppowned ? HDATA_APPOWNED : 0);
if (pszData = (LPTSTR)DdeAccessData(hDataRand[iFmt], NULL)) {
wsprintf(pszData, TEXT("%d"), seed);
DdeUnaccessData(hDataRand[iFmt]);
}
}
hData = hDataRand[iFmt];
if (!fAppowned)
hDataRand[iFmt] = 0;
return(hData);
break;

case XTYP_ADVSTART:
return((HDDEDATA)1);
}
return(0);
}

/*
* This is a runaway item. Each time it is requested, it changes.
* pokes just make it change again.
*/
HDDEDATA TestCountXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
TCHAR szT[16]; // SS==DS!
LPTSTR pszData;
HDDEDATA hData;
WORD i;

switch (pXferInfo->wType) {
case XTYP_POKE:
// we expect an ascii number to replace the current count.
pszComment = TEXT("Count poke received");
InvalidateRect(hwndServer, &rcComment, TRUE);
InvalidateRect(hwndServer, &rcCount, TRUE);
if (DdeGetData(pXferInfo->hData, (PBYTE)szT, 10, 0)) {
szT[9] = TEXT('\0'); // just incase we overran.
_stscanf(szT, TEXT("%ld"), &count);
for (i = 0; i < CFORMATS; i++) {
if (hDataCount[i])
DdeFreeDataHandle(hDataCount[i]);
hDataCount[i] = 0;
}
DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
return((HDDEDATA)1);
}
break;

case XTYP_REQUEST:
pszComment = TEXT("Count data requested.");
InvalidateRect(hwndServer, &rcComment, TRUE);
case XTYP_ADVREQ:
Delay(RenderDelay, FALSE);
if (!hDataCount[iFmt]) {
hDataCount[iFmt] = DdeCreateDataHandle(idInst, NULL, 0, 10, pXferInfo->hszItem,
pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
if (pszData = (LPTSTR)DdeAccessData(hDataCount[iFmt], NULL)) {
wsprintf(pszData, TEXT("%ld"), count);
DdeUnaccessData(hDataCount[iFmt]);
}
}
hData = hDataCount[iFmt];
if (!fAppowned)
hDataCount[iFmt] = 0;
return(hData);
break;

case XTYP_ADVSTART:
return((HDDEDATA)1);
}
return(0);
}


/*
* This is not a runaway item. Only Pokes make it change.
*/
HDDEDATA TestHugeXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
BOOL fSuccess;
DWORD ulcb;
LPBYTE lpData;
WORD i;
HDDEDATA hData;

switch (pXferInfo->wType) {
case XTYP_POKE:
ulcb = DdeGetData(pXferInfo->hData, NULL, 0, 0);
fSuccess = CheckHugeData(pXferInfo->hData);
if (fSuccess) {
pszComment = TEXT("Huge poke data successfully received.");
} else {
wsprintf(szComment, TEXT("%ld bytes of invalid Huge data received."), ulcb);
pszComment = szComment;
}
InvalidateRect(hwndServer, &rcComment, TRUE);
InvalidateRect(hwndServer, &rcHugeSize, TRUE);
if (fSuccess) {
for (i = 0; i < CFORMATS; i++) {
if (hDataHuge[i]) {
DdeFreeDataHandle(hDataHuge[i]);
hDataHuge[i] = 0;
}
}
/*
* Since callback data handles are only good for the duration of
* the callback, we must copy the data to our own data handle.
*/
lpData = DdeAccessData(pXferInfo->hData, &cbHuge);
hDataHuge[iFmt] = DdeCreateDataHandle(idInst, lpData, cbHuge, 0,
pXferInfo->hszItem, pXferInfo->wFmt, fAppowned ? HDATA_APPOWNED : 0);
DdeUnaccessData(pXferInfo->hData);
DdePostAdvise(idInst, pXferInfo->hszTopic, pXferInfo->hszItem);
}
return((HDDEDATA)fSuccess);
break;

case XTYP_REQUEST:
pszComment = TEXT("Huge data requested.");
InvalidateRect(hwndServer, &rcComment, TRUE);
case XTYP_ADVREQ:
Delay(RenderDelay, FALSE);
if (!hDataHuge[iFmt]) {
cbHuge = (DWORD)rand() * 64L + 0x10000L;
wsprintf(szComment, TEXT("Generating huge data - length=%ld..."), cbHuge);
pszComment = szComment;
InvalidateRect(hwndServer, &rcComment, TRUE);
UpdateWindow(hwndServer);
hDataHuge[iFmt] = CreateHugeDataHandle((LONG)cbHuge, 4325, 345, 5,
pXferInfo->hszItem,
pXferInfo->wFmt, (WORD)(fAppowned ? HDATA_APPOWNED : 0));
pszComment = TEXT("");
InvalidateRect(hwndServer, &rcComment, TRUE);
InvalidateRect(hwndServer, &rcHugeSize, TRUE);
}
hData = hDataHuge[iFmt];
if (!fAppowned)
hDataHuge[iFmt] = 0;
return(hData);
break;

case XTYP_ADVSTART:
return((HDDEDATA)1);
}
return(0);
}


HDDEDATA HelpXfer(
PXFERINFO pXferInfo,
WORD iFmt)
{
HDDEDATA hData;

switch (pXferInfo->wType) {
case XTYP_REQUEST:
pszComment = TEXT("Help text requested.");
InvalidateRect(hwndServer, &rcComment, TRUE);
case XTYP_ADVREQ:
if (!hDataHelp[iFmt]) {
hDataHelp[iFmt] = DdeCreateDataHandle(idInst,
(PBYTE)szDdeHelp,
(_tcslen(szDdeHelp) + 1) * sizeof(TCHAR),
0, pXferInfo->hszItem, pXferInfo->wFmt,
fAppowned ? HDATA_APPOWNED : 0);
}
hData = hDataHelp[iFmt];
if (!fAppowned)
hDataHelp[iFmt] = 0;
return(hData);
break;

case XTYP_ADVSTART:
return((HDDEDATA)1);
}
return(0);
}


/***************************** Private Function ****************************\
* This creates often used global hszs from standard global strings.
* It also fills the hsz fields of the topic and item tables.
*
\***************************************************************************/
VOID Hszize()
{
register ITEMLIST *pItemList;
int iTopic, iItem;

hszAppName = DdeCreateStringHandle(idInst, szServer, 0);

for (iTopic = 0; iTopic < CTOPICS; iTopic++) {
topicList[iTopic].hszTopic =
DdeCreateStringHandle(idInst, topicList[iTopic].pszTopic, 0);
pItemList = topicList[iTopic].pItemList;
for (iItem = 0; iItem < (int)topicList[iTopic].cItems; iItem++) {
pItemList[iItem].hszItem =
DdeCreateStringHandle(idInst, pItemList[iItem].pszItem, 0);
}
}
}





/***************************** Private Function ****************************\
* This destroys often used global hszs from standard global strings.
*
\***************************************************************************/
VOID UnHszize()
{
register ITEMLIST *pItemList;
int iTopic, iItem;

DdeFreeStringHandle(idInst, hszAppName);

for (iTopic = 0; iTopic < CTOPICS; iTopic++) {
DdeFreeStringHandle(idInst, topicList[iTopic].hszTopic);
pItemList = topicList[iTopic].pItemList;
for (iItem = 0; iItem < (int)topicList[iTopic].cItems; iItem++) {
DdeFreeStringHandle(idInst, pItemList[iItem].hszItem);
}
}
}