明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

用DELPHI進(jìn)行 Win32環(huán)境下串行通訊的程序設(shè)計(jì)

[摘要]張秀德 姜新通 張冬生 摘要 由于在Delphi環(huán)境中沒有提供通訊控件,本文介紹了用Delphi4.0實(shí)現(xiàn)的Win32環(huán)境下基于線程的串行通訊程序設(shè)計(jì),能適當(dāng)降低數(shù)據(jù)丟失率以及提高系統(tǒng)可靠性,...
張秀德 姜新通   張冬生  

摘要 由于在Delphi環(huán)境中沒有提供通訊控件,本文介紹了用Delphi4.0實(shí)現(xiàn)的Win32環(huán)境下基于線程的串行通訊程序設(shè)計(jì),能適當(dāng)降低數(shù)據(jù)丟失率以及提高系統(tǒng)可靠性,并給出了一個(gè)通訊程序?qū)嵗?br>
關(guān)鍵詞 串行通訊 多線程 程序設(shè)計(jì)

  

在自動(dòng)化工業(yè)控制應(yīng)用中,經(jīng)常需要計(jì)算機(jī)與外圍設(shè)備進(jìn)行數(shù)據(jù)通訊。而異步串行通訊是一種常用的通訊手段。在單任務(wù)操作系統(tǒng)中,不能同時(shí)處理兩件以上不同的任務(wù)。Win32是基于線程的多任務(wù)操作系統(tǒng),使得應(yīng)用程序能同時(shí)執(zhí)行多個(gè)任務(wù),即在一個(gè)進(jìn)程中可同時(shí)運(yùn)行多個(gè)線程。利用Win32的這個(gè)特點(diǎn),在通訊過程中可以適當(dāng)降低數(shù)據(jù)丟失率,提高系統(tǒng)可靠性。

隨著Win95系統(tǒng)的逐步普及,程序員們更愿意在Win95下編程。而Delphi也越來越為廣大程序員所喜愛。然而,令人遺憾的是在Delphi環(huán)境中沒有象其它的一些編程語言一樣提供標(biāo)準(zhǔn)通訊控件。因此,利用Delphi進(jìn)行通訊程序設(shè)計(jì)時(shí),不但要掌握多線程編程技術(shù),還要了解一些與通訊相關(guān)的API函數(shù)的使用。

一 多線程基本概念

首先介紹進(jìn)程概念。一個(gè)進(jìn)程通常定義為程序的一個(gè)實(shí)例。在Win32中,進(jìn)程占據(jù)4GB地址空間。實(shí)際上,一個(gè)進(jìn)程可以包含幾個(gè)線程,它們可以同時(shí)執(zhí)行進(jìn)程的地址空間中的代碼。為了運(yùn)行所有這些線程,操作系統(tǒng)以輪轉(zhuǎn)方式為每個(gè)獨(dú)立線程分配一些CPU時(shí)間片。這給人一種假象,好像這些線程是在同時(shí)運(yùn)行。創(chuàng)建一個(gè)Win32進(jìn)程時(shí),它的第一個(gè)線程稱為主線程,由系統(tǒng)自動(dòng)生成。然后可由主線程生成其它的線程,這些線程又可生成更多的線程。

線程描述了進(jìn)程內(nèi)的執(zhí)行,是組成進(jìn)程的基本單位。每次初始化一個(gè)進(jìn)程時(shí),系統(tǒng)創(chuàng)建一個(gè)主線程。通常對于許多應(yīng)用程序,主線程是應(yīng)用程序的唯一線程。但是,進(jìn)程也可以創(chuàng)建額外的線程,目的在于盡可能充分合理的利用CPU時(shí)間。線程可以使用CreateThread()函數(shù)來創(chuàng)建。

