天天看點

用TAPI實作MODEM通信程式設計

轉自:http://bbs.tech.ccidnet.com/read.php?tid=192210

TAPI意即Telephony API,是一組Windows作業系統提供的一組針對電話MODEM進行通信程式設計的API函數。下面就幾年前筆者在工程應用中的實際代碼對使用TAPI實作電話MODEM通信程式設計的通信過程和相關API函數作詳細的介紹。

1、初始化線路

通過使用lineInitializeEx函數初始化TAPI.DLL得到TAPI使用句柄的指針個g_hTapi,通過調用lineOpen函數(用到參數g_hTapi)獲得線路句柄m_hLine;再利用lineGetID(用到參數m_hLine)擷取數據機句柄m_hModem。

(1)首先應使用lineInitializeEx初始化TAPI.DLL得到使用TAPI的句柄,該函數是使用TAPI的任何其它函數之前必須調用的函數,它除傳回使用句柄g_hTapi外,還傳回系統中的可用線路數g_dwLineTotalNum,LineCallBackProc為設定的回調函數,後面會講到。如果成功傳回值為0,否則為錯誤資訊。在下面封裝的函數TelephonyInitialize中還通過版本協商确定了線路号是否可用,這些可用的線路号在打開線路時會用到。同時用到幾個全局變量。

BOOL g_bTapiInitialized = 0;//確定lineInitializeEx在程式中隻被調用一次

HLINEAPP g_hTapi = 0;//使用TAPI的通路句柄。

DWORD g_dwLineTotalNum = 0;//系統中安裝的線路條數。

DWORD g_dwUsedTapiVersion[100];//儲存協商後使用的TAPI版本,後面的函數将會用到。

DWORD g_dwlineUsable[100];//系統中可用的線路号

long TelephonyInitialize(int nRetryNum)

{

if(g_bTapiInitialized) return 0;

LONG lrc = -1l;

DWORD i;

LINEEXTENSIONID extensions;

DWORD dwVersion;

LINEINITIALIZEEXPARAMS lineParam;

lineParam.dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW;

lineParam.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS);

for(i = 0; i < USABLELINE_MAX; i++)

g_dwlineUsable = 0xffffffff;

int nRetryCount = 0;

while (nRetryCount <= nRetryNum)

{

lrc = ::lineInitializeEx(&g_hTapi, ::AfxGetApp()->m_hInstance,

  (LINECALLBACK)LineCallBackProc, ::AfxGetAppName(),

  &g_dwLineTotalNum, &dwVersion, &lineParam);

if(lrc == LINEERR_REINIT)

{

  Sleep (5);   // sleep for five seconds

  TRACE("電話系統初始化....../n");

  nRetryCount++;

  continue;

}

else

  break;

} // end while (TAPI reinitializing)

if(lrc) return lrc;

g_bTapiInitialized = TRUE;

DWORD dwUsedVersion = 0;

for (i = 0; (unsigned)i < g_dwLineTotalNum; i++)

{

// negotiate version of TAPI to use

lrc = ::lineNegotiateAPIVersion(g_hTapi, i,

  WIN95TAPIVERSION, WIN95TAPIVERSION,

  &dwUsedVersion, &extensions);

if (lrc) continue;

g_dwUsedTapiVersion = dwUsedVersion;

g_dwlineUsable = i;

}

return 0l;

}

(2)使用lineOpen打開指定的線路,使用到上一個函數傳回的g_hTapi和一個要打開的線路号(m_dwActulLineNo),傳回已打開的線路句柄m_hLine儲存在筆者封裝的一個類CPhoneComm的成員變量中。須注意的是此處lineOpen的參數dwCallbackInstance帶入了this指針,供回調函數時使用。

long CPhoneComm::OpenLine()

