天天看點

【轉】VS2010下MFC的序列槽程式設計

序列槽通信簡介

  一般來說,計算機都有一個或多個串行端口,這些序列槽提供了外部裝置與PC進行資料傳輸和通信的通道,在CPU和外設之間充當解釋器的角色。當字元資料從CPU發送給外設時,這些字元資料将被轉換成串行比特流資料;當接收資料時,比特流資料被轉換為字元資料傳遞給CPU,再進一步說,在作業系統方面,Windows用通信驅動程式(COMM.DRV)調用API函數發送和接收資料;當用通信控件或聲明調用API函數時,它們由COMM.DRV解釋并傳遞給裝置驅動程式。作為一個程式員,要編寫通信程式,隻需知道通信控件提供的Windows API通信函數的接口即可,換句話說,隻需設定和監視通信控件的屬性和事件即可。

  序列槽通信方法一般有以下幾種:

  1. 利用Windows API通信函數;
  2. 利用Visual C++的标準通信函數_inp、_inpw、_inpd、_outp、_outpw、_outpd等直接對序列槽進行操作;
  3. 通過微軟的序列槽通信控件MSComm,它是一種ActiveX控件;
  4. 利用第3方編寫的通信類,比如MuMega Technologies公司提供的CSerail類;

  我在項目開發過程中用的是第三種方法——通過MSComm控件操作序列槽,下面是我使用此控件的筆記。

MSComm控件簡介

  MSComm 是 Microsoft 公司為簡化Windows下串行端口程式設計而提供的ActiveX控件,它提供了一系列标準通訊指令的使用接口。MSComm 控件通過串行端口(serial port)傳送和接收資料,為應用程式提供了串行通訊功能。在可視化程式設計盛行的今天,我們可以很友善的在Visual Basic(VB)、Visual C++(VC)、Delphi等語言及開發平台中應用。處理資料的方式有事件驅動(Event-driver)、查詢法(Inquire)兩種。

  事件驅動法:在使用事件驅動法設計程式時,每當有新字元到達、端口狀态變化或發生錯誤時,MSComm控件将觸發OnComm事件,而應用程式在捕獲該事件後,通過檢查MSComm控件的CommEvent屬性可以獲知所發生的事件或錯誤,進而采取相應的操作。這種方法的優點是程式響應及時,可靠性高。

  查詢法:這種方法适合于較小的應用程式。在這種情況下,每當應用程式執行完某一串行口操作後,将不斷檢查MSComm控件的CommEvent屬性以檢查執行結果或者檢查某一事件是否發生。例如,當程式向串行裝置發送了某個指令後,可能隻是在等待收到一個特定的響應字元串,而不是對收到的每一個字元都立刻響應并處理。

  使用的每個MSComm控件都與一個序列槽對應。如果在應用程式中需要通路多個序列槽,必須使用多個MSComm控件,可以在Windows 控制台中修改序列槽位址的中斷位址。

MSComm控件的常用屬性

  • CommPort屬性:設定或傳回通訊端口号,可以設定為1到16之間的任何值;
  • Settings屬性:以字元串形式設定或傳回波特率、奇偶校驗、資料位和停止位;
  • PortOpen屬性:設定或傳回通訊口的狀态以及打開和關閉端口,可通過把該屬性設定為true或者false來打開或者關閉端口;
  • InBufferSize和OutBufferSize屬性:分别設定接收和發送緩沖區配置設定的記憶體數量,機關為位元組,預設值分别為1024byte和512byte;
  • InputLen屬性:确定希望從接收緩沖區移出的字元數量,當InputLen=0時,一次把接收緩沖區的字元全部移出;
  • Input屬性:從接收緩沖區中讀出資料,然後将該資料從緩沖區移走。
  • OutPut屬性:向發送緩沖區傳遞待發送的資料。
  • InBufferCount和OutBufferCount屬性:分别确定目前駐留在接收緩沖區等待被取出和發送緩沖區準備發送的字元數量,這兩個屬性設定為0,接收和發送緩沖區的内容将被清除;
  • InputMode屬性:設定接收傳入資料的格式,設定為0采用文本形式,設定為1采用二進制格式;
  • SThreshold屬性:儲存一個産生發送OnComm事件的界限值,本系統設定該屬性為0,發送資料時不産生OnComm事件;
  • RThreshold屬性:設定當接收幾個字元時觸發OnComm事件,本系統設定該屬性為1,每接收一個字元就産生一個OnComm事件;