在有若干線程并行運(yùn)行的環(huán)境里,同步各不同線程活動(dòng)的能力是非常重要的,這樣可以避免對共享資源的訪問沖突。事件對象是同步線程的最基本形式,它用以向其它線程發(fā)信號以表示某一操作已經(jīng)完成。例如,一個(gè)進(jìn)程可能運(yùn)行了兩個(gè)線程。第一個(gè)線程從文件讀數(shù)據(jù)到內(nèi)存緩沖區(qū)中。每當(dāng)數(shù)據(jù)已被讀入,第一個(gè)線程就發(fā)信號給第二個(gè)線程它可以處理數(shù)據(jù)了。當(dāng)?shù)诙䝼(gè)線程完成了對數(shù)據(jù)的處理時(shí),它可能需要再次給第一個(gè)線程發(fā)信號以讓第一個(gè)線程能夠從文件中讀入下一塊數(shù)據(jù)。事件可以使用CreateEvent()函數(shù)來創(chuàng)建。線程和事件在任何時(shí)候都處于兩種狀態(tài)之一:有信號和無信號。當(dāng)線程被創(chuàng)建和正在運(yùn)行時(shí),它是無信號的。一旦線程終止,它就變成有信號的。線程可以通過使用SetEvent()和ResetEvent()函數(shù)來將事件置成有信號和無信號。

除了以上介紹的概念和函數(shù),在通訊程序中還要用到等待函數(shù)WaitForSingleObject()和重疊I/O操作。等待函數(shù)能使線程阻塞自身執(zhí)行,而重疊I/O操作能使費(fèi)時(shí)的操作在后臺(tái)中運(yùn)行。

二 通訊程序設(shè)計(jì)

在Windows環(huán)境下,對于串行通訊的控制是通過中斷機(jī)制驅(qū)動(dòng)的,由系統(tǒng)自行處理。Windows禁止應(yīng)用程序直接和硬件打交道,程序員只能使用Windows提供的標(biāo)準(zhǔn)函數(shù)通過通訊驅(qū)動(dòng)程序與硬件接口。首先,用CreateFile()函數(shù)打開通訊端口,然后通過SetupComm() 函數(shù)給通訊的輸入輸出隊(duì)列分配一定大小的內(nèi)存緩沖區(qū),接著通過BuildCommDCB()函數(shù) 和SetCommState()等函數(shù)對主要通訊參數(shù)進(jìn)行設(shè)置。初始化完成后就可以利用ReadFile()函數(shù)和 WriteFile() 函數(shù)對通訊端口進(jìn)行讀寫操作了。程序界面如圖所示。

本文提供的實(shí)例程序使用簡單方便。利用一條串行數(shù)據(jù)線連接在兩臺(tái)計(jì)算機(jī)Com2之間就可以進(jìn)行文本文件傳輸。對于Delphi的具體編程方法這里不再贅述。實(shí)例中有詳細(xì)注釋。

  

unit comunate;

interface

uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

  Dialogs, Buttons, StdCtrls, ComCtrls;

const

  WM_COMMNOTIFY = WM_USER + 1;  // 通訊消息

type

  TForm1 = class(TForm)

    Button1: TButton;

    Button2: TButton;

    Button3: TButton;

    Button4: TButton;

    OpenDialog1: TOpenDialog;

Label1: TLabel;

BitBtn1: TBitBtn;

    RichEdit1: TRichEdit;

    procedure Button1Click(Sender: TObject);

    procedure Button2Click(Sender: TObject);

    procedure Button3Click(Sender: TObject);

    procedure Button4Click(Sender: TObject);

  private

    { Private declarations }

    procedure WMCOMMNOTIFY(var Message :TMessage);message WM_COMMNOTIFY;

  public

    { Public declarations }

  end;

  

var

  Form1: TForm1;

implementation

{$R *.DFM}

var

  hNewCommFile,Post_Event: THandle;

  Read_os : Toverlapped;

  Receive :Boolean;

  ReceiveData : Dword;

  

