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;
}