MSComm控件的事件

  MSCOMM控件隻使用一個事件OnComm,用屬性CommEvent的17個值來區分不同的觸發時機,主要有以下幾個:

  • CommEvent=1時:傳輸緩沖區中的字元個數已少于Sthreshold(可設定的屬性值)個;
  • CommEvent=2時:接收緩沖區中收到Rthreshold(可設定的屬性值)個字元,利用此事件可編寫接收資料的過程;
  • CommEvent=3時:CTS線發生變化;
  • CommEvent=4時:DSR線發生變化;
  • CommEvent=5時:CD線發生變化;
  • CommEvent=6時:檢測到振鈴信号;

  另外十種情況是通信錯誤時産生,即錯誤代碼。

基于VS2010下MFC的MSComm序列槽程式的實作

1、注冊MSComm控件

  我在網上下載下傳了MSComm控件之後,将其放于項目目錄下,并在目前目錄建了個.bat批處理檔案,其内容如下:

copy .\\MSCOMM\\MSCOMM.SRG %windir%\system32
copy .\\MSCOMM\\MSCOMM32.DEP %windir%\system32
copy .\\MSCOMM\\MSCOMM32.oca %windir%\system32
copy .\\MSCOMM\\mscomm32.ocx %windir%\system32

regsvr32 mscomm32.ocx      

輕按兩下此檔案,即可注冊MSComm控件。

2、添加MSComm控件

  首先将MSComm控件添加進VS2010工具箱,再給項目添加該ActiveX控件對應的“基于MFC的ATL類”,最後将工具箱中的MSComm控件(電話圖示)拖至對話框即可。在對話框中添加MSComm控件後,其側面會有白色,右擊此控件,選擇“編輯控件”,即可去除白色。

3、添加控件變量

  在主對話框中添加與MSComm控件相關聯的控件變量(成員對象),通過此成員變量可操作序列槽。

4、序列槽資訊配置及打開序列槽

  在對話框模闆上右擊MSComm控件,選擇Property菜單項,即可設定MSComm控件各項屬性。在數據機通訊的程式中,設定“Control”屬性頁中Handshaking項為“2-comRTS”,否則國内部分廠家modem不能正常通訊,其它接受預設設定。另外亦可通過修改對話框類的OnInitDialog()函數來設定控件的屬性。具體參考MSDN中的關于Comm Control的詳細說明。

  我程式的序列槽設定代碼大緻如下:

//*********************** 序列槽設定 **************************//
    m_ctrlComm.put_CommPort(port);//選擇com口
    m_ctrlComm.put_InputMode(1);//輸入方式為二進制方式
    m_ctrlComm.put_InBufferSize(1024);//輸入緩沖區大小為1024byte
    m_ctrlComm.put_OutBufferSize(512);//輸出緩沖區大小為512byte

    CString strBaudrate;
    strBaudrate.Format(_T("%ld"),baudrate);
    m_ctrlComm.put_Settings(strBaudrate+_T(",n,8,1"));//設定序列槽參數:9600波特率,無奇偶校驗,8個資料位,1個停止位
    
    if(!m_ctrlComm.get_PortOpen())
    {
        /*
        HANDLE m_hCom;        
        CString strCom;  
        strCom.Format(_T("\\\\.\\COM%d"),(int)(m_ctrlComm.get__CommPort()));  
        // 這裡的CreateFile函數起了很大的作用,可以用來建立系統裝置檔案,
        //如果該裝置不存在或者被占用,則會傳回一個錯誤,即下面的 INVALID_HANDLE_VALUE ,
        //據此可以判斷可使用性。詳細參見MSDN中的介紹。  
        m_hCom = CreateFile(strCom, 0, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);  
        if(m_hCom == INVALID_HANDLE_VALUE)//如果沒有該裝置,或者被其他應用程式在用  
        {  
            int errornum=GetLastError();  
            if(errornum==2)  
                strCom.Format(_T("端口%d 不存在"),(int)(m_ctrlComm.get__CommPort()));  
            else if(errornum==5)  
                strCom.Format(_T("端口%d被占用"),(int)(m_ctrlComm.get__CommPort()));  
            AfxMessageBox(strCom);  
            CloseHandle(m_hCom); // 關閉檔案句柄,後面我們采用控件,不用API  
            return ;//這是因為序列槽初始化封裝在另一個函數裡面在OnInitDialog調用。  
        }  
        CloseHandle(m_hCom); // 關閉檔案句柄,後面我們采用控件,不用API  
        */
        try
        {
            m_ctrlComm.put_PortOpen(true);//打開序列槽
        }
        catch(COleDispatchException *e)
        {
            CString strError;
            strError.Format(_T("打開序列槽失敗!\n\nError Number: %d \nError Message: %s"),
                e->m_wCode,e->m_strDescription);
            MessageBoxW(strError,_T("錯誤提示"),MB_ICONERROR);
            return;
        }
    }
    else
    {
        //MessageBox(_T("Cannot open serial port!"));
    }

    m_ctrlComm.put_RThreshold(1);//每當序列槽接收緩沖區有多餘或等于1個字元時将引發一個接收資料的oncomm事件
    m_ctrlComm.put_InputLen(0);//設定目前接收區資料長度為0
    m_ctrlComm.get_Input();//預讀緩沖區以清空殘留資料      

