用WindowsAPI設計多線(xiàn)程的串行通信ActiveX控件
串行通信ActiveX控件的設計方法,并給出主要的通信程序代碼。
本文引用地址:http://dyxdggzs.com/article/201612/332845.htm串行通信 多線(xiàn)程
串行通信是計算機之間及計算機及數字化儀器和設備的一種重要通信手段,是實(shí)現工業(yè)監控的一種主要方式。Windows下的串行通信主要有兩種方法:利用VB的MSCOMM控件和利用Windows API。MSCOMM控件簡(jiǎn)單易用,但由于其對串口設備的封裝及調用方式的局限性,不能靈活方便地對串口設備進(jìn)行控制。而通過(guò)Windows API則可以實(shí)現對串口設備的完全控制,并且可以提供多線(xiàn)程的通信機制。
在復雜應用中,通信通常在后臺完成,需要采用多線(xiàn)程技術(shù)。一個(gè)多線(xiàn)程的應用程序實(shí)際上是在其內部實(shí)現了多任務(wù)擴展,為代碼賦予了并行執行的特性,適于執行一些實(shí)時(shí)性或隨機性很強的操作,也有利于提高CPU的利用率,加快通信程序的信息處理速度。
本文以一臺工業(yè)控制PC機與多臺基于單片機的智能控制單片進(jìn)行串行通信為實(shí)例。PC機和各智能控制單元通過(guò)RS485總線(xiàn)互聯(lián)。由于RS485的通信方式是半雙工的,只能由作為主節點(diǎn)的PC機依次輪詢(xún)網(wǎng)絡(luò )上的各智能控制單元子節點(diǎn)。每次通信都是由PC機通過(guò)串口向智能控制單元發(fā)布命令,智能控制單元在接收到正確的命令后做出應答。
系統的主節點(diǎn)應用程序是用VB6.0編寫(xiě)的,為了既能提供多線(xiàn)程的串行通信機制,又可使應用程序易于實(shí)現串行通信功能,利用VC++6.0開(kāi)發(fā)基于Window API的多線(xiàn)程串行通信ActiveX控件。主節點(diǎn)的應用程序通過(guò)對串行通信ActiveX控件的調用完成與各子節點(diǎn)的通信。
1 創(chuàng )建ActiveX控件JinRiComm.OCX
VGC++6.0和MFC是健建ActiveX控件的強大而又靈活的工具。JinRiComm控件創(chuàng )建步驟簡(jiǎn)單概述如下:
(1)用MFC ActiveX ControlWizard生成ActiveX控件工程,命名為JinRiComm。
(2)打開(kāi)ClassWizard窗口,選擇Automation標簽,單擊“Add Property”按鈕,命名新的屬性。單擊“AddMethod”按鈕,命名新的方法。選擇ActiveX Event標簽,單擊“Add Event”按鈕,命名新的事件。
(3)向控件工程中添加類(lèi)CserialPort,為該類(lèi)添加成員變量和成員函數,該類(lèi)將完成串行通信工作。
2 串口通信的基本編程
用Windows API函數實(shí)現串行通信,其特點(diǎn)是對串口的操作如對文件操作一樣,打開(kāi)和關(guān)閉串行設備與打開(kāi)和關(guān)閉文件使用相同的函數。
(1)打開(kāi)串口
m_hComm=CreateFile(szPort,GENERIC_READ |GENERIC_WRITE,0,NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
(2)獲取當前通信信息,設備通信設備
GetCommState(m_hComm,&m_dcb);
SetCommState(m_hComm,&m_dcb);
(3)讀、寫(xiě)串口
bResult=ReadFile(port->m_hComm,&RXBuff,1,&BytesRead,Port->m_ov);
bResult=WriteFile(port->m_hComm,&(port-m_Byte)[i],1,&BytesSent,&port->m_ov);
(4)關(guān)閉串口
CloseHandle (m_hComm);
3 設計程序中的線(xiàn)程
MFC執行兩種類(lèi)型的線(xiàn)程:用戶(hù)界面線(xiàn)程和工作線(xiàn)程。前者用來(lái)處理用戶(hù)輸入,響應由用戶(hù)產(chǎn)生的事件和消息。后者不處理窗口消息,用于完成后臺計算、打印和其它一些沒(méi)有必要強迫用戶(hù)來(lái)等待的任務(wù)。在本程序中,用戶(hù)界面線(xiàn)程就是程序的主線(xiàn)程,另外再添加兩個(gè)工作線(xiàn)程:通信線(xiàn)程和延時(shí)線(xiàn)程。它們的功能介紹如表1所示。
表1
線(xiàn)程名稱(chēng) | 主要功能 | 線(xiàn)程函數 |
主線(xiàn)程 | 響應用戶(hù)對控件的調用(設置控件屬性和調用控制方法);初始化串口;處理通信線(xiàn)程接收到的數據,并通知用戶(hù)(觸發(fā)控件消息);通知通信線(xiàn)程向串口寫(xiě)數據。 | |
通信線(xiàn)程 | 在主線(xiàn)程初始化串口后被創(chuàng )建。CommThread函數進(jìn)入死循環(huán),線(xiàn)程一直監視串口事件,當讀串口事件發(fā)生,讀取串口接收到的數據,向主線(xiàn)程發(fā)自定義消息WM_COMM_RXCHAR,通知主線(xiàn)程處理數據;收到主線(xiàn)程的寫(xiě)串口命令時(shí),將緩存中的數據寫(xiě)到串口。 | CommThread(LPVOID pParam) |
延時(shí)線(xiàn)程 | 在主線(xiàn)程向串口寫(xiě)數據之后被創(chuàng )建。如果主線(xiàn)置為真,延時(shí)線(xiàn)程在檢查到其為真后,線(xiàn)程函數返回;否則會(huì )延時(shí)10ms再判斷一次。如果超過(guò)八規定時(shí)間仍沒(méi)檢查到其為真,則向主線(xiàn)程發(fā)自定義消息WM_DELAY_TIMEOUT,通知主線(xiàn)程重發(fā)剛才的命令,然后線(xiàn)程函數返回。 | DelayThread(LPVOID pParam) |
應用AfxBeginThread函數來(lái)啟動(dòng)一個(gè)工作線(xiàn)程,用法如下:
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlage=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL)
在啟動(dòng)一個(gè)工作線(xiàn)程之前,必須為線(xiàn)程編寫(xiě)一個(gè)全局的線(xiàn)程函數。這個(gè)線(xiàn)程函數接受一個(gè)32位的LPVOID作為參數,返回一個(gè)UINT,線(xiàn)程函數的結構為:
UINT ThreadUFunction(LPVOID pParam)
{
//線(xiàn)程處理代碼
return 0;
}
終止線(xiàn)程有兩種途徑:當線(xiàn)程函數返回時(shí),線(xiàn)程終止;線(xiàn)程函數也可以在內部調用AfxEndThread函數來(lái)終止自己。
程序流程圖如圖1所示。
4 線(xiàn)程間的通信
(1)通過(guò)全局變量
主線(xiàn)程可以采用多種方式與工作線(xiàn)程進(jìn)行通信,最簡(jiǎn)單的辦法是通過(guò)全局變量,因為進(jìn)程中的所有線(xiàn)程都可以訪(fǎng)問(wèn)所有的全局變量。如:定義全局變量bReceiveSuccess,它表示是否收到了正確的響應。在主線(xiàn)程向串口寫(xiě)數據之后它被置為FALSE,然后延時(shí)線(xiàn)程啟動(dòng)。當系統收到正確的響應后,bReceiveSuccess被主線(xiàn)程改為T(mén)RUE。延時(shí)線(xiàn)程根據bReceiveSuccess的值來(lái)決定是結束該線(xiàn)程還是給主線(xiàn)程發(fā)消息。
(2)通過(guò)參數
主線(xiàn)程可以向工作線(xiàn)程傳遞一個(gè)4字節的參數,一種使用該參數的常見(jiàn)方式是傳遞一個(gè)指針,它指向這個(gè)線(xiàn)程的父類(lèi)。如:
UINT CserialPort::CommThread(LPVOID pParam)
{
CserialPort port=(CSerialPort)pParam; //取得串口類(lèi)指針
//線(xiàn)程處理代碼
}
(3)通過(guò)消息
工作線(xiàn)程獲得主線(xiàn)程的窗口句柄,則可以給主線(xiàn)程發(fā)送消息。如:
通信線(xiàn)程通知主線(xiàn)程,串口接收到了數據
::PostMessage ((port ->m_pOwner) ->m_hWnd,WM_COMM_RXCHAR,(WPARAM) RXBuff,(LPARAM)port->m_nPortNr);
5 線(xiàn)程的同步
多線(xiàn)程的優(yōu)點(diǎn)之一是所有線(xiàn)程都可以訪(fǎng)問(wèn)相同的全局對象和共享資源,它提供了程序設計的簡(jiǎn)捷性和便利性,提高了對信息處理的并發(fā)度。但如果不妥善處理好線(xiàn)程的并發(fā)問(wèn)題,也會(huì )帶來(lái)數據的錯誤或是資源的死鎖。為了避免這些問(wèn)題發(fā)生,線(xiàn)程在使用共享資源或對象前必須獲得一個(gè)約束訪(fǎng)問(wèn)同步對象的權力,也就是通過(guò)同步的機制來(lái)控制這種權力的使用。線(xiàn)程間的同步多種方法。
(1)臨界區
臨界區是通過(guò)多個(gè)線(xiàn)程的串行化來(lái)訪(fǎng)問(wèn)公共資源或一段代碼。如:
InitializeCriticalSection(&(port->Lm_csCommunication Sync));
//初始化臨界區對象
EnterCriticalSection(&prot->m_csCommunicationSyne);
//使調用線(xiàn)程等待得臨界區對象并在獲得擁有權時(shí)返回
Do
{
if(!bReceiveSuccess) //訪(fǎng)問(wèn)全局變量
{
LeaveCriticalSection(&port->m_csCommunicationSync);
//釋放對臨界區對象的擁有權
//其它處理代碼
}
}
(2)事件
事件用來(lái)通知線(xiàn)程有一些事件已經(jīng)發(fā)生,比較適合于信號控制。事件有手動(dòng)復位和自動(dòng)復位兩種。手動(dòng)復位事件是在應用程序或系統后臺控制不改變它的信號狀態(tài)。當手動(dòng)復位事件處于有信號狀態(tài)時(shí),所有等待該事件的線(xiàn)程都被激活,事件保留有信號狀態(tài)直到被一個(gè)應用程序復位為止。當一個(gè)自動(dòng)復位事件處于有信號狀態(tài)時(shí),只有一個(gè)等待線(xiàn)程被激活,并且事件將復位成無(wú)信號,其它所有等待著(zhù)的線(xiàn)程仍將保持掛起狀態(tài)。
定義3個(gè)事件:
m_hEventArray[0]=m_hShutdownEvent;
//結束通過(guò)線(xiàn)程事件
m_hEventArray[1]=m_ov.hEvent; //讀事件
m_hEventArray[2]=m_hWriteEvent;//寫(xiě)事件
在通信線(xiàn)程的線(xiàn)程函數CommThread中等待3個(gè)事件的發(fā)生
Event=WaitForMultipleObjects (3,port ->m_hEventArray,FALSE,INFINITE);
switch (Event)
{
case 0: //結束通信線(xiàn)程事件
{
port->m_bThreadAlive=FALSE;
AfxEndThread(100);//結束通信線(xiàn)程
Bread;
}
case 1://讀事件
{
GetCommMask(port->m_hComm,&CommEvent);
If (CommEvent & EV_RECHAR)
ReceiveChar(port,comstat); //從串口讀數
Break;
}
case 2: //寫(xiě)事件
{
WriteChar(port); //向窗口寫(xiě)數
break;
}
}//end switch
評論