www.pudn.com > tapiwave.zip > TapiWave.c


/*----------------------------------------------------------*\ 
TapiWave 
 
This sample console application is designed to demonstrate 
how to use TAPI to play and record wave files using a voice modem. 
 
\*----------------------------------------------------------*/ 
 
//#define UNICODE 
//#define _UNICODE 
 
#pragma comment(linker, "/subsystem:console") 
#pragma comment(lib, "tapi32") 
 
#include  
#include  
#include  
#include  
 
#ifdef UNICODE 
#define TAPI_CURRENT_VERSION 0x00020000 
#else 
#define TAPI_CURRENT_VERSION 0x00010004 
#endif 
#include  
 
 
TCHAR szAppName[] = TEXT("TapiWave"); 
TCHAR szAppFileName[] = TEXT("TapiWave.exe"); 
 
 
// TAPI constants; command line settable 
DWORD dwDeviceID = 0; 
DWORD dwAddressID = 0; 
 
// TAPI global variables. 
HINSTANCE hInstance; 
HLINEAPP hLineApp = 0; 
DWORD dwNumDevs; 
DWORD dwAPIVersion; 
HLINE hLine; 
HCALL hCall = 0; 
LINEEXTENSIONID LineExtensionID; 
 
 
#if TAPI_CURRENT_VERSION >= 0x00020000 
LINEINITIALIZEEXPARAMS lineInitializeExParams = { 
   sizeof(lineInitializeExParams), 0, 0, 
   LINEINITIALIZEEXOPTION_USEHIDDENWINDOW, 
   NULL, 0 
   }; 
#endif 
 
 
TCHAR szPhoneNumber[256] = TEXT("45776"); 
 
#define BIGBUFF 8096 
 
LPVARSTRING             pVarString        = NULL; 
LPLINEDEVSTATUS         pLineDevStatus    = NULL; 
LPLINECALLINFO          pLineCallInfo     = NULL; 
LPLINECALLPARAMS        pLineCallParams   = NULL; 
LPLINETRANSLATEOUTPUT   pTranslateOutput  = NULL; 
 
// State machine information. 
DWORD dwMakeCallAsyncID=0; 
DWORD dwLineDropAsyncID=0; 
BOOL bDropped = FALSE; 
BOOL bConnected = FALSE; 
BOOL bAnswered  = FALSE; 
BOOL bReadyToEnd = FALSE; 
BOOL bPrintedEnd = FALSE; 
 
// Constants in how the state machine behaves. 
BOOL bCommandLineError = FALSE; 
BOOL bLogExtraInfo = FALSE; 
BOOL bAnswer = TRUE; 
 
// Variables so we can ^C to shutdown and clean up properly. 
DWORD dwThreadID; 
DWORD dwWaveThreadID; 
DWORD dwTimeToWaitBeforePlaying = 5000; 
 
// /// Waveaudio content /// 
 
HANDLE hWaveThread = NULL; 
UINT WaveInID = 0; 
UINT WaveOutID = 0; 
BOOL bWaveLocal = FALSE; 
TCHAR szFileName[1024] = TEXT("greeting.wav"); 
DWORD dwWaveMapped = WAVE_MAPPED; 
 
 
// Prototypes 
void CALLBACK lineCallbackFunc( 
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,  
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3); 
BOOL SetupEnvironment (int argc, LPTSTR argv[]); 
void PrintHelp(); 
BOOL BreakHandlerRoutine(DWORD dwCtrlType); 
BOOL GetCallInfo(); 
void StopEverything(); 
BOOL PumpMessages(BOOL bWaitForMessage); 
DWORD WINAPI WaveThread(LPVOID pVoid); 
 
 
 
void PlayFromMemory (LPSTR szWavData); 
DWORD WINAPI ThreadRecorded (LPVOID pvThreadParam); 
void RecordToMemory (LPSTR szWavData); 
void BuildWavData (LPSTR szFilename, LPSTR szWavData, DWORD dwMaxBufferSize); 
 
void __cdecl MyPrintf(LPCTSTR pszFormat, ...); 
LPCTSTR FormatError(DWORD dwError); 
LPCTSTR FormatErrorBuffer(DWORD dwError, LPTSTR pszBuff, DWORD dwNumChars); 
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType); 
LPTSTR FormatTapiError (long lError); 
 
 
// Console app starting place. 
int __cdecl _tmain(int argc, _TCHAR *argv[], _TCHAR *envp[]) 
{ 
   LONG lRet; 
   MSG msg; 
 
   // Setup basic stuff. 
   hInstance = GetModuleHandle(NULL); 
   dwThreadID = GetCurrentThreadId(); 
   SetConsoleCtrlHandler((PHANDLER_ROUTINE) BreakHandlerRoutine, TRUE);	 
 
   if (!SetupEnvironment(argc, argv)) 
      goto end; 
 
   if (bWaveLocal) 
   { 
      hWaveThread = CreateThread(NULL, 0, WaveThread, NULL, 0, &dwWaveThreadID); 
      goto end; 
   } 
 
   // Prime the message queue.  TAPI callback is called as a result 
   // of messages being dispatched.  By default, console apps don't have  
   // a message queue to hold these messages.  PeekMessage will create it. 
   PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); 
 
   pVarString       = LocalAlloc(LPTR, BIGBUFF); 
   pLineDevStatus   = LocalAlloc(LPTR, BIGBUFF); 
   pLineCallInfo    = LocalAlloc(LPTR, BIGBUFF); 
   pLineCallParams  = LocalAlloc(LPTR, BIGBUFF); 
   pTranslateOutput = LocalAlloc(LPTR, BIGBUFF); 
 
   pVarString       -> dwTotalSize = BIGBUFF; 
   pLineDevStatus   -> dwTotalSize = BIGBUFF; 
   pLineCallInfo    -> dwTotalSize = BIGBUFF; 
   pTranslateOutput -> dwTotalSize = BIGBUFF; 
 
   pLineCallParams -> dwTotalSize = BIGBUFF; 
   pLineCallParams -> dwBearerMode       = LINEBEARERMODE_VOICE; 
   pLineCallParams -> dwMediaMode        = LINEMEDIAMODE_AUTOMATEDVOICE, 
   pLineCallParams -> dwCallParamFlags   = 0; 
   pLineCallParams -> dwAddressMode      = LINEADDRESSMODE_ADDRESSID; 
   pLineCallParams -> dwAddressID        = dwAddressID; 
 
