帮你贴篇文章不知有没有用
用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
Char;Len
word);
// 接收的数据送入显示区
begin
str[Len]:=#0;
Form1.RichEdit1.Text:=Form1.RichEdit1.Text+StrPas(str);
end;
procedure CommWatch(Ptr
ointer);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
WORD;
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