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;
} /* end if (need more space) */
break; /* success */
} 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;
} /* LineCallBackProc */
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);
}