#if TAPI_CURRENT_VERSION >= 0x00020000 
   dwAPIVersion = TAPI_CURRENT_VERSION; 
   lRet = lineInitializeEx(&hLineApp, hInstance, lineCallbackFunc,  
                         szAppName, &dwNumDevs,  
                         &dwAPIVersion, &lineInitializeExParams); 
#else 
   // Note that you can't use this function and be UNICODE 
   lRet = lineInitialize(&hLineApp, hInstance, lineCallbackFunc,  
                         szAppName, &dwNumDevs); 
#endif 
   if (lRet) 
   { 
      MyPrintf(TEXT("lineInitialize failed: %s.\r\n"), FormatTapiError(lRet)); 
      goto end; 
   } 
 
   if (lRet = lineNegotiateAPIVersion(hLineApp, dwDeviceID,  
      0x00010004, 0x00010004, &dwAPIVersion, &LineExtensionID)) 
   { 
      MyPrintf(TEXT("lineNegotiateAPIVersion failed: %s.\r\n"), FormatTapiError(lRet)); 
      goto end; 
   } 
 
   if (lRet = lineOpen(hLineApp, dwDeviceID, &hLine, dwAPIVersion, 0, 0, 
            LINECALLPRIVILEGE_NONE, 0, NULL)) 
   { 
      MyPrintf(TEXT("lineOpen failed: %s.\r\n"), FormatTapiError(lRet)); 
      goto end; 
   } 
    
   while (TRUE) 
   { 
      if (lRet = lineGetLineDevStatus(hLine, pLineDevStatus)) 
      { 
         MyPrintf(TEXT("lineGetLineDevStatus failed: %s.\r\n"), FormatTapiError(lRet)); 
         goto end; 
      } 
 
      if (pLineDevStatus->dwNeededSize > pLineDevStatus->dwTotalSize) 
      { 
         LocalReAlloc(pLineDevStatus, pLineDevStatus->dwNeededSize, LMEM_MOVEABLE); 
         pLineDevStatus->dwTotalSize = pLineDevStatus->dwNeededSize; 
         continue; 
      } 
      break; 
   } 
 
   if (pLineDevStatus -> dwOpenMediaModes) 
      MyPrintf(TEXT("!!!WARNING!!!  Another application is already waiting for calls.\r\n\r\n")); 
 
   if (!((pLineDevStatus -> dwLineFeatures) & LINEFEATURE_MAKECALL)) 
      MyPrintf(TEXT("!!!WARNING!!!  No call appearances available at this time.\r\n\r\n")); 
 
   if (bAnswer) 
   { 
      lineClose(hLine); 
 
      if (lRet = lineOpen(hLineApp, dwDeviceID, &hLine, dwAPIVersion, 0, 0, 
            LINECALLPRIVILEGE_OWNER, LINEMEDIAMODE_AUTOMATEDVOICE, NULL)) 
      { 
         MyPrintf(TEXT("lineOpen failed: %s.\r\n"), FormatTapiError(lRet)); 
         goto end; 
      } 
 
      MyPrintf(TEXT("Waiting for a call on TAPI Line Device %lu\r\n"), dwDeviceID); 
   } 
   else 
   { 
 
      if (lRet = lineTranslateAddress(hLineApp, dwDeviceID, dwAPIVersion, szPhoneNumber, 
                     0, 0, pTranslateOutput)) 
      { 
         MyPrintf(TEXT("lineTranslateAddress failed: %s.\r\n"), FormatTapiError(lRet)); 
         goto end; 
      } 
 
      lRet = lineMakeCall(hLine, &hCall, szPhoneNumber, 0, pLineCallParams); 
      if (lRet < 0) 
      { 
         MyPrintf(TEXT("lineMakeCall failed: %s.\r\n"), FormatTapiError(lRet)); 
         goto end; 
      } 
      else 
      { 
         dwMakeCallAsyncID = lRet; 
      } 
   } 
 
   // TAPI callback is called only when messages are dispatched! 
   while (PumpMessages(TRUE)); 
 
  end: 
 
   StopEverything(); 
 
   if (hLineApp) 
      lineShutdown(hLineApp); 
   hLineApp = 0; 
 
   if (pLineDevStatus) 
      LocalFree(pLineDevStatus); 
   if (pLineCallInfo) 
      LocalFree(pLineCallInfo); 
   if (pLineCallParams) 
      LocalFree(pLineCallParams); 
   if (pVarString) 
      LocalFree(pVarString); 
   if (pTranslateOutput) 
      LocalFree(pTranslateOutput); 
 
   return 1; 
} 
 
 
 