procedure AddToMemo(Str:PChar;Len:Dword);  // 接收的數(shù)據(jù)送入顯示區(qū)

begin

  str[Len]:=#0;

  Form1.RichEdit1.Text:=Form1.RichEdit1.Text+StrPas(str);

end;

  

procedure CommWatch(Ptr:Pointer);stdcall;     // 通訊監(jiān)視線程

var

  dwEvtMask,dwTranser : Dword;

  Ok : Boolean;

  Os : Toverlapped;

begin

  Receive :=True;

  FillChar(Os,SizeOf(Os),0);

  Os.hEvent :=CreateEvent(nil,True,False,nil); // 創(chuàng)建重疊讀事件對象

  if Os.hEvent=null then

    begin

      MessageBox(0,'Os.Event Create Error !','Notice',MB_OK);

      Exit;

    end;

  if (not SetCommMask(hNewCommFile,EV_RXCHAR)) then

    begin

      MessageBox(0,'SetCommMask Error !','Notice',MB_OK);

      Exit;

    end;

  while(Receive) do

    begin

      dwEvtMask:=0;

      // 等待通訊事件發(fā)生

      if not WaitCommEvent(hNewCommFile,dwEvtMask,@Os) then

        begin

          if ERROR_IO_PENDING=GetLastError then

             GetOverLappedResult(hNewCommFile,Os,dwTranser,True)

        end;

      if ((dwEvtMask and EV_RXCHAR)=EV_RXCHAR) then

        begin

          // 等待允許傳遞WM_COMMNOTIFY通訊消息

          WaitForSingleObject(Post_event,INFINITE);

          // 處理WM_COMMNOTIFY消息時(shí)不再發(fā)送WM_COMMNOTIFY消息

          ResetEvent(Post_Event);

          // 傳遞WM_COMMNOTIFY通訊消息

          Ok:=PostMessage(Form1.Handle,WM_COMMNOTIFY,hNewCommFile,0);

          if (not Ok) then

            begin

              MessageBox(0,'PostMessage Error !','Notice',MB_OK);

              Exit;

            end;

        end;

    end;

  CloseHandle(Os.hEvent); // 關(guān)閉重疊讀事件對象

end;

  

procedure TForm1.WMCOMMNOTIFY(var Message :TMessage);  // 消息處理函數(shù)

var

  CommState : ComStat;

  dwNumberOfBytesRead : Dword;

  ErrorFlag : Dword;

  InputBuffer : Array [0..1024] of Char;

begin

  if not ClearCommError(hNewCommFile,ErrorFlag,@CommState) then

    begin

      MessageBox(0,'ClearCommError !','Notice',MB_OK);

      PurgeComm(hNewCommFile,Purge_Rxabort or Purge_Rxclear);

      Exit;

    end;

  if (CommState.cbInQue>0) then

    begin

      fillchar(InputBuffer,CommState.cbInQue,#0);

      // 接收通訊數(shù)據(jù)

      if (not ReadFile( hNewCommFile,InputBuffer,CommState.cbInQue,

                        dwNumberOfBytesRead,@Read_os )) then

        begin

          ErrorFlag := GetLastError();

          if (ErrorFlag <> 0) and (ErrorFlag <> ERROR_IO_PENDING) then

            begin

              MessageBox(0,'ReadFile Error!','Notice',MB_OK);

              Receive :=False;

              CloseHandle(Read_Os.hEvent);

              CloseHandle(Post_Event);

              CloseHandle(hNewCommFile);

              Exit;

            end

          else

            begin           

              WaitForSingleObject(hNewCommFile,INFINITE); // 等待操作完成

              GetOverlappedResult(hNewCommFile,Read_os,

                                  dwNumberOfBytesRead,False);

            end;

        end;

      if dwNumberOfBytesRead>0 then

        begin

          Read_Os.Offset :=Read_Os.Offset+dwNumberOfBytesRead;

          ReceiveData := Read_Os.Offset;

          // 處理接收的數(shù)據(jù)

          AddToMemo(InputBuffer,dwNumberOfBytesRead);

        end;

end;

  // 允許發(fā)送下一個(gè)WM_COMMNOTIFY消息

  SetEvent(Post_Event);

end;

  

procedure TForm1.Button1Click(Sender: TObject); // 打開文件用于發(fā)送

begin

  if OpenDialog1.Execute then

    begin

      Button3.Enabled :=False;

      Button4.Enabled :=False;

      RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);

      Form1.Caption := IntToStr(RichEdit1.GetTextLen);

    end;

    Button1.Enabled :=False;

