首页 | 社区 | 博客 | 招聘 | 文章 | 新闻 | 下载 | 读书 | 代码
亲,您未登录哦! 登录 | 注册

用DELPHI进行 Win32环境下串行通讯的程序设计

打印文章

分享到:
张秀德 姜新通   张冬生  

摘要 由于在Delphi环境中没有提供通讯控件,本文介绍了用Delphi4.0实现的Win32环境下基于线程的串行通讯程序设计,能适当降低数据丢失率以及提高系统可靠性,并给出了一个通讯程序实例。

关键词 串行通讯 多线程 程序设计

  

在自动化工业控制应用中,经常需要计算机与外围设备进行数据通讯。而异步串行通讯是一种常用的通讯手段。在单任务操作系统中,不能同时处理两件以上不同的任务。Win32是基于线程的多任务操作系统,使得应用程序能同时执行多个任务,即在一个进程中可同时运行多个线程。利用Win32的这个特点,在通讯过程中可以适当降低数据丢失率,提高系统可靠性。

随着Win95系统的逐步普及,程序员们更愿意在Win95下编程。而Delphi也越来越为广大程序员所喜爱。然而,令人遗憾的是在Delphi环境中没有象其它的一些编程语言一样提供标准通讯控件。因此,利用Delphi进行通讯程序设计时,不但要掌握多线程编程技术,还要了解一些与通讯相关的API函数的使用。

一 多线程基本概念

首先介绍进程概念。一个进程通常定义为程序的一个实例。在Win32中,进程占据4GB地址空间。实际上,一个进程可以包含几个线程,它们可以同时执行进程的地址空间中的代码。为了运行所有这些线程,操作系统以轮转方式为每个独立线程分配一些CPU时间片。这给人一种假象,好像这些线程是在同时运行。创建一个Win32进程时,它的第一个线程称为主线程,由系统自动生成。然后可由主线程生成其它的线程,这些线程又可生成更多的线程。

线程描述了进程内的执行,是组成进程的基本单位。每次初始化一个进程时,系统创建一个主线程。通常对于许多应用程序,主线程是应用程序的唯一线程。但是,进程也可以创建额外的线程,目的在于尽可能充分合理的利用CPU时间。线程可以使用CreateThread()函数来创建。

在有若干线程并行运行的环境里,同步各不同线程活动的能力是非常重要的,这样可以避免对共享资源的访问冲突。事件对象是同步线程的最基本形式,它用以向其它线程发信号以表示某一操作已经完成。例如,一个进程可能运行了两个线程。第一个线程从文件读数据到内存缓冲区中。每当数据已被读入,第一个线程就发信号给第二个线程它可以处理数据了。当第二个线程完成了对数据的处理时,它可能需要再次给第一个线程发信号以让第一个线程能够从文件中读入下一块数据。事件可以使用CreateEvent()函数来创建。线程和事件在任何时候都处于两种状态之一:有信号和无信号。当线程被创建和正在运行时,它是无信号的。一旦线程终止,它就变成有信号的。线程可以通过使用SetEvent()和ResetEvent()函数来将事件置成有信号和无信号。

除了以上介绍的概念和函数,在通讯程序中还要用到等待函数WaitForSingleObject()和重叠I/O操作。等待函数能使线程阻塞自身执行,而重叠I/O操作能使费时的操作在后台中运行。

二 通讯程序设计

在Windows环境下,对于串行通讯的控制是通过中断机制驱动的,由系统自行处理。Windows禁止应用程序直接和硬件打交道,程序员只能使用Windows提供的标准函数通过通讯驱动程序与硬件接口。首先,用CreateFile()函数打开通讯端口,然后通过SetupComm() 函数给通讯的输入输出队列分配一定大小的内存缓冲区,接着通过BuildCommDCB()函数 和SetCommState()等函数对主要通讯参数进行设置。初始化完成后就可以利用ReadFile()函数和 WriteFile() 函数对通讯端口进行读写操作了。程序界面如图所示。

本文提供的实例程序使用简单方便。利用一条串行数据线连接在两台计算机Com2之间就可以进行文本文件传输。对于Delphi的具体编程方法这里不再赘述。实例中有详细注释。

  

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);  // 接收的数据送入显示区

begin

  str[Len]:=#0;

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

end;

  

procedure CommWatch(Ptr:Pointer);stdcall;     // 通讯监视线程

var

  dwEvtMask,dwTranser : Dword;

  Ok : Boolean;

  Os : Toverlapped;

begin

  Receive :=True;

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

  Os.hEvent :=CreateEvent(nil,True,False,nil); // 创建重叠读事件对象

  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;

      // 等待通讯事件发生

      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消息时不再发送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); // 关闭重叠读事件对象

end;

  

procedure TForm1.WMCOMMNOTIFY(var Message :TMessage);  // 消息处理函数

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

      // 接收通讯数据

      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;

          // 处理接收的数据

          AddToMemo(InputBuffer,dwNumberOfBytesRead);

        end;

end;

  // 允许发送下一个WM_COMMNOTIFY消息

  SetEvent(Post_Event);

end;

  

procedure TForm1.Button1Click(Sender: TObject); // 打开文件用于发送

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); // 发送数据

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); // 设置缓冲区大小及主要通讯参数

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

        // 为重叠写创建事件对象

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

        SetCommMask(hNewCommFile,EV_TXEMPTY);

        Label1.Caption:='正在发送数据...!';

        repeat

           Label1.Repaint;

           // 发送通讯数据

           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:='发送成功!';

  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;

  // 建立通信监视线程

  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:='正在接收数据...!';

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.

  

参考文献

1.Windows95 Windows NT3.5高级编程技术 Jeffrey Richter著

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

3.Windows 95中的串行通信 王齐 <<微电脑世界>> 1997。3

本栏文章均来自于互联网,版权归原作者和各发布网站所有,本站收集这些文章仅供学习参考之用。任何人都不能将这些文章用于商业或者其他目的。( Pfan.cn )

编程爱好者论坛

本栏最新文章