用DELPHI進(jìn)行 Win32環(huán)境下串行通訊的程序設(shè)計(jì)
發(fā)表時(shí)間:2023-08-14 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]張秀德 姜新通 張冬生 摘要 由于在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