end;

  

procedure TForm1.Button2Click(Sender: TObject); // 發(fā)送數(shù)據(jù)

var

  dcb : TDCB;

  Error :Boolean;

  dwNumberOfBytesWritten,dwNumberOfBytesToWrite,

  ErrorFlag,dwWhereToStartWriting : DWORD;

  pDataToWrite : PChar;

  write_os: Toverlapped;

begin

  Form1.Caption :='';

  // 打開通訊端口COM2

  hNewCommFile:=CreateFile( 'COM2',GENERIC_WRITE,0,

                           nil, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0 );

  if hNewCommFile = INVALID_HANDLE_VALUE then

     MessageBox(0,'Error opening com port!','Notice',MB_OK);

  SetupComm(hNewCommFile,1024,1024); // 設(shè)置緩沖區(qū)大小及主要通訊參數(shù)

  GetCommState( hNewCommFile,dcb);

  dcb.BaudRate :=9600;

  dcb.ByteSize :=8;

  dcb.Parity :=NOPARITY;

  dcb.StopBits := ONESTOPBIT;

  Error := SetCommState( hNewCommFile, dcb );

  if ( not Error) then MessageBox(0,'SetCommState Error!','Notice',MB_OK);

  dwWhereToStartWriting := 0;

  dwNumberOfBytesWritten := 0;

  dwNumberOfBytesToWrite :=RichEdit1.GetTextLen;

  if (dwNumberOfBytesToWrite=0) then

    begin

      ShowMessage('Text Buffer is Empty!');

      Exit;

    end

  else

    begin

      pDataToWrite:=StrAlloc(dwNumberOfBytesToWrite+1);

      try

        RichEdit1.GetTextBuf(pDataToWrite,dwNumberOfBytesToWrite);

        Label1.Font.Color :=clRed;

        FillChar(Write_Os,SizeOf(write_os),0);

        // 為重疊寫創(chuàng)建事件對象

        Write_Os.hEvent := CreateEvent(nil,True,False,nil);

        SetCommMask(hNewCommFile,EV_TXEMPTY);

        Label1.Caption:='正在發(fā)送數(shù)據(jù)...!';

        repeat

           Label1.Repaint;

           // 發(fā)送通訊數(shù)據(jù)

           if not WriteFile( hNewCommFile,pDataToWrite[dwWhereToStartWriting],

                              dwNumberOfBytesToWrite,dwNumberOfBytesWritten,

                         @write_os ) then

           begin

               ErrorFlag :=GetLastError;

               if ErrorFlag<>0 then

                 begin

                  if ErrorFlag=ERROR_IO_PENDING then

                     begin

                       WaitForSingleObject(Write_Os.hEvent,INFINITE);

                       GetOverlappedResult(hNewCommFile,Write_os,

                                           dwNumberOfBytesWritten,False);

                     end

                   else

                     begin

                       MessageBox(0,'WriteFile Error!','Notice',MB_OK);

                       Receive :=False;

                       CloseHandle(Read_Os.hEvent);

                       CloseHandle(Post_Event);

                       CloseHandle(hNewCommFile);

                       Exit;

                     end;

                end;

             end;

         Dec( dwNumberOfBytesToWrite, dwNumberOfBytesWritten );

           Inc( dwWhereToStartWriting, dwNumberOfBytesWritten );

        until (dwNumberOfBytesToWrite <= 0);  // Write the whole thing!

        Form1.Caption:=IntToStr(dwWhereToStartWriting);

      finally

        StrDispose(pDataToWrite);

      end;

      CloseHandle(hNewCommFile);

    end;

  Label1.Font.Color :=clBlack;

  Label1.Caption:='發(fā)送成功!';

  Button1.Enabled :=True;

  Button3.Enabled :=True;

  Button4.Enabled :=True;