{

long lrc;

if(!g_bTapiInitialized)

{

lrc = TelephonyInitialize();

if(lrc) return lrc;

}

if(g_dwlineUsable[m_dwActulLineNo] == m_dwActulLineNo)

{

lrc = ::lineOpen(g_hTapi, m_dwActulLineNo, &m_hLine,

    g_dwUsedTapiVersion[m_dwActulLineNo], 0, (DWORD)this,

    LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM, NULL);

if (lrc) return lrc;

}

m_bLineUsable = TRUE;

return lrc;

}

return LINEERR_NODEVICE;

}

(3)使用lineGetID取得裝置的通信句柄m_hModem,儲存它後面将會用到,同時可儲存該線路的名稱備用。

long CPhoneComm::LineGetID()

{

  CommID FAR *cid;

  VARSTRING *vs;

  LONG lrc;

  DWORD dwSize;

  vs = (VARSTRING *) calloc (1, sizeof(VARSTRING));

  vs->dwTotalSize = sizeof(VARSTRING);

  do {

lrc = ::lineGetID(m_hLine, 0, NULL, LINECALLSELECT_LINE, vs, "comm/datamodem");

if (lrc) {free (vs);return lrc;}

if (vs->dwTotalSize < vs->dwNeededSize)

{

        dwSize = vs->dwNeededSize;

        free (vs);

        vs = (VARSTRING *) calloc(1, dwSize);

          vs->dwTotalSize = dwSize;

  continue;

    }

    break;

  } while (TRUE);

cid = (CommID FAR *) ((LPSTR)vs + vs->dwStringOffset);

  m_szModemName = &cid->szDeviceName[0];

// save modem handle

m_hModem = cid->hComm;

free (vs);

return 0l;

}

2、配置線路(可選):

調用SetCommConfig(用到hModem)改變數據機的設定。

long CPhoneComm::SetModemConfig()

{

DWORD dwsize = sizeof(COMMCONFIG)+sizeof(MODEMSETTINGS);

LONG lrc;

do

{

if (m_pCommconfig != NULL) {

  free (m_pCommconfig);

  m_pCommconfig = NULL;

}

m_pCommconfig = (COMMCONFIG *) calloc (1, dwsize);

  m_pCommconfig->dwSize = dwsize;

lrc = ::GetCommConfig(m_hModem, m_pCommconfig, &dwsize);

m_pModemsettings = (MODEMSETTINGS *) m_pCommconfig->wcProviderData;

// try again

if (m_pCommconfig->dwSize < dwsize) continue;

break;

} while (TRUE);

m_pCommconfig->dcb.fTXContinueOnXoff = 1;

dwsize = m_pCommconfig->dwSize + m_pModemsettings->dwActualSize;

lrc = ::SetCommConfig(m_hModem, m_pCommconfig, dwsize);

return 0l;

}

在應答方,就可以等待呼入了。

3、撥号與應答

(1)呼叫方:使用lineMakeCall函數(用到m_hLine)進行撥号,完成後獲得呼叫句柄m_hCall(呼叫方的呼叫句柄),這樣在呼叫方就等待應答方的連接配接了。

long CPhoneComm::DialCall(LPCTSTR lpszDialNum, LPCTSTR szCountryCode)

{

char szTemp[100], szCode[20];

memset(szTemp, 0, 60);

memset(szCode, 0, 20);

LPLINETRANSLATEOUTPUT lto;

LINECALLPARAMS *pCallParams;

long lResult;

if(lpszDialNum == NULL)//預設撥打組态号碼

lpszDialNum = m_szDialNo;

if(szCountryCode == NULL)

::tapiGetLocationInfo (szCode, szTemp);

else

memcpy(szCode, szCountryCode, strlen(szCountryCode));

memset(szTemp, 0, 100);

if(szCode[0] != '+')

szTemp[0] = '+';

strcat(szTemp, szCode);

strcat(szTemp, lpszDialNum);

lto = (LINETRANSLATEOUTPUT *)

  calloc(sizeof(LINETRANSLATEOUTPUT)+5000,1);

lto->dwTotalSize = sizeof(LINETRANSLATEOUTPUT)+5000;

lResult = ::lineTranslateAddress(g_hTapi,

  m_dwActulLineNo, g_dwUsedTapiVersion[m_dwActulLineNo],

  szTemp, 0,

  0,lto);

if (lResult) {free(lto);return lResult; }

memcpy (szTemp, (LPSTR)((DWORD)lto+lto->dwDialableStringOffset), lto->dwDialableStringSize);

szTemp[lto->dwDialableStringSize] = 0;

pCallParams = (LINECALLPARAMS *)

    calloc(sizeof(LINECALLPARAMS),1);

memset(pCallParams, 0, sizeof(LINECALLPARAMS));

pCallParams->dwTotalSize = sizeof(LINECALLPARAMS);

pCallParams->dwBearerMode = LINEBEARERMODE_VOICE;

pCallParams->dwMediaMode = LINEMEDIAMODE_DATAMODEM;

lResult = ::lineMakeCall(m_hLine, &m_hCall,

  szTemp, 0, pCallParams);

free (pCallParams);

free(lto);

if (lResult < 0) return lResult;

return 0l;

}

