天天看点

用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);

}