// Here's the TAPI callback.  Mondo switch statement! 
void CALLBACK lineCallbackFunc( 
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,  
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3) 
{ 
   LONG lRet; 
 
   /* 
   if (bLogExtraInfo) 
      MyPrintf(TEXT("LCBF: %s\r\n"),    // LCBF stands for lineCallBackFunc 
         FormatLineCallback( 
               dwDevice, dwMsg, dwCallbackInstance,  
               dwParam1, dwParam2, dwParam3, 
               szBuff)); 
               */ 
 
   switch(dwMsg) 
   { 
      case LINE_LINEDEVSTATE: 
         if (dwParam1 == LINEDEVSTATE_REINIT) 
         { 
            MyPrintf(TEXT("LINEDEVSTATE_REINIT\r\n")); 
            StopEverything(); 
         } 
         break; 
 
      case LINE_REPLY: 
         if (dwParam2 == dwLineDropAsyncID) 
         { 
            if (dwParam2 != 0) 
            { 
               MyPrintf(TEXT("lineDrop LINE_REPLY with failure: %s.  Stopping.\r\r\n"),  
                  FormatTapiError((long) dwParam2)); 
               StopEverything(); 
            } 
         } 
         else if (dwParam2 == dwMakeCallAsyncID) 
         { 
            if (dwParam2 != 0) 
            { 
               MyPrintf(TEXT("lineMakeCall LINE_REPLY with failure: %s.  Stopping.\r\r\n"),  
                  FormatTapiError((long) dwParam2)); 
               StopEverything(); 
            } 
         } 
 
 
         // else ignore it. 
         break; 
 
      case LINE_CALLSTATE: 
      { 
         // Is this a new call? 
         if (dwParam3 == LINECALLPRIVILEGE_OWNER) 
         { 
            // Do we already have a call? 
            if (hCall && (hCall != (HCALL) dwDevice)) 
            { 
               if (dwMsg == LINECALLSTATE_IDLE) 
                  lineDeallocateCall((HCALL) dwDevice); 
               else 
               { 
                  MyPrintf(TEXT("New call; %lu, but already managing one.  Dropping it.\r\r\n"), 
                        dwDevice); 
                  lineDrop((HCALL) dwDevice, NULL, 0); 
               } 
               break; 
            } 
 
            if (hCall) 
               MyPrintf( 
                  TEXT("Given OWNER privs to a call already owned?\r\n") 
                  TEXT(" - Should only happen if handed a call already owned.\r\n")); 
            else 
            { 
               hCall = (HCALL) dwDevice; 
               MyPrintf(TEXT("New incoming hCall 0x%lx on TAPI Line Device.\r\r\n"), 
                  hCall, dwDeviceID); 
            } 
         } 
 
         if (hCall != (HCALL) dwDevice) 
         { 
            if (dwMsg == LINECALLSTATE_IDLE) 
               lineDeallocateCall((HCALL) dwDevice); 
            else 
            { 
               lineDrop((HCALL) dwDevice, NULL, 0); 
               MyPrintf(TEXT("LINE_CALLSTATE 0x%lx for non-main hCall: 0x%lx.  Dropping.\r\n"),  
                  dwParam1, dwDevice); 
            } 
            break; 
         } 
 
         switch (dwParam1) 
         { 
            case LINECALLSTATE_IDLE: 
            { 
               MyPrintf(TEXT("IDLE.\n\r\n")); 
               lRet = lineDeallocateCall(hCall); 
 
               // Should make sure lineDeallocateCall succeeded 
 
               hCall = 0; 
               StopEverything(); 
               break; 
            } 
 
            case LINECALLSTATE_BUSY: 
            case LINECALLSTATE_DISCONNECTED: 
               if (dwParam1 == LINECALLSTATE_BUSY) 
                  MyPrintf(TEXT("BUSY.  Hanging up.\r\n")); 
               else 
                  MyPrintf(TEXT("DISCONNECTED.  Hanging up.\r\n")); 
 
               if (!bDropped) 
                  dwLineDropAsyncID = lineDrop(hCall, NULL, 0); 
               if (dwLineDropAsyncID < 0) 
               { 
                  MyPrintf(TEXT("lineDrop failed.  Terminating\r\n")); 
                  hCall = 0; 
                  StopEverything(); 
               } 
 
               bDropped = TRUE; 
 
               break; 
 
            case LINECALLSTATE_OFFERING: 
            case LINECALLSTATE_ACCEPTED:  // Could be handed off an accepted call 
               if (bAnswered) 
                  break; 
 
               if (dwParam1 == LINECALLSTATE_OFFERING) 
                  MyPrintf(TEXT("Answering an OFFERING call.\r\n\r\n")); 
               else 
                  MyPrintf(TEXT("Answering an ACCEPTED call.\r\n\r\n")); 
 
               lRet = lineAnswer(hCall, NULL, 0); 
 
               // Should check to make sure lineAnswer succeeded 
 
               bAnswered = TRUE; 
 
               break; 
 
            case LINECALLSTATE_CONNECTED: 
               if (!bConnected) 
               { 
                  bConnected = TRUE; 
                  MyPrintf(TEXT("CONNECTED.\r\n")); 
 
                  hWaveThread = CreateThread(NULL, 0, WaveThread, NULL, 0, &dwWaveThreadID); 
               } 
               break; 
         } 
 
         break; 
      } 
   } 
} 
 
 
BOOL GetCallInfo() 
{ 
   LONG lRet; 
   while (TRUE) 
   { 
      if (lRet = lineGetCallInfo(hCall, pLineCallInfo)) 
      { 
         MyPrintf(TEXT("lineGetCallInfo failed: %s.\r\n"), FormatTapiError(lRet)); 
         return FALSE; 
      } 
 
      if (pLineCallInfo->dwNeededSize > pLineCallInfo->dwTotalSize) 
      { 
         LocalReAlloc(pLineCallInfo, pLineCallInfo->dwNeededSize, LMEM_MOVEABLE); 
         pLineCallInfo->dwTotalSize = pLineCallInfo->dwNeededSize; 
         continue; 
      } 
 
      return TRUE; 
   } 
} 
 
 
// Parse the command line and change the default settings. 
BOOL SetupEnvironment (int argc, LPTSTR argv[]) 
{ 
   int i = 0, j; 
   TCHAR chFlag; 
   BOOL bMaxCallsSet = FALSE; 
 
   if (argc == 1) 
   { 
      PrintHelp(); 
      return FALSE; 
   } 
 
   while (++i < argc) 
   { 
      j = 0; 
      if ((argv[i][j] == TEXT('/')) || (argv[i][j] == TEXT('-')) || (argv[i][j] == TEXT('+'))) 
         j = 1; 
 
      chFlag = argv[i][j++]; 
 
      if (argv[i][j] == TEXT(':')) 
         j++; 
      if (argv[i][j] == TEXT('\"')) 
         j++; 
 
      switch(tolower(chFlag)) 
      { 
         case TEXT('?'): 
            MyPrintf(TEXT("Runs a test of the TAPI and WAVE system.\r\n")); 
            PrintHelp(); 
            return FALSE; 
 
         case TEXT('l'): 
            dwDeviceID = _ttoi(&argv[i][j]); 
            if (!dwDeviceID) 
            { 
               MyPrintf(TEXT("Invalid [L]ine selection.\r\n")); 
               bCommandLineError = TRUE; 
               PrintHelp(); 
               return FALSE; 
            } 
            break; 
 
         case TEXT('i'): 
            dwAddressID = _ttoi(&argv[i][j]); 
            if (!dwAddressID) 
            { 
               MyPrintf(TEXT("Invalid Address [I]D selection.\r\n")); 
               bCommandLineError = TRUE; 
               PrintHelp(); 
               return FALSE; 
            } 
            break; 
 
         case TEXT('x'): 
            bLogExtraInfo = TRUE; 
            MyPrintf(TEXT("Extra call and data flow information will be displayed.\r\n")); 
            break; 
 
         case TEXT('a'): 
            bAnswer = TRUE; 
            break; 
 
         case TEXT('d'): 
            lstrcpy(szPhoneNumber, &argv[i][j]); 
            bAnswer = FALSE; 
            break; 
 
         case TEXT('f'): 
            lstrcpy(szFileName, &argv[i][j]); 
            break; 
 
         case TEXT('w'): 
            WaveInID = _ttoi(&argv[i][j]); 
            if (!WaveInID && (argv[i][j] != TEXT('0'))) 
            { 
               MyPrintf(TEXT("Invalid [W]ave ID selection.\r\n")); 
               bCommandLineError = TRUE; 
               PrintHelp(); 
               return FALSE; 
            } 
            if (WaveInID == 99) 
            { 
               MyPrintf(TEXT("Using WAVE_MAPPER.\r\n")); 
               WaveOutID = WaveInID = WAVE_MAPPER; 
               dwWaveMapped = 0; 
            } 
            else 
               WaveOutID = WaveInID; 
 
            bWaveLocal = TRUE; 
            break; 
 
         case TEXT('z'): 
            dwWaveMapped = 0; 
            break; 
 
         case TEXT('p'): 
            dwTimeToWaitBeforePlaying = _ttoi(&argv[i][j]); 
            break; 
      } 
   } 
 
   return TRUE; 
} 
 
 
// Print the help screen. 
void PrintHelp() 
{ 
   MyPrintf( 
TEXT("\r\n") 
TEXT("%s [options]\r\n") 
TEXT("\r\n") 
TEXT("[Wave File]  File to record to or play back.\r\n") 
TEXT("\r\n") 
TEXT("Options:\r\n") 
TEXT("/L:#        Line Device ID (dwDeviceID) to use.\r\n") 
//TEXT("/I:#        Line Address ID to use.  Ignored when AutoAnswer is set.\r\n") 
TEXT("/X          Display extra call and data flow information.\r\n") 
TEXT("/F:file     Use file as the wave file\r\n") 
TEXT("/D:[number] Dial number\r\n") 
TEXT("/A          Answer a call\r\n") 
TEXT("\r\n") 
//TEXT("/W:#        Specify a wave ID to use for both IN and OUT.\r\n") 
//TEXT("            If this flag is used, all TAPI is bypassed.\r\n") 
//TEXT("            A wave ID of 99 means to use WAVE_MAPPER.\r\n") 
TEXT("/Z          Don't use WAVE_MAPPED\r\n") 
TEXT("/P:#        Pause # milliseconds after dialing, before playing.\r\n") 
TEXT("\r\n") 
TEXT("defaults: /L:%lu /I:%lu\n /F:%s /A\r\n"), 
TEXT("\r\n"), 
   szAppName, dwDeviceID, dwAddressID, szFileName); 
} 
 
 
 
 
// Break Hander: Try and terminate gracefully. 
// Note that ^Break can call lineShutdown *DURING* a lineMakeCall.  Very 
// unpredictable results; TAPI is not re-entrant and is *usually* protected by 
// the Win16 Mutex.  However, a Break Handler is a special case. 
BOOL BreakHandlerRoutine(DWORD dwCtrlType) 
{ 
   // ^BREAK will always break.   
   // Use with care; it can mess up TAPI if in the middle of a TAPI API. 
   if (dwCtrlType == CTRL_BREAK_EVENT) 
   { 
      MyPrintf(TEXT("\r\n^[Break] terminated!  TAPI may not be left in a usable state.\r\n\r\n")); 
 
      lineShutdown(hLineApp); 
      LocalFree(pLineDevStatus); 
      LocalFree(pLineCallInfo); 
      LocalFree(pLineCallParams); 
      ExitProcess(1); 
   } 
   else 
   { 
      if (hCall) 
         MyPrintf(TEXT("\r\n^C Dropping call in progress.\r\n\r\n")); 
      else  
         MyPrintf(TEXT("\r\n^C stopped.\r\n\r\n")); 
 
      StopEverything(); 
   } 
   return TRUE; 
} 
 