5、序列槽資料的讀寫

  MSComm 類的讀寫函數比較簡單:get_Input()和put_Output()。函數原形分别為VARIANT get_Input()和void put_Output(const VARIANT newValue),均使用VARIANT類型。但PC機發送和接收資料時習慣用字元串形式。MSDN中查閱VARIANT類型,可以用BSTR表示字元串,但所有的BSTR都包含寬字元,而隻有Windows NT支援寬字元,Windows 9X并不支援。是以要完成一個适應各平台的序列槽應用程式必須解決這個問題,這裡使用CByteArray解決之。

  添加接收資料函數,在對話框中輕按兩下Comm Control,接受預設函數,則對話框類的成員函數為OnCommMscomm(),其大緻代碼如下:

   CDataTypeConverter DTC;
    //電話圖示可能有一半白邊去不了,右擊電話圖示點選edit control就可以去掉
    if(m_ctrlComm.get_CommEvent()==2)//事件值為2表示接收事件
    {
        BYTE rxdata[255]={0};//設定BYTE數組
        VARIANT variant_inp=m_ctrlComm.get_Input();//讀緩沖區
        COleSafeArray safearray_inp = variant_inp;//VARIANT型變量轉換為COleSafeArray變量
        long len=safearray_inp.GetOneDimSize();//得到有效資料長度
        for(long k=0;k<len;k++)
            safearray_inp.GetElement(&k,rxdata+k);//轉換為BYTE數組
        m_ctrlComm.put_OutBufferCount(0);//清空發送緩沖區
        m_ctrlComm.put_InBufferCount(0);//滑空接收緩沖區
        safearray_inp.Clear();
            
        for(long k=0;k<len;k++)
        {
            BYTE bt = *(char*)(rxdata+k);//字元型
            short int intDec=(int)bt;
            CString strtemp=DTC.Dec2Hex(intDec);
            m_strDataRXTemp+=strtemp;//加入接收編輯框對應字元串
        }
        m_strDataRX=m_strDataRXTemp;
        m_strDataRXTemp="";
   }      

其中,Dec2Hex()函數的代碼如下:

CString CDataTypeConverter::Dec2Hex(unsigned int intDec)
{
    CString strHex;
    char charHex[255];
    sprintf(charHex,"%x",intDec);
    strHex=charHex;
    if(strHex.GetLength()==1)
        strHex=_T("0")+strHex;
    return strHex;
}      

  發送資料的代碼大緻如下:

//UpdateData(true);//讀取編輯框内容m_strDataTX

//發送的字元串上表面為十六進制格式
CString m_strCtrlLightBL;
m_strCtrlLightBL="55AA0AAA6B4310100000";//"55aa0aaa6b4310100000"
    
CDataTypeConverter DTC;
COleVariant m_OleVariant=DTC.HexM2OleVariant(m_strCtrlLightBL);

m_ctrlComm.put_Output(m_OleVariant);//發送資料      

其中,HexM2OleVariant()函數定義如下:

COleVariant CDataTypeConverter::HexM2OleVariant(CString strHexM)
{
    BYTE bt[255];
    short int len=strHexM.GetLength();
    short int length=0;
    short int intDec;
    for(int n=0,i=0;n<len-1;n+=2,i++)
    {        
        intDec=Hex2Dec(strHexM.Mid(n,2));
        bt[i]=char(intDec);
        length=i+1;
    }
    CByteArray m_Array;
    m_Array.RemoveAll();
    m_Array.SetSize(length);
    for(int i=0;i<length;i++)
        m_Array.SetAt(i,bt[i]);
    return COleVariant(m_Array);
}      

注意:接收資料時,RThreshold屬性很重要,因為它影響着OnComm事件的觸發條件,在程式中可以通過put_RThreshold()函數來設定RThreshold屬性。

相關連結:

  深入淺出VC++序列槽程式設計(五) 基于第三方類庫:http://blog.csdn.net/nash635/article/details/5339704

中文名:高洪臣

英文名:Gordon

E-mail:[email protected]

轉載于:https://www.cnblogs.com/zhanjxcom/p/4471986.html

繼續閱讀