前言
隨著計算機軟硬件的是益發(fā)展,基于Windows95及NT平臺的軟件越來越多,在智能化電子儀表及計算機控制系統(tǒng)中都涉及到計算機與智能儀或計算機之間進行信息交換,而串行通信是計算機之間以及計算機與單片機等數字化儀器通信的一種重要手段,是實現工業(yè)監(jiān)控的一種主要方式,由于它高效可靠,價格便宜,遵循統(tǒng)一的標準,因而得到廣泛應用。隨著計算機技術不斷發(fā)展,編程手段也不斷提高,如Visual Basic 、Delphi 、Visual C++ 以及 C++ Builder等采用面向對象構件的方法,使得編寫Windows下的應用程序變得迅速和容易 ,其中Delphi功能強大,代碼效率高,深受軟件開發(fā)人員睛睞, 但Delphi同Visual C++ 以及 C++ Builder一樣均未提供通信構件,為此用Delphi開發(fā)通信應用軟件時就得應用API函數或Visual Basic的通信構件,API函數對一般開發(fā)人員有一定難度而且不太方便 ,而用VB 的通信構件開發(fā)的應用程序需在WINDOWS95或NT中安裝并注刪相應的動態(tài)庫才能運行,這對應用用戶來說很不方便。為此本文介紹用API函數和多線程編程技術在Delphi3.0下設計出自已的通信構件,并提供了全部源程序,利用Delphi安裝新構件方法將其安裝到自已的編譯系統(tǒng)中,就可以十分方便地開發(fā)出通信程序,該構件在智能超聲液體成份分析儀及集散式網絡測控熱處理系統(tǒng)的被成功地應用。從中可以看出利用Delphi編制構件不斷豐富Delphi的內容的方法。
1 串行通信構件設計思想
一般基于DOS編程的程序員在編寫串行通信時,往往是編寫一個中斷服務程序,一旦串行口有數據它就會向CPU發(fā)出中斷請求,CPU在響應該中斷后會執(zhí)行串口的中斷服務程序,從而完成預定的任務。在Windows操作系統(tǒng)下,由于Windows禁止應用程序直接和硬件打交道,所以程序員只能使用Windows提供的標準函數編程。雖然由于無需對硬件編程對有關硬件調試方便,但Windows本身遠比DOS復雜,所以對這些標準函數和它們攜帶參數的理解和使用也遠比DOS困難,在Windows3.X中,當一個通信設備被打開并允許傳送WM-COMMNOTIFY消息時,只要該通信設備收到數據,操作系統(tǒng)就會在消息隊列中置入WM-COMMNOTIFY消息,應用程序可以通過截獲操作系統(tǒng)發(fā)出的WM-COMMNOTIFY消息來對已打開的通信設備進行操作。
在Windows95與NT中,修改了Windows3.X對串行口操作的標準函數,進行了更統(tǒng)一的規(guī)范化,取消了WM-COMMNOTIFY消息以及OpenComm,CloseComm,ReadComm,WriteComm,FlushComm等函數,對待串行口操作如同文件一樣,其串行設備的打開和關閉操作使用與文件打開與關閉操作相同的函數,如CreatFile,CloseFile,ReadFile,WriteFile,PurgeComm等,由于Windows95與NT中允許用戶定義大小的讀寫緩沖區(qū),這樣數據丟失可能性很小,同時使得讀寫速度很快。在Windows95與NT中支持多線程編程技術,而Delphi3.0為多線程編程和編制構件提供了支持,這樣就可以編制串行通信構件了,即建立新的“.pak”文件就行了。
考慮到篇幅,在這個構件中只提供必要且夠一般常用的幾個屬性和當輸入緩沖有數據時而產生的事件,這些屬性中可視屬性為波特率、數據位、效驗位、停止位、串行口名、輸入緩沖大。醋x緩沖)、輸出緩沖大。磳懢彌_)、觸發(fā)事件方式;運行屬性有串口設備句柄、消息窗句柄、事件句柄;運行中的方法有端口打開和端口關閉函數。
構件的設計思想是:可視屬性中的數據位、效驗位、停止位、觸發(fā)事件方式用梅舉類型定義,編程人員將方便地選擇所需的值就行了,可視屬性中波特率、串行口名、輸入緩沖大小、輸出緩沖大小由編程人員輸入設定;觸發(fā)事件方式有每收一字符觸發(fā)和一隊列收到后觸發(fā)。在構件的創(chuàng)建過程中將可視屬性賦缺省值,當程序運行構件的端口打開函數(ComPortOpen )時,將串口按構件可視屬性設定值進行端口初始化及創(chuàng)建監(jiān)視串口線程并返回端口句柄(hCommFile);監(jiān)視線程的作用是,按觸發(fā)事件方式監(jiān)視串口,當串口有數據時就向窗函數發(fā)出自定義的WM_COMMNOTIFY消息,窗函數收到WM_COMMNOTIFY消息后觸發(fā)OnComm事件;當執(zhí)行端口關閉函數(comPortClose)時,該函數關閉端口并撤消監(jiān)視線程。程序流程圖為圖1。
圖 1
2 應用說明
當執(zhí)行ComPortOpen函數(即方法)時,用CreatFile()打開串行口,此時fdwShareMode,參數必須是零,打開獨占訪問的資源。FdwCreate參數必須是指定的OPEN_EXISTING標志,hTemplateFile參數必須是Nil,用GetCommState設置通信參數,用CreateEvent()創(chuàng)建事件對象,用AllocateHWnd()得到窗口數構柄;利用Delphi3.0創(chuàng)建多線工具建立一個監(jiān)視線程的對象TmyCommWacth;在監(jiān)視線程中用ResetEVent()設置事件句柄,用WaitForSingleObject()指定對象處于信號或超時狀態(tài)時返回,用PostMessage()向指定窗發(fā)送消息; 窗函數收到消息后用ClearCommError()清除錯誤,用自定的過程 OnCommData(PChar(msg.LParam), msg.WParam )觸發(fā)事件OnComm,當執(zhí)行端口關閉函數comPortClose時 ,用CloseMyComThread撤消監(jiān)視線程,用DeallocateHWnd()釋放消息窗句柄,用 CloseHandle()關閉事件和串口;用RegisterComponents 對構件進行注冊?紤]到篇幅源程序未提供讀寫緩沖數據程序,實際上接收數據可在OnComm事件中用ReadFile()讀,其文件句柄為ComPortOpen返回的串口設備句柄hCommFile;寫數據可編一過程或函數用WriteFile(),其文件句柄同讀句柄,讀寫數據比較簡單。圖2為編譯安裝后構件在Object Inspector下所現示的屬性及事件。
圖 2
3 構件源程序
unit comm32;
interface
uses
Windows,Messages,SysUtils,Classes, Graphics, Controls, Forms, Dialogs;
const
WMCOMMNOTIFY = WMUSER + 1;
Type{定義屬性用梅舉類型}
TParity = ( None, Odd, Even, Mark, Space );
TStopBits = (1, 15, 2 );
TOncommMode = (evchar,evflag);
TComPorts=( com1,com2,com3,com4);
ECommsError = class( Exception );
TOncommEvent = procedure(Sender: TObject;Buffer:Pointer;BufferLength: Word) of
object;{觸發(fā)事件對像}
Type{創(chuàng)建監(jiān)視線程類}
TMyCommWacth = class(TThread)
private
PostEvent: Integer;
。 Private declarations }
protected
procedure Execute; override;
Public
hCommFile: THandle;{串口句柄}
hCloseEvent: THandle; {事件句柄}
hComm32Window:THandle;{消息窗句柄}
Lpoverlapped:TOVERLAPPED;
ConStructor Create;{構造函數}
end;
type{創(chuàng)建構件對象}
Tcomm32 = class(TComponent)
Private{定義屬性的私有變量}
MyComThread: TMyCommWacth;
BaudRates: Integer;
comName: TComPorts;
parity: TParity ;
Stopbits : TStopBits ;
DataBits : Byte;
InPutbuffers: Integer;
OutPutbuffers: Integer;
commMode: TOncommMode;
OnCommMsg: TOnCommEvent;
procedure CommWndProc( var msg: TMessage );message WMCOMMNOTIFY;
{ Private declarations }
protected
procedure OnCommData(Buffer: PChar; BufferLength: Word);
。 Protected declarations }
public{運行屬性}
hCommFile: THandle;
hCloseEvent: THandle;
hComm32Window:THandle;
Function ComPortOpen : Thandle;
Function ComPortClose : Boolean;
procedure CloseMyComThread;
Constructor
Create(Aowner:TComponent);override;
destructor Destroy; override;
。 Public declarations }
published{可視屬性及事件}
property comParity: TParity read Parity Write Parity default None;
property ComPortName:TComPorts read comName Write comName default com2;
property BaudRate:Integer read BaudRates Write BaudRates default 9600 ;
property Stopbit:TStopBits read Stopbits Write Stopbits default1;
property ByteDataBit:Byte read DataBits Write DataBits default 8;
property InBuffersize: Integer read InPutbuffers Write InPutbuffers default 1024;
property OutBuffersize:Integer read OutPutbuffers Write OutPutbuffers default 1024;
property SetComMode:TOncommMode read commMode Write commMode default evChar;
property OnComm:TOnCommEvent read OnCommMsg write OnCommMsg;
end;
procedure Register;
implementation
TMyCommWacth.Create();{監(jiān)視線程創(chuàng)建}
begin
inherited Create(False);
FreeOnTerminate:=True;
end;
{監(jiān)視線程執(zhí)行}
procedure TMyCommWacth.Execute;
Var DwTransfer,DwEvtMask:Integer;
begin
if Comm32.SetComMode = Evchar then
begin
if not SetCommMask(hCommFile,
EVRXCHAR) then Exit;
While( true) do
begin
DwEvtMask:=0;
WaitCommEvent(hCommFile,
DwEvtMask,@Lpoverlapped);
if((DwEvtMaskandEVRXCHAR)
=EVRXCHAR) then
begin
WaitForSingleObject(PostEvent, 1000000);
ResetEVent(PostEvent);
PostMessage(hComm32Window ,WMCOMMNOTIFY,hcommfile,0);
end;
end;
end else
begin
if not setCommMask(hCommFile,
EVRXFLAG) then Exit;
While( true) do
begin
DwEvtMask:=0;
WaitCommEvent(hCommFile,DwEvtMask,@comm32.Lpoverlapped);
if ((DwEvtMask and EVRXFLAG)
=EVRXFLAG) then
begin
WaitForSingleObject(comm32.PostEvent,1000000);
ResetEVent(comm32.PostEvent);
PostMessage(hComm32Window,WMCOMMNOTIFY,hCommFile,NULL);
end;
end;
end;
end; {監(jiān)視線程結束}
{建立通信構件}
Tcomm32.Create(Aowner:Tcomponent);
begin
inherited Create(aOwner);
MyComThread:= nil;
hCommFile := 0;
hCloseEvent := 0;
Parity:=None;
ComName:=com2;
BaudRates:=9600;
Stopbits:=1;
DataBits:=8;
InPutBuffers:=1024;
OutPutBuffers:=1024;
CommMode:=Evchar;
end;
destructor TComm32.Destroy;{構件析構函數}
begin
if not(csDesigning in ComponentState)then
DeallocateHWnd(hComm32Window);
inherited Destroy;
end;
procedure Register;{構件注冊}
begin
RegisterComponents(’Sample’, [Tcomm32]);
end;
procedure TComm32.OnCommData(Buffer: PChar; BufferLength: Word);
begin
if Assigned(OnCommMsg) then
OnCommMsg( self , Buffer, BufferLength);
end;
{構件端口打開方法}
Function TComm32.comPortOpen : Thandle;
var dcbPort:TDCB;
ComBuff:BOOlean;
StrCom:string;
begin
StrCom:=’Com’+IntToStr(Ord(comName)+1);
hCommFile:=CreateFile(PChar(StrCom),GENERICREAD or GENERICWRITE,0,nil, OPENEXISTING, FILEATTRIBUTENORMAL or FILEFLAGOVERLAPPED ,LongInt(0));
if (hCommFile <> INVALIDHANDLEVALUE) then
begin
if GetCommState(hCommFile, dcbPort) then begin
dcbPort.BaudRate := BaudRate;
dcbPort.ByteSize := DataBits;
dcbPort.Parity :=Ord(parity);
dcbPort.StopBits :=Ord(Stopbit);
dcbPort.Flags := 0;
SetCommState(hCommFile, cbPort);
end;
end else
begin
application.messagebox(’不能打開端口 ’+’請重新設置端口 !’, ’Error’, mbOk + mbDefButton1);
result:=0;
Exit;
end;
ComBuff:=SetupComm(hCommFile ,InBuffersize ,OutBuffersize);
hComm32Window := AllocateHWnd(CommWndProc);
hCloseEvent := CreateEvent( nil, True, False, nil );
if hCloseEvent = 0 then
begin
CloseHandle( hCommFile );
hCommFile := 0;
raise CommsError.Create (’不能創(chuàng)建事件’ )
end;
try
MyComThread:=TMyCommWacth.Create();
except
MyComThread := nil;
CloseHandle( hCloseEvent );
CloseHandle( hCommFile );
hCommFile := 0;
raise ECommsError.Create(’不能建立監(jiān)視線程’)
end;
MyComThread.hCommFile := hCommFile;
MyComThread.hCloseEvent := hCloseEvent;
MyComThread.hComm32Window := hComm32Window;
PurgeComm(hCommFile,PURGETXCLEAR);
PurgeComm(hCommFile,PURGERXCLEAR);
MyComThread.Resume;
result:=hCommFile;
end;
Function TComm32.comPortClose : Boolean;{端口關閉方法}
begin
if hCommFile = 0 then
begin
result:=False;
Exit;
end;
CloseMyComThread;
CloseHandle( hCloseEvent );
CloseHandle(hCommFile );
hCommFile := 0;
result:=True;
end;
{消息窗}
procedure TComm32.CommWndProc( var msg: TMessage );
var comstate,dwerrorcode:Integer;
begin
ClearCommError(hCommfile, dwErrorCode, @ComState);
OnCommData(PChar(msg.LParam), msg.WParam );
LocalFree(hcommfile);
end;
{撤消監(jiān)視線程}
procedure TComm32.CloseMyComThread;
begin
if MyComThread 〈 〉 nil then
begin
SetEvent( hCloseEvent );
PurgeComm( hCommFile, PURGERXABORT + PURGERXCLEAR );
if (WaitForSingleObject(MyComThread.Handle, 10000) = WAITTIMEOUT) then
MyComThread.Terminate;
MyComThread.Free;
MyComThread := nil
end
end;
end.