BOOL PumpMessages(BOOL bWaitForMessage) 
{ 
	static MSG msg; 
 
   if (bReadyToEnd) 
	   return FALSE; 
 
   if (bWaitForMessage) 
   { 
      if (!GetMessage(&msg, NULL, 0, 0)) 
         return FALSE; 
   } 
   else 
      if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
         return TRUE; 
      else 
         if (msg.message == WM_QUIT) 
            return FALSE; 
 
   TranslateMessage(&msg); 
   DispatchMessage(&msg); 
 
   return TRUE; 
} 
 
 
void StopEverything() 
{ 
   bReadyToEnd = TRUE; 
 
   WaitForSingleObject(hWaveThread, INFINITE); 
 
   if (hCall && !bDropped) 
   { 
      dwLineDropAsyncID = lineDrop(hCall, NULL, 0); 
      if (dwLineDropAsyncID < 0) 
      { 
         MyPrintf(TEXT("lineDrop failed.  Terminating\r\n")); 
         hCall = 0; 
      } 
      bDropped = TRUE; 
   } 
 
   // lets prime the pump with a message. 
   PostThreadMessage(dwThreadID, WM_USER, 0, 0); 
} 
 
 
// --------------- 
 
typedef struct MYWAVEDATA_tag 
{ 
   struct MYWAVEDATA_tag * pNext; 
   WAVEHDR wavehdr; 
   BYTE data[1]; 
} MYWAVEDATA, *PMYWAVEDATA; 
 
