轉自: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);
}