You need to create a number of variables HANDLE ghMyInstance; //my instance, simple short gnMyInstanceCount; //2 bytes HWND hwndMain; //the main visible window HWND hwndDDE; //The invisible DDE window. HWND hwndMyContact; //the DDE window our DDE window talks to BOOL bTerminating; //Boolean. Any number of bytes. BOOL bAlreadyConversing; //Tre or False, whether or not we are already playing ATOM atomPlayerNotReady; //atom ATOM atomPlayerReady; //atom ATOM atomMyName; //atom ATOM atomNewHeartsPlayer; //atom HANDLE hDDEInformation; //Handle to a Globally allocated memory, allocated by dealer. You need a number of core functions to get off the ground int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow); long FAR PASCAL MainWndProc (HWND, unsigned, WORD, LONG); long FAR PASCAL DDEWndProc (HWND, unsigned, WORD, LONG); BOOL InitApplication (HANDLE); void UnInitApplication (); BOOL InitInstance (HANDLE,HANDLE, int); BOOL FAR PASCAL AboutDlgProc (HWND, unsigned, WORD, LONG); char* ConvertCardToText (cardtype* cardToConvert, char* szResultText); short ProcessInitiate (HWND hwnd, unsigned message, WORD wParam, LONG lParam); short ProcessTermination (HWND hwnd, unsigned message, WORD wParam, LONG lParam); short ProcessDealerMessage (HWND hwnd, unsigned message, WORD wParam, LONG lParam); void CauseConversationTermination(short bMakeSelfAvailableAgain); This is what each of the functions need to do. int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ // All you have to do is simply initialize your code. // Create the main window and the DDE window. // Run the message loop // //example C code. MSG msg; if(!hPrevInstance){ if(!InitApplication(hInstance)) return (FALSE); } if(!InitInstance(hInstance, hPrevInstance, nCmdShow)) return (FALSE); while (GetMessage(&msg, NULL, NULL, NULL)){ TranslateMessage(&msg); DispatchMessage(&msg); } UnInitApplication(); return (msg.wParam); } BOOL InitApplication(HANDLE hInstance){ // If there was no previous instance of this program, then call this function. // Register the main window and DDE window classes. //example C code WNDCLASS wc; wc.style = NULL; wc.lpfnWndProc = (long(pascal*)(unsigned int,unsigned int,unsigned int,long))MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = 0; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = SZ_CLIENT_WINDOW_CLASS; wc.lpszClassName = SZ_CLIENT_WINDOW_CLASS; if(!RegisterClass(&wc)) return FALSE; wc.style = NULL; wc.lpfnWndProc = (long(pascal*)(unsigned int,unsigned int,unsigned int,long))DDEWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = SZ_CLIENT_DDE_WINDOW_CLASS; return RegisterClass(&wc); } BOOL InitInstance(HANDLE hInstance, HANDLE hPrevInstance, int nCmdShow){ //Create the two windows, the main and DDE windows. //Show the main window. //Create the four necessary atoms. //Send a message to all 'overlapped' windows telling them we are here. //In the example code, we get the count of ourselves by asking any previous // instance what its count was. This is useful if we want to play two or // more of them at the same time. Giving ourselves a count makes it easier // to identify which player is which. //example C code HDC hDC; ghMyInstance = hInstance; hwndMain = CreateWindow(SZ_CLIENT_WINDOW_CLASS,SZ_CLIENT_WINDOW_NAME, WS_OVERLAPPEDWINDOW,100,100,100,100, //Height NULL,NULL,hInstance,NULL); if(!hwndMain) return FALSE; ShowWindow(hwndMain, nCmdShow); hwndDDE = CreateWindow(SZ_CLIENT_DDE_WINDOW_CLASS, SZ_CLIENT_DDE_WINDOW_NAME, WS_OVERLAPPED,0,0,1,1,NULL,NULL,hInstance, NULL); if(!hwndDDE){ DestroyWindow(hwndMain); return FALSE; } if(hPrevInstance) gnMyInstanceCount =SendMessage(hPrevInstance,WM_WHATS_YOUR_INSTANCE_COUNT, 0, 0L)+1; if(gnMyInstanceCount>0) //only put this count if there is more than a single instance of me. wsprintf(ATOM_MY_NAME+N_ATOM_MY_NAME, "%d",gnMyInstanceCount+1); //'+1' converts to 1-based counting atomPlayerNotReady =GlobalAddAtom(ATOM_TOPIC_YOUR_CONTACT); atomPlayerReady =GlobalAddAtom(ATOM_TOPIC_PLAYER_READY); atomMyName =GlobalAddAtom(ATOM_MY_NAME); atomNewHeartsPlayer=GlobalAddAtom(ATOM_TOPIC_NEW_HEARTS_PLAYER); //We send out a message to everyone (especially any Hearts games playing) that we are here. SendMessage(SEND_TO_ALL_WINDOWS, HM_INITIATE_CONVERSATION, hwndDDE, MAKELONG(atomMyName,atomNewHeartsPlayer)); return TRUE; } void UnInitApplication(){ //This is called when the user quits the external player application. //If we are still in a game with the dealer, then 'cause it to stop' //destroy the DDE window. We don't destroy the main window here becuase // presumably the destruction of it by other means is what caused this // function to be called. //Delete the four all-important atoms. //example C code. if(bAlreadyConversing) CauseConversationTermination(FALSE); if(hwndDDE) DestroyWindow(hwndDDE); GlobalDeleteAtom(atomPlayerNotReady); GlobalDeleteAtom(atomPlayerReady); GlobalDeleteAtom(atomMyName); GlobalDeleteAtom(atomNewHeartsPlayer); } long FAR PASCAL MainWndProc(HWND hwnd, unsigned message, WORD wParam, LONG lParam){ //This is our main window procedure. //We don't do anything unusual here except respond to the WM_WHATS_YOUR_INSTANCE_COUNT // message and call the 'CauseConversationTermination()' function in the // WM_DESTROY message. //example C code PAINTSTRUCT paintstructTemp; HDC hdcTemp; FARPROC procAboutBox; switch (message){ case WM_PAINT: hdcTemp = BeginPaint(hwnd, &paintstructTemp); //Put whatever you want here. EndPaint(hwnd, &paintstructTemp); break; case WM_DESTROY: bTerminating = TRUE; CauseConversationTermination(FALSE); //Save This Function Call. PostQuitMessage(0); break; case WM_COMMAND: switch (wParam){ case ID_ABOUT: procAboutBox = MakeProcInstance((FARPROC)AboutDlgProc, ghMyInstance); if(procAboutBox){ DialogBox(ghMyInstance, SZ_ABOUT_DIALOG_NAME, hwndMain, procAboutBox); FreeProcInstance(procAboutBox); } break; case ID_QUIT: PostQuitMessage(0); break; case ID_END_PLAY: CauseConversationTermination(TRUE); break; } break; case WM_WHATS_YOUR_INSTANCE_COUNT: return gnMyInstanceCount; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 1L; } BOOL FAR PASCAL AboutDlgProc(HWND hDlg, unsigned message, WORD wParam, LONG lParam){ //This is the 'About' dialog procedure. You may want to make an 'About box' that // gives some information about the external player. Then give it an OK button //example C code switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if(wParam == IDOK || wParam == IDCANCEL) { EndDialog(hDlg, TRUE); return TRUE; } break; } return FALSE; } long FAR PASCAL DDEWndProc(HWND hwnd, unsigned message, WORD wParam, LONG lParam){ //This is the DDE window's procedure. It must respond to the three important // DDE messages from the dealer. It doesn't need to paint becuase it is invisible. // Each of the three messages from the dealer have an associated function that // we call. See the example below. //example C code switch (message){ case HM_INITIATE_CONVERSATION: //(same as WM_DDE_INITIATE) return ProcessInitiate(hwnd, message, wParam, lParam); case HM_TERMINATE_CONVERSATION: //(same as WM_DDE_TERMINATE) return ProcessTermination(hwnd, message, wParam, lParam); case HM_DLR_PROCESS_THIS_MESSAGE: //(same as WM_DDE_DATA) return ProcessDealerMessage(hwnd, message, wParam, lParam); default: return DefWindowProc(hwnd, message, wParam, lParam); } } short ProcessInitiate(HWND hwnd, unsigned message, WORD wParam, LONG lParam){ //This is called from the DDE window procedure. There are two types of // initiate messages, one for the NEW_GAME, and one for a NEW_CONTACT. See // the documentation for the description of the initiation procedure. //Essentially, we must see if it is a new game initiation. If so then // ignore it if we are already playing. Also, ignore the message if it is // coming from ourselves. The HWND sending is in the wParam of the message. //If it is a NEW_CONTACT message, then if we are not already playing with // another dealer, set the contact HWND (in wParam) to be our contact. //Send acknowledgment messages back if OK in both cases. //example C code char szAtomText[256]; WORD wApplication=LOWORD(lParam); WORD wTopic =HIWORD(lParam); if(wParam == hwndDDE) //Return if the sender is myself. return NOT_OK; GlobalGetAtomName(wTopic, szAtomText, 255); if(!lstrcmp(szAtomText, ATOM_TOPIC_NEW_HEARTS_GAME)){ if(!bAlreadyConversing){ //Send message back to the sender that we can converse with him. SendMessage((HWND)wParam, HM_ACKNOWLEDGEMENT, hwndDDE, MAKELONG(atomMyName, wTopic)); return OK; } //otherwise ignore the initiation, we are busy already with someone else } else if(!lstrcmp(szAtomText,ATOM_TOPIC_YOUR_CONTACT)){ //This means that we are being called into the conversation for good. if(!bAlreadyConversing){ bAlreadyConversing =TRUE; hwndMyContact=(HWND)wParam; SendMessage((HWND)wParam, HM_ACKNOWLEDGEMENT, hwndDDE, MAKELONG(atomMyName, atomPlayerReady)); return OK; } else{ return NOT_OK; } } //Otherwise we cannot be a client for the message sender. return NOT_OK; } short ProcessTermination(HWND hwnd, unsigned message, WORD wParam, LONG lParam){ //We are being told to terminate. //This means our conversation is over and we can prepare to make a conversation // with someone else if they ask. //All we have to do is set everything to zero. Since the Contact told us to close, we // don't have to send it anything back. //example C code hwndMyContact = hDDEInformation = bTerminating = bAlreadyConversing =0; return OK; } void CauseConversationTermination(short bMakeSelfAvailableAgain){ //Tell the contact that we are OUTTA HERE. //Don't tell anyone unless we are actually in a conversation with them. //example C code if(bAlreadyConversing){ PostMessage(hwndMyContact, HM_TERMINATE_CONVERSATION, hwndDDE, 0L); } hwndMyContact =0; //Not necessary if we are closing our app, of course. bTerminating =0; bAlreadyConversing =0; hDDEInformation =0; if(bMakeSelfAvailableAgain) SendMessage(SEND_TO_ALL_WINDOWS, HM_INITIATE_CONVERSATION, hwndDDE, MAKELONG(atomMyName,atomNewHeartsPlayer)); } short ProcessDealerMessage(HWND hwnd, unsigned message, WORD wParam, LONG lParam){ //loword is hData, hiword is atomItem. Same as WM_DDE_DATA. //This function is the main card playing function of importance. //There should be a function (or equivalent) for each message from the dealer // that you want to respond to. You MUST respond to the 5 required messages. // You don't have to respond to the other 'notify' messages. //You want to first make sure the message sender is your contact. //Then lock the global memory share so you can read the message. //Then test if it is a notify message. If so then do what you want with it. //If instead it is a 'your turn' message, then you must call functions to // come up with your answer and put your answer into the appropriate place // in the global share and then return to this function and post your // answer message to the dealer. //Below the example code shows everything except the filling in of the // global share with the answer. //example C code. MessageStruct far* messageStructFromDealer; short i; char szAtomText[256]; WORD hData =LOWORD(lParam); WORD wTopic=HIWORD(lParam); if(wParam != hwndMyContact) return NOT_OK; if(!hDDEInformation) hDDEInformation = LOWORD(lParam); if(!hDDEInformation) return NOT_OK; messageStructFromDealer = (MessageStruct far*) GlobalLock(hDDEInformation); GlobalGetAtomName(wTopic, szAtomText, 255); if(!lstrcmp(szAtomText, ATOM_TOPIC_NOTIFY)){ //Call a function(s) here to process the notification. } else if(!lstrcmp(szAtomText, ATOM_TOPIC_YOUR_TURN)){ //Call a function here depending on exactly which message was sent. //The global storage should be filled in with the answer. //Call the all important functions here. if(nearMessageStruct.nQueryMessage == P_DO_PASS || nearMessageStruct.nQueryMessage == P_LEAD_CARD || nearMessageStruct.nQueryMessage == P_PLAY_CARD || nearMessageStruct.nQueryMessage == P_REGISTER_PLAYER) { messageStructFromDealer->nReturnMessage = messageStructFromDealer->nQueryMessage; //In this case we must send our answer back to the dealer. //For this PostMessage, loword is hData and hiword is atomItem. Same as sending WM_DDE_POKE. //Normally, in DDE, we put the anwser (hData) in LOWORD(lParam) and topic in HIWORD(lParam) // Since these values are already known by both parties, this is not necessary. PostMessage(hwndMyContact, HM_PLR_HERE_IS_MY_ANSWER, hwndDDE, 0L); } //same as WM_DDE_POKE } GlobalUnlock(hDDEInformation); return OK; } char* ConvertCardToText(cardtype* cardToConvert, char* szResultText){ //This function is not necessary but may be useful for debugging. It // converts a 'cardtype' (see external.txt/doc) to a text string for // display (possibly in the main window) to show at run-time. Since // DDE is a real-time system, you cannot sit in a debugger while the // messages are trying to be passed around and time-outs are happening. //example C code //The return is a pointer to the 'szResultText' //The result text is an array of characters (min of 4) to be filled in with the text. //The card text is in the format of: 10H, JD, QS, 4C, 9H, AD, etc. char szSuitChars[4][2] = {"S","D","C","H"}; char szNumberChars[13][3] = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"}; lstrcpy(szResultText,szNumberChars[cardToConvert->kind.number-2]); lstrcat(szResultText,szSuitChars[cardToConvert->kind.suit-1]); return szResultText; }