PrintWaveInfo(WaveInID, WaveOutID); 
PMYWAVEDATA LoadWaveInfo(WAVEFORMATEX* pFormat); 
LPTSTR FormatWaveOutError(MMRESULT mmResult); 
LPTSTR FormatWaveInError(MMRESULT mmResult); 
 
 
// CONNECTED!  Now do data steam testing.  Return FALSE to stop TAPI. 
DWORD WINAPI WaveThread(LPVOID pVoid) 
{ 
   DWORD i; 
   HWAVEIN     hWaveIn = NULL; 
   HWAVEOUT    hWaveOut = NULL; 
   MMRESULT mmResult; 
   PMYWAVEDATA pWaveData, pWaveCurr; 
   BYTE WaveFormatBytes[4096]; 
   PWAVEFORMATEX  pFormat = (PWAVEFORMATEX) WaveFormatBytes; 
 
   // Get the wave devices to use 
   if (!bWaveLocal) 
   { 
      LONG lRet; 
 
      if (lRet = lineGetID(0, 0, hCall, LINECALLSELECT_CALL, pVarString, TEXT("wave/in"))) 
      { 
         MyPrintf(TEXT("lineGetID failed %s\r\n"), FormatTapiError(lRet)); 
         return FALSE; 
      } 
 
      WaveInID = *(UINT *)((LPBYTE)pVarString + pVarString->dwStringOffset); 
 
      if (lRet = lineGetID(0, 0, hCall, LINECALLSELECT_CALL, pVarString, TEXT("wave/out"))) 
      { 
         MyPrintf(TEXT("lineGetID failed %s\r\n"), FormatTapiError(lRet)); 
         return FALSE; 
      } 
 
      WaveOutID = *(UINT *)((LPBYTE)pVarString + pVarString->dwStringOffset); 
   } 
 
   MyPrintf(TEXT("Using WaveInID %lu and WaveOutID %lu\r\n"), 
          (DWORD) WaveInID, (DWORD) WaveOutID); 
 
   PrintWaveInfo(WaveInID, WaveOutID); 
 
 
   // Load the wave data from file.  Yes, this assumes we are playing. 
   pFormat->cbSize = sizeof(WaveFormatBytes); 
   pWaveData = LoadWaveInfo(pFormat); 
   if (pWaveData == NULL) 
      return 0; 
 
 
   // Open a waveform output device. 
   mmResult = waveOutOpen(&hWaveOut, WaveOutID, pFormat, 0 , 0L, dwWaveMapped); 
   if (mmResult) 
   { 
      MyPrintf(TEXT("waveOutOpen returned %s\r\n"), FormatWaveOutError(mmResult)); 
      goto end; 
   } 
 
 
   // First, sleep 5 seconds to give the other end time to answer. 
   // This is an important step as modems have no idea when the other end actually answered. 
   // A sleep isn't the best way to wait, but it works for now. 
   MyPrintf(TEXT("Waiting %lu milliseconds for other end to answer because modems don't know when a VOICE call has answered\r\n"),  
      dwTimeToWaitBeforePlaying); 
   for(i = dwTimeToWaitBeforePlaying; i; i-=100) 
   { 
      if (!(i%1000)) 
         MyPrintf(TEXT("%lu\r\n"), i/1000); 
      if (bReadyToEnd) 
         break; 
      Sleep(100); 
   } 
 
   // Now start playing, looping through the wave file repeatedly. 
 
   pWaveCurr = pWaveData; 
    
   while(!bReadyToEnd) 
   { 
      // If a buffer is queued, wait till its done. 
      if (pWaveCurr->wavehdr.dwFlags & WHDR_INQUEUE) 
      { 
         if (bLogExtraInfo) 
            MyPrintf(TEXT("Waiting for wave buffer to finish rendering\r\n")); 
         Sleep(250); 
         continue; 
      } 
 
      // If we've used it, we need to unprepare the header 
      if (pWaveCurr->wavehdr.dwFlags & WHDR_DONE ) 
      { 
         if(mmResult = waveOutUnprepareHeader(hWaveOut, &pWaveCurr->wavehdr, sizeof(WAVEHDR))) 
         { 
            MyPrintf(TEXT("waveOutUnprepareHeader returned %s\r\n"), FormatWaveOutError(mmResult)); 
            break; 
         } 
      } 
 
      pWaveCurr->wavehdr.dwFlags = 0; 
 
      // prepare each WAVEHDR 
      if(mmResult = waveOutPrepareHeader(hWaveOut, &pWaveCurr->wavehdr, sizeof(WAVEHDR))) 
      { 
         MyPrintf(TEXT("waveOutPrepareHeader returned %s\r\n"), FormatWaveOutError(mmResult)); 
         break; 
      } 
 
      // Send to the output device. 
      if (mmResult = waveOutWrite(hWaveOut, &pWaveCurr->wavehdr, sizeof(WAVEHDR))) 
      { 
         MyPrintf(TEXT("waveOutWrite returned %s\r\n"), FormatWaveOutError(mmResult)); 
         break; 
      } 
 
      if (!(pWaveCurr->wavehdr.dwFlags & WHDR_INQUEUE)) 
      { 
         MyPrintf(TEXT("waveOutWrite did not succesfully queue.")); 
         break; 
      } 
 
      if (bLogExtraInfo) 
         MyPrintf(TEXT("Wave buffer successfully queued.\r\n")); 
 
      pWaveCurr = pWaveCurr->pNext; 
      if (!pWaveCurr) 
         pWaveCurr = pWaveData; 
   } 
 
  end: 
 
   if (hWaveOut) 
   { 
      if (mmResult = waveOutReset(hWaveOut)) 
         MyPrintf(TEXT("waveOutReset returned %s\r\n"), FormatWaveOutError(mmResult)); 
 
      Sleep(500);  // Give it 1/2 sec to actually reset. 
    
      if(mmResult = waveOutClose(hWaveOut)) 
         MyPrintf(TEXT("waveOutClose returned %s\r\n"), FormatWaveOutError(mmResult)); 
 
      Sleep(500); // Give it 1/2 sec to clean up. 
      hWaveOut = 0; 
   } 
 
   while(pWaveData) 
   { 
      pWaveCurr = pWaveData; 
      pWaveData = pWaveCurr->pNext; 
 
      LocalFree(pWaveCurr); 
   } 
 
   return FALSE; 
} 
 
 
 