(2)應答方:應答方的回調函數得到LINECALLSTATE_OFFERING消息時,調用lineAnswer函數實作自動應答(呼叫句柄m_hCall由回調函數的參數給出)。代碼參見回調函數。

4、回調函數與資料通信

回調函數是在調用lineInitializeEx設定給TAPI.DLL的,當有消息時DLL通過該回調函數叫應用程式處理。回調函數處理了已建立連接配接和對方挂線消息。

void FAR PASCAL LineCallBackProc(DWORD dwDevice, DWORD dwMessage,DWORD dwInstance,DWORD dwParam1, DWORD dwParam2,DWORD dwParam3)

{

CPhoneComm * pPhone = (CPhoneComm *)dwInstance;//該參數在lineOpen時帶入。

If(pPhone == NULL) return;

switch (dwMessage)

{

case LINE_CALLSTATE:

  pPhone->DoLineCallState(dwDevice, dwParam1, dwParam2, dwParam3);

  break;

default:

  break;

} //end switch

return;

}

void CPhoneComm::DoLineCallState(DWORD dwDevice, DWORD dwParam1, DWORD dwParam2,DWORD dwParam3)

{ //LINE_CALLSTATE

long lrc;

switch (dwParam1)

{

case LINECALLSTATE_IDLE:

  StopReadWrite();

  ::lineDeallocateCall (m_hCall);

  m_bCallValid = FALSE;

  break;

case LINECALLSTATE_OFFERING:

  if(m_bAllowAnswer)

  {

  if(!m_bCallValid)

  {

  m_hCall = (HCALL)dwDevice;

  m_bCallValid = TRUE;

  }

  lrc = ::lineAnswer(m_hCall,NULL,0);

  }

  break;

case LINECALLSTATE_CONNECTED:

  StartReadWrite();

  break;

case LINECALLSTATE_DISCONNECTED:

lineClose(m_hLine);

default:

  break;

} //end switch

}

當回調函數收到LINECALLSTATE_CONNECTED消息後,就可以使用函數為WriteFile及ReadFile函數進行資料交換了,調類成員函數StartReadWrite(),此處不在詳述隻用。

5、某一方挂機、關閉線路

通信完畢任何一方都可以調用函數lineDrop來停止呼叫,使用到呼叫句柄m_hCall,該函數還發送LINECALLSTATE_IDLE消息給回調函數同時對方在挂斷之後也會收到該消息,這時應首先停止資料通信如StartReadWrite(),可對呼叫調用lineDeallocateCal釋放占用的資源,參見回調函數。使用lineClose釋放由lineOpen配置設定的資源。

long CPhoneComm::LineDrop()

{

long lResult = ::lineDrop(m_hCall,NULL,0);

if (lResult < 0)

return lResult;

return 0l;

}

6、釋放TAPI.DLL

最後當不在使用TAPI時使用lineShutdown釋放TAPI為你配置設定的資源。

void telephonyShutdown()

{

if(g_bTapiInitialized)

::lineShutdown(g_hTapi);

}