end;

  

procedure TForm1.Button3Click(Sender: TObject); // 接收處理

var

  Ok : Boolean;

  dcb : TDCB;

  com_thread: Thandle;

  ThreadID:DWORD;

begin

  ReceiveData :=0;

  Button1.Enabled :=False;

  Button2.Enabled :=False;

  RichEdit1.Clear;

  // 打開COM2

  hNewCommFile:=CreateFile( 'COM2',GENERIC_READ,0,

                         nil, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0 );

  if hNewCommFile = INVALID_HANDLE_VALUE then

     begin

       MessageBox(0,'Error opening com port!','Notice',MB_OK);

       Exit;

     end;

  Ok:=SetCommMask(hNewCommFile,EV_RXCHAR);

  if ( not Ok) then

    begin

      MessageBox(0,'SetCommMask Error!','Notice',MB_OK);

      Exit;

    end;

  SetupComm(hNewCommFile,1024,1024);

  GetCommState( hNewCommFile, dcb );

  dcb.BaudRate :=9600;

  dcb.ByteSize :=8;

  dcb.Parity :=NOPARITY;

  dcb.StopBits := ONESTOPBIT;

  Ok := SetCommState( hNewCommFile, dcb );

  if ( not Ok) then MessageBox(0,'SetCommState Error!','Notice',MB_OK);

  FillChar(Read_Os,SizeOf(Read_Os),0);

  Read_Os.Offset := 0;

  Read_Os.OffsetHigh := 0;

  // Create Event for Overlapped Read

  Read_Os.hEvent :=CreateEvent(nil,true,False,nil);

  if Read_Os.hEvent=null then

    begin

      CloseHandle(hNewCommFile);

      MessageBox(0,'CreateEvent Error!','Notice',MB_OK);

      Exit;

    end;

  // Create Event for PostMessage

  Post_Event:=CreateEvent(nil,True,True,nil);

  if Post_Event=null then

    begin

      CloseHandle(hNewCommFile);

      CloseHandle(Read_Os.hEvent);

      MessageBox(0,'CreateEvent Error!','Notice',MB_OK);

      Exit;

end;

  // 建立通信監(jiān)視線程

  Com_Thread:=CreateThread(nil,0,@CommWatch,nil,0,ThreadID);

  if (Com_Thread=0) then

    MessageBox(Handle,'No CraeteThread!',nil,mb_OK);

  EscapeCommFunction(hNewCommFile,SETDTR);

  Label1.Font.Color :=clRed;

  Label1.Caption:='正在接收數(shù)據(jù)...!';

end;

  

procedure TForm1.Button4Click(Sender: TObject); // 停止通訊處理

begin

  Label1.Font.Color :=clBlack;

  Label1.Caption:='infomation';

  Form1.Caption := IntToStr(ReceiveData);

  Receive :=False;

  CloseHandle(Read_Os.hEvent);

  CloseHandle(Post_Event);

  CloseHandle(hNewCommFile);

  Button1.Enabled :=True;

  Button2.Enabled :=True;

end;

  

end.

  

參考文獻(xiàn)

1.Windows95 Windows NT3.5高級編程技術(shù) Jeffrey Richter著

2.基于Windows 95&NT的串行通信編程 李柯 <<微電腦世界>> 1997。5

3.Windows 95中的串行通信 王齊 <<微電腦世界>> 1997。3