PrintWaveInfo(WaveInID, WaveOutID) 
{ 
   WAVEINCAPS in; 
   WAVEOUTCAPS out; 
 
   if (MMSYSERR_NOERROR == waveInGetDevCaps(WaveInID, &in, sizeof(in))) 
   { 
      MyPrintf( 
         TEXT("WaveInDevCaps:\r\n") 
         TEXT("  wMid: 0x%04X\r\n") 
         TEXT("  wPid: 0x%04X\r\n") 
         TEXT("  vDriverVersion: 0x%04X\r\n") 
         TEXT("  pszPname: %s\r\n") 
         TEXT("  dwFormats: 0x%08X\r\n") 
         TEXT("  dwChannels: 0x%04X\r\n") 
         , 
         in.wMid, in.wPid, in.vDriverVersion, in.szPname, in.dwFormats, in.wChannels); 
   } 
   else 
      MyPrintf(TEXT("waveInGetDevCaps failed.\r\n")); 
 
   if (MMSYSERR_NOERROR == waveOutGetDevCaps(WaveOutID, &out, sizeof(out))) 
   { 
      MyPrintf( 
         TEXT("WaveOutDevCaps:\r\n") 
         TEXT("  wMid: 0x%04X\r\n") 
         TEXT("  wPid: 0x%04X\r\n") 
         TEXT("  vDriverVersion: 0x%04X\r\n") 
         TEXT("  pszPname: %s\r\n") 
         TEXT("  dwFormats: 0x%08X\r\n") 
         TEXT("  dwChannels: 0x%04X\r\n") 
         , 
         out.wMid, out.wPid, out.vDriverVersion, out.szPname, out.dwFormats, out.wChannels); 
   } 
   else 
      MyPrintf(TEXT("waveInGetDevCaps failed.\r\n")); 
 
   // TODO Find out if its capable of simultaneous recording and playing 
} 
 
 
PMYWAVEDATA LoadWaveInfo(WAVEFORMATEX* pFormat) 
{ 
   MMRESULT       mmResult; 
   HMMIO          hmmio = {0}; 
   MMCKINFO       mmckinfoParent; 
   MMCKINFO       mmckinfoSubchunk; 
   DWORD          dwFmtSize; 
   DWORD          dwChunkSize; 
   PMYWAVEDATA    pWaveHead = NULL,  
                  pWaveCurr = NULL, 
                  pWavePrev = NULL; 
   BOOL bLooping = TRUE; 
   DWORD dwBuffers = 0; 
 
   // Open the given file for reading using buffered I/O. 
   if(!(hmmio = mmioOpen(szFileName, NULL, MMIO_READ | MMIO_ALLOCBUF))) 
   { 
      MyPrintf(TEXT("mmioOpen failed to open file.\r\n")); 
      return NULL; 
   } 
 
   // Locate a 'RIFF' chunk with a 'WAVE' form type to make sure it's a WAVE file. 
   mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E'); 
   if (mmResult = mmioDescend(hmmio, &mmckinfoParent, NULL, MMIO_FINDRIFF)) 
   { 
      MyPrintf(TEXT("mmioDescend RIFF WAVE returned %s\r\n"), FormatWaveOutError(mmResult)); 
      goto end; 
   } 
 
   //  Now, find the format chunk (form type 'fmt '). It should be 
   //  a subchunk of the 'RIFF' parent chunk. 
   mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' '); 
   if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, 
      MMIO_FINDCHUNK)) 
   { 
      MyPrintf(TEXT("Wave file corrupt.\r\n")); 
      goto end; 
   } 
 
   // Get the size of the format chunk, allocate and lock memory for it. 
   dwFmtSize = mmckinfoSubchunk.cksize; 
   if (pFormat->cbSize < dwFmtSize) 
   { 
      MyPrintf(TEXT("Format chunk not big enough.\r\n")); 
      goto end; 
   } 
 
   // Read the format chunk. 
   if (mmioRead(hmmio, (HPSTR) pFormat, dwFmtSize) != (LONG) dwFmtSize) 
   { 
      MyPrintf(TEXT("mmioRead: failed to read FMT chunk.\r\n")); 
      goto end; 
   } 
 
   MyPrintf(TEXT("wFormatTag = %lu\r\n"),        (DWORD) pFormat->wFormatTag); 
   MyPrintf(TEXT("nChannels = %lu\r\n"),         (DWORD) pFormat->nChannels ); 
   MyPrintf(TEXT("nSamplesPerSec = %lu\r\n"),    (DWORD) pFormat->nSamplesPerSec); 
   MyPrintf(TEXT("nAvgBytesPerSec = %lu\r\n"),   (DWORD) pFormat->nAvgBytesPerSec); 
   MyPrintf(TEXT("nBlockAlign = %lu\r\n"),       (DWORD) pFormat->nBlockAlign); 
   MyPrintf(TEXT("wBitsPerSample = %lu\r\n"),    (DWORD) pFormat->wBitsPerSample); 
   MyPrintf(TEXT("cbSize = %lu\r\n"),            (DWORD) pFormat->cbSize); 
 
   // Ascend out of the format subchunk. 
   mmioAscend(hmmio, &mmckinfoSubchunk, 0); 
 
   // Find the data subchunk. 
   mmckinfoSubchunk.ckid = mmioFOURCC('d', 'a', 't', 'a'); 
   if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, 
      MMIO_FINDCHUNK)) 
   { 
      MyPrintf(TEXT("mmioDescend: No DATA chunk.\r\n")); 
      goto end; 
   } 
 
   //  Get the size of the data subchunk. 
   if (mmckinfoSubchunk.cksize == 0L) 
   { 
      MyPrintf(TEXT("Data chunk actually has no data.\r\n")); 
      goto end; 
   } 
   MyPrintf(TEXT("Size of data is %lu\r\n"),mmckinfoSubchunk.cksize); 
 
   // Now read the data and allocate MYWAVEDATA buffers 
   dwChunkSize = (pFormat->nAvgBytesPerSec/4); 
   dwChunkSize -= dwChunkSize % pFormat->nBlockAlign; 
   if (dwChunkSize < pFormat->nBlockAlign) 
   { 
      MyPrintf(TEXT("Couldn't calculate a good block size\r\n")); 
      goto end; 
   } 
 
   while(bLooping) 
   { 
      LONG lRead; 
 
      pWaveCurr = (PMYWAVEDATA) LocalAlloc(LPTR, dwChunkSize + sizeof(MYWAVEDATA)); 
      pWaveCurr->wavehdr.lpData = pWaveCurr->data; 
 
      if (pWaveHead == NULL) 
         pWaveHead = pWaveCurr; 
 
      // Read the waveform data subchunk. 
      lRead = mmioRead(hmmio, pWaveCurr->data, dwChunkSize); 
      pWaveCurr->wavehdr.dwBufferLength = lRead; 
      if (lRead == -1) 
      { 
         MyPrintf(TEXT("Error reading from file.\r\n")); 
         pWaveHead = NULL; // Leak leak 
         goto end; 
      } 
 
      if (lRead == 0) 
      { 
         LocalFree(pWaveCurr); 
         break; 
      } 
 
      if ((DWORD)lRead != dwChunkSize) 
      { 
         bLooping = FALSE; 
      } 
 
      if (pWavePrev != NULL) 
         pWavePrev->pNext = pWaveCurr; 
      pWavePrev = pWaveCurr; 
      dwBuffers++; 
   } 
 
   MyPrintf(TEXT("There were %lu buffers read from %s\r\n"), dwBuffers, szFileName); 
 
  end: 
 
   mmioClose(hmmio, 0); 
 
   return pWaveHead; 
} 
 
 
 
  
/*=== Printing routines =============================================================*/ 
 
/* 
  This is easily used to do error messages like this: 
 
  MyPrintf(TEXT("API blah failed with error: %s\r\n"), FormatError(GetLastError())); 
*/ 
 
#define MAX_PRINT_STRING 1024 
 
//#define MSG_BOX_PRINT 
 
#ifdef _DEBUG 
#define MSG_DEBUG_PRINT 
#endif 
 
#define MSG_CONSOLE_PRINT 
 
//#define MSG_FILE_PRINT  
 
#ifdef MSG_FILE_PRINT 
TCHAR szFilePrint[MAX_PATH] = TEXT(".\\out.txt"); 
BOOL bZeroFile = FALSE; 
#endif 
 
LPTSTR FormatWaveError(MMRESULT mmrError, MMRESULT (WINAPI *pfn) (MMRESULT, LPTSTR, UINT), LPTSTR szErr) 
{ 
   _declspec(thread) static TCHAR szOutput[MAX_PRINT_STRING]; 
   MMRESULT mmResult2; 
   mmResult2 = pfn(mmrError, szOutput, MAX_PRINT_STRING); 
   if (mmResult2 != MMSYSERR_NOERROR) 
   { 
      TCHAR szTmp[256]; 
      MMRESULT mmResult3; 
 
      mmResult3 = pfn(mmResult2, szOutput, 256); 
      if (mmResult2 != MMSYSERR_NOERROR) 
         wsprintf(szOutput, TEXT("%s returned an %lu on %lu"), szErr, mmResult2, mmrError); 
      else 
         wsprintf(szOutput, TEXT("%s on error %lu"), szTmp, mmrError); 
   } 
   return szOutput; 
} 
 
LPTSTR FormatWaveOutError(MMRESULT mmrError) 
{ 
   return FormatWaveError(mmrError, waveOutGetErrorText, TEXT("waveOutGetError")); 
} 
 
LPTSTR FormatWaveInError(MMRESULT mmrError) 
{ 
   return FormatWaveError(mmrError, waveInGetErrorText, TEXT("waveInGetError")); 
} 
 
 
// Turn a TAPI Line error into a printable string. 
LPTSTR FormatTapiError (long lError) 
{ 
   static LPTSTR pszLineError[] =  
   { 
     TEXT("LINEERR No Error"), 
     TEXT("LINEERR_ALLOCATED"), 
     TEXT("LINEERR_BADDEVICEID"), 
     TEXT("LINEERR_BEARERMODEUNAVAIL"), 
     TEXT("LINEERR Unused constant, ERROR!!"), 
     TEXT("LINEERR_CALLUNAVAIL"), 
     TEXT("LINEERR_COMPLETIONOVERRUN"), 
     TEXT("LINEERR_CONFERENCEFULL"), 
     TEXT("LINEERR_DIALBILLING"), 
     TEXT("LINEERR_DIALDIALTONE"), 
     TEXT("LINEERR_DIALPROMPT"), 
     TEXT("LINEERR_DIALQUIET"), 
     TEXT("LINEERR_INCOMPATIBLEAPIVERSION"), 
     TEXT("LINEERR_INCOMPATIBLEEXTVERSION"), 
     TEXT("LINEERR_INIFILECORRUPT"), 
     TEXT("LINEERR_INUSE"), 
     TEXT("LINEERR_INVALADDRESS"), 
     TEXT("LINEERR_INVALADDRESSID"), 
     TEXT("LINEERR_INVALADDRESSMODE"), 
     TEXT("LINEERR_INVALADDRESSSTATE"), 
     TEXT("LINEERR_INVALAPPHANDLE"), 
     TEXT("LINEERR_INVALAPPNAME"), 
     TEXT("LINEERR_INVALBEARERMODE"), 
     TEXT("LINEERR_INVALCALLCOMPLMODE"), 
     TEXT("LINEERR_INVALCALLHANDLE"), 
     TEXT("LINEERR_INVALCALLPARAMS"), 
     TEXT("LINEERR_INVALCALLPRIVILEGE"), 
     TEXT("LINEERR_INVALCALLSELECT"), 
     TEXT("LINEERR_INVALCALLSTATE"), 
     TEXT("LINEERR_INVALCALLSTATELIST"), 
     TEXT("LINEERR_INVALCARD"), 
     TEXT("LINEERR_INVALCOMPLETIONID"), 
     TEXT("LINEERR_INVALCONFCALLHANDLE"), 
     TEXT("LINEERR_INVALCONSULTCALLHANDLE"), 
     TEXT("LINEERR_INVALCOUNTRYCODE"), 
     TEXT("LINEERR_INVALDEVICECLASS"), 
     TEXT("LINEERR_INVALDEVICEHANDLE"), 
     TEXT("LINEERR_INVALDIALPARAMS"), 
     TEXT("LINEERR_INVALDIGITLIST"), 
     TEXT("LINEERR_INVALDIGITMODE"), 
     TEXT("LINEERR_INVALDIGITS"), 
     TEXT("LINEERR_INVALEXTVERSION"), 
     TEXT("LINEERR_INVALGROUPID"), 
     TEXT("LINEERR_INVALLINEHANDLE"), 
     TEXT("LINEERR_INVALLINESTATE"), 
     TEXT("LINEERR_INVALLOCATION"), 
     TEXT("LINEERR_INVALMEDIALIST"), 
     TEXT("LINEERR_INVALMEDIAMODE"), 
     TEXT("LINEERR_INVALMESSAGEID"), 
     TEXT("LINEERR Unused constant, ERROR!!"), 
     TEXT("LINEERR_INVALPARAM"), 
     TEXT("LINEERR_INVALPARKID"), 
     TEXT("LINEERR_INVALPARKMODE"), 
     TEXT("LINEERR_INVALPOINTER"), 
     TEXT("LINEERR_INVALPRIVSELECT"), 
     TEXT("LINEERR_INVALRATE"), 
     TEXT("LINEERR_INVALREQUESTMODE"), 
     TEXT("LINEERR_INVALTERMINALID"), 
     TEXT("LINEERR_INVALTERMINALMODE"), 
     TEXT("LINEERR_INVALTIMEOUT"), 
     TEXT("LINEERR_INVALTONE"), 
     TEXT("LINEERR_INVALTONELIST"), 
     TEXT("LINEERR_INVALTONEMODE"), 
     TEXT("LINEERR_INVALTRANSFERMODE"), 
     TEXT("LINEERR_LINEMAPPERFAILED"), 
     TEXT("LINEERR_NOCONFERENCE"), 
     TEXT("LINEERR_NODEVICE"), 
     TEXT("LINEERR_NODRIVER"), 
     TEXT("LINEERR_NOMEM"), 
     TEXT("LINEERR_NOREQUEST"), 
     TEXT("LINEERR_NOTOWNER"), 
     TEXT("LINEERR_NOTREGISTERED"), 
     TEXT("LINEERR_OPERATIONFAILED"), 
     TEXT("LINEERR_OPERATIONUNAVAIL"), 
     TEXT("LINEERR_RATEUNAVAIL"), 
     TEXT("LINEERR_RESOURCEUNAVAIL"), 
     TEXT("LINEERR_REQUESTOVERRUN"), 
     TEXT("LINEERR_STRUCTURETOOSMALL"), 
     TEXT("LINEERR_TARGETNOTFOUND"), 
     TEXT("LINEERR_TARGETSELF"), 
     TEXT("LINEERR_UNINITIALIZED"), 
     TEXT("LINEERR_USERUSERINFOTOOBIG"), 
     TEXT("LINEERR_REINIT"), 
     TEXT("LINEERR_ADDRESSBLOCKED"), 
     TEXT("LINEERR_BILLINGREJECTED"), 
     TEXT("LINEERR_INVALFEATURE"), 
     TEXT("LINEERR_NOMULTIPLEINSTANCE") 
   }; 
 
   _declspec(thread) static TCHAR szError[512]; 
   DWORD dwError; 
   HMODULE hTapiUIMod = GetModuleHandle(TEXT("TAPIUI.DLL")); 
 
   if (hTapiUIMod) 
   { 
      dwError = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, 
                    (LPCVOID)hTapiUIMod, TAPIERROR_FORMATMESSAGE(lError), 
                    0, szError, sizeof(szError)/sizeof(TCHAR), NULL); 
      if (dwError) 
         return szError; 
   } 
 
   // Strip off the high bit to make the error code positive. 
   dwError = (DWORD)lError & 0x7FFFFFFF; 
 
   if ((lError > 0) || (dwError > sizeof(pszLineError)/sizeof(pszLineError[0]))) 
   { 
      wsprintf(szError, TEXT("Unknown TAPI error code: 0x%lx"), lError); 
      return szError; 
   } 
 
   return pszLineError[dwError]; 
} 
 
 
 
void __cdecl MyPrintf(LPCTSTR pszFormat, ...) 
{ 
   _declspec(thread) static TCHAR szOutput[MAX_PRINT_STRING]; // max printable string length 
   va_list v1; 
   DWORD dwSize; 
 
   va_start(v1, pszFormat); 
 
   dwSize = wvsprintf(szOutput, pszFormat, v1);  
 
#ifdef MSG_DEBUG_PRINT 
   OutputDebugString(szOutput); 
#endif 
 
#ifdef MSG_CONSOLE_PRINT 
   _tprintf(szOutput); 
#endif 
 
#ifdef MSG_BOX_PRINT 
   MessageBox(NULL, szOutput,TEXT("MyPrintf Output"), MB_OK); 
#endif 
 
#ifdef MSG_FILE_PRINT 
   { 
	   static HANDLE hFile = NULL; 
	   DWORD dwNumWritten; 
	   if (hFile == NULL) 
	   { 
		   hFile = CreateFile(szFilePrint, GENERIC_WRITE, FILE_SHARE_READ, NULL,  
			   bZeroFile ? CREATE_ALWAYS : OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
 
		   if (hFile == INVALID_HANDLE_VALUE) 
			   MyPrintf(TEXT("CreateFile output log file %s failed with %s\r\n"), szFilePrint, FormatError(GetLastError())); 
		   else 
            SetFilePointer(hFile, 0, NULL, FILE_END); 
	   } 
	   if (hFile != INVALID_HANDLE_VALUE) 
      { 
         OVERLAPPED ol = {0}; 
         LockFileEx(hFile, 0, 0, 1, 0, &ol); 
		   WriteFile(hFile, szOutput, dwSize*sizeof(TCHAR), &dwNumWritten, NULL); 
         UnlockFileEx(hFile, 0, 1, 0, &ol); 
      } 
   } 
#endif 
} 
 
LPCTSTR FormatError(DWORD dwError) 
{ 
   _declspec(thread) static TCHAR szBuff[MAX_PRINT_STRING]; 
   return FormatErrorBuffer(dwError, szBuff, MAX_PRINT_STRING); 
} 
 
LPCTSTR FormatErrorBuffer(DWORD dwError, LPTSTR pszBuff, DWORD dwNumChars) 
{ 
   DWORD dwRetFM = 0; 
 
   dwRetFM = wsprintf(pszBuff, TEXT("%lu - "), dwError); 
   dwRetFM = FormatMessage( 
      FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0, 
      &pszBuff[dwRetFM], dwNumChars - dwRetFM, NULL); 
 
   if (dwRetFM == 0) 
   { 
      wsprintf(pszBuff, TEXT("FormatMessage failed on %lu with %lu"), 
         dwError, GetLastError()); 
   } 
 
   return pszBuff; 
}