二、通信過(guò)程描述
----1初始化線(xiàn)路(通信雙方都應該初始化線(xiàn)路)
----通過(guò)使用lineInitialize函數初始化TAPI.DLL得到TAPI使用句柄的指針hTapi,請注意參數中回調函數的定義(所有提及函數的用法均可從BC++5.0及VisualC++5.0的幫助中獲得);通過(guò)調用lineOpen函數(用到參數hTapi)獲得線(xiàn)路句柄hLine;再利用lineGetID(用到參數hLine)獲取調制解調器句柄hModem
----2配置線(xiàn)路(可選)
----
----調用SetCommConfig(用到hModem)改變調制解調器的設置
----3撥號(由呼叫方執行)
----使用lineMakeCall函數(用到hLine)進(jìn)行撥號,完成后獲得呼叫句柄hCall(呼叫方的呼叫句柄)
----4應答鏈接(由被呼叫方執行)
----被呼叫的一方的回調函數得到LINECALLSTATE_OFFERING消息時(shí),調用lineAnswer函數實(shí)現自動(dòng)應答(呼叫句柄hCall由回調函數的參數給出)
----5數據通信(雙方)
----當回調函數收到LINECALLSTATE_CONNECTED消息后,請先清除接收緩沖區,可以使用函數為WriteFile及ReadFile函數進(jìn)行數據交換,注意參數hFile為調制解調器句柄hModem
----6掛機(某一方)
----通信完畢任何一方都可以調用函數lineDrop(hCall,NULL,0)來(lái)停止呼叫,該函數還發(fā)送LINECALLSTATE_IDLE消息給回調函數
----7關(guān)閉線(xiàn)路(雙方)
----通信雙方的回調函數在收到LINECALLSTATE_IDLE消息時(shí)都應該調用函數lineDeallocateCall(hCall)釋放相應呼叫占用的資源;當回調函數收到LINECALLSTATE_DISCONNECTED消息時(shí)請使用lineClose(hLine)釋放由lineOpen分配的資源,調用lineShutDown(hTapi)釋放為線(xiàn)路設備分配的資源
三、軟硬件環(huán)境
----下圖示意出了我們的應用程序所處的位置以及涉及到的軟硬件環(huán)境:
----我們的通信應用程序通過(guò)TAPI操作Modem撥號、應答、鏈接、掛機控制電話(huà)呼叫,在編制DOS應用程序的時(shí)候,我們經(jīng)常使用Hayes兼容的AT命令集來(lái)完成這些操作,由于各調制解調器廠(chǎng)家對該命令集都做了各自的擴展,因而,我們的DOS應用程序一般只能操作一小部分調制解調器,而各廠(chǎng)家都提供Windows驅動(dòng)程序,所以,使用TAPI編制的應用程序能夠操作絕大多數調制解調器;圖中的通信API是應用程序發(fā)送、接收數據的編程接口。
四、程序流程結構框圖
----由于Win95為多任務(wù)操作系統,我們的流程圖只能代表本應用程序的執行先后關(guān)系,程序中的等待及檢測實(shí)際上是等待Win95提供的消息,所以并不占用CPU時(shí)間,在下面的程序中可以看出。另外,數據交換的協(xié)議可由自己制定,也可使用已有的協(xié)議。
五、軟件編制
----由于Windows編程的框架基本相同,在此我們只介紹涉及到通信的一部分源程序:
----1頭文件中應該包括:
----#include
----請注意工程文件的屬性應該是Windows32位應用程序
----2通信所涉及到的一些全局變量定義及類(lèi)型定義:
charRecBuf[20],buf[20]//緩沖區
DWORDError; //錯誤碼
COMSTATStatus; //狀態(tài)碼
DWORDNumLine; //允許使用的線(xiàn)路設備數
LINECALLPARAMSpara;//呼叫參數
TmyDecFrame*pwin=NULL;//主窗口指針
HLINEAPPmyhTapi;//線(xiàn)路應用程序句柄
HLINEmyhLine;//線(xiàn)路句柄
HANDLEmyhModem;//調制解調器句柄
HCALLmyhCall;//呼叫句柄
typedefstructtagModemID{
HANDLEhModem;
charModemName[1];
}ModemID;
----3下面為獲取調制解調器句柄的函數定義
----因為每個(gè)調制解調器的標志字符串長(cháng)度不一,所以函數中用到了可變長(cháng)度的字符串,處理方法是先為字符串指針?lè )峙鋝izeof(VARSTRING)大小的空間,再利用該空間容納調用LineGetID時(shí)Windows返回的信息,根據返回信息判斷所需空間大小重新分配空間,再次調用LineGetID就可以取得完整的標志字符串。
voidGethModem(HLINEhLine)
{ ModemIDfar*mid;
VARSTRING*str;
LONGlid;
DWORDsize;
charmark=1;
str=(VARSTRING*)malloc(sizeof(VARSTRING));
if(!str)
returnNULL;
str->dwTotalSize=sizeof(VARSTRING);
do
{ if((lineGetID(myhLine,0,NULL,LINECALLSELECT_LINE,str,
"comm/datamodem")==0)&&(str->dwTotalSizedwNeededSize))
{ dwSize=str->dwNeededSize;
free(str);
str=(VARSTRING*)malloc(dwSize);
if(!str)
{ myhModem=NULL;
mark=2;
}
str->dwTotalSize=dwSize;
}
elsemark=0;
}while(mark==1);
if(mark==0)
{ mid=(ModemIDfar*)((LPSTR)str+str->dwStringOffset);
myhModem=mid->hModem;
}
free(str);
}
----4在主窗口初始化函數中加入對線(xiàn)路的初始化過(guò)程:
pwin=this;//獲得主窗口指針
while(lineInitialize(&myhTAPI,GetModule()->GetInstance(),
(LINECALLBACK)MakeProcInstance((FARPROC)lpfnCallback,
GetModule()->GetInstance()),"TRY",&NumLine)==LINEERR_REINIT)
{ sleep(1);//延遲 };
Error=lineOpen(hTAPI,0,&HLine,0x10004,0,0,LINECALLPRIVILEGE_MONITOR+
LINECALLPRIVILEGE_OWNER,LINEMEDIAMODE_DATAMODEM,NULL);
if(Error!=0)
{ sprintf(buf,"%lx",Error);
MessageBox(buf,0,MB_OK); }
else
{ GethModem(myhLine);//取得myhModem的值
if(myhModem!=NULL)
{ para.dwBearerMode=LINEBEARERMODE_VOICE;
para.dwMediaMode=LINEMEDIAMODE_DATAMODEM;
para.dwTotalSize=sizeof(LINECALLPARAMS);
Error=lineMakeCall(myhLine,&myhCall,"8880751",0,?);
If(Error!=0)
{ sprintf(buf,"%lx",Error);
MessageBox(buf,0,MB_OK); }
}
}
}
----5呼叫方回調函數的定義
voidfarpascalTMyDecFrame::lpfnCallback
(DWORDhDevice,DWORDdwMsg,
DWORDdwCallbackInstance,
DWORDdwParam1,DWORDdwParam2,
DWORDdwParam3)//
參數定義同lineCallbackFunc函數中的參數定義
{ intRec_num=0;
switch(dwParam1)
{ caseLINECALLSTATE_CONNECTED:
DWORDlen;
ClearCommError(myhModem,&Error,&Status);
Rec_num=Status.cbInQue;
ReadFile(myhModem,RecBuf,Rec_num,&len,0);
//至此已經(jīng)為數據通信做好了前期準備,可設立標志
WriteFile(myhModem,"Success",7,&len,0);
ReadFile(myhModem,RecBuf,8,&len,0);
pwin->MessageBox(RecBuf,0,MB_OK);
break;
caseLINECALLSTATE_IDLE:
lineDeallocateCall(myhCall);
break;
caseLINECALLSTATE_DISCONNECTED:
lineClose(myhLine);
lineShutDown(myhTapi);
break;
}
}
----6被叫方回調函數的定義
voidfarpascalTMyDecFrame::lpfnCallback(DWORDhDevice,DWORDdwMsg,
DWORDdwCallbackInstance,DWORDdwParam1,DWORDdwParam2,
DWORDdwParam3)
{intRec_num=0;
switch(dwParam3)
{ caseLINECALLPRIVILEGE_OWNER:
myhCall=(HCALL)hDevice;
Break;
}//只有對呼叫具有私有特權的調用者才能應答呼叫,
在此獲得呼叫句柄
switch(dwParam1)
{ caseLINECALLSTATE_CONNECTED:
DWORDlen;
ClearCommError(myhModem,&Error,&Status);
Rec_num=ComS.cbInQue;
ReadFile(myhModem,RecBuf,Rec_num,&len,0);//清除接收緩沖區
ReadFile(myhModem,RecBuf,7,&len,0);
WriteFile(myhModem,"Received",8,&len,0);
pwin->MessageBox(RecBuf,0,MB_OK);
break;
caseLINECALLSTE_OFFERING:
lineAnswer(myhCall,NULL,0);
break;//完成自動(dòng)應答
caseLINECALLSTATE_IDLE:
lineDeallocateCall(myhCall);
break;
caseLINECALLSTATE_DISCONNECTED:
lineClose(myhLine);
lineShutDown(myhTapi);
break;
}
}
六、改進(jìn)措施
----以上程序中使用的是同步讀寫(xiě)方式,只要WriteFile或者ReadFile沒(méi)有完成指定的I/O任務(wù),它們就不會(huì )返回進(jìn)程,在許多情況下,這是令人難以容忍的CPU時(shí)間浪費;改進(jìn)的辦法是在每次讀之前采用ClearCommError函數確定系統的串行口緩沖區中到底有了多少字節的接收數據,而寫(xiě)方式采用異步方式,首先應該定義一個(gè)OVERLAPPED結構,從BC++5.0中獲得的結構定義如下:
typedefstruct_OVERLAPPED{//o
DWORDInternal;
DWORDInternalHigh;
DWORDOffset;
DWORDOffsetHigh;
HANDLEhEvent;
}OVERLAPPED;
----我們定義OVERLAPPEDmyOVLP;
----我們只用到了其中的hEvent成員,其他成員均置0;hEvent設置為CreateEvent(NULL,TRUE,FALSE,NULL)產(chǎn)生的事件句柄;然后如下調用WriteFile(myhModem,"Received",8,&len,&myOVLP);
----函數將立即返回,此后,只要GetOverlappedResult函數返回TRUE,寫(xiě)操作就算完成了。