前言
随着计算机软硬件的是益发展,基于Windows95及NT平台的软件越来越多,在智能化电子仪表及计算机控制系统中都涉及到计算机与智能仪或计算机之间进行信息交换,而串行通信是计算机之间以及计算机与单片机等数字化仪器通信的一种重要手段,是实现工业监控的一种主要方式,由于它高效可靠,价格便宜,遵循统一的标准,因而得到广泛应用。随着计算机技术不断发展,编程手段也不断提高,如Visual Basic 、Delphi 、Visual C++ 以及 C++ Builder等采用面向对象构件的方法,使得编写Windows下的应用程序变得迅速和容易 ,其中Delphi功能强大,代码效率高,深受软件开发人员睛睐, 但Delphi同Visual C++ 以及 C++ Builder一样均未提供通信构件,为此用Delphi开发通信应用软件时就得应用API函数或Visual Basic的通信构件,API函数对一般开发人员有一定难度而且不太方便 ,而用VB 的通信构件开发的应用程序需在WINDOWS95或NT中安装并注删相应的动态库才能运行,这对应用用户来说很不方便。为此本文介绍用API函数和多线程编程技术在Delphi3.0下设计出自已的通信构件,并提供了全部源程序,利用Delphi安装新构件方法将其安装到自已的编译系统中,就可以十分方便地开发出通信程序,该构件在智能超声液体成份分析仪及集散式网络测控热处理系统的被成功地应用。从中可以看出利用Delphi编制构件不断丰富Delphi的内容的方法。
1 串行通信构件设计思想
一般基于DOS编程的程序员在编写串行通信时,往往是编写一个中断服务程序,一旦串行口有数据它就会向CPU发出中断请求,CPU在响应该中断后会执行串口的中断服务程序,从而完成预定的任务。在Windows操作系统下,由于Windows禁止应用程序直接和硬件打交道,所以程序员只能使用Windows提供的标准函数编程。虽然由于无需对硬件编程对有关硬件调试方便,但Windows本身远比DOS复杂,所以对这些标准函数和它们携带参数的理解和使用也远比DOS困难,在Windows3.X中,当一个通信设备被打开并允许传送WM-COMMNOTIFY消息时,只要该通信设备收到数据,操作系统就会在消息队列中置入WM-COMMNOTIFY消息,应用程序可以通过截获操作系统发出的WM-COMMNOTIFY消息来对已打开的通信设备进行操作。
在Windows95与NT中,修改了Windows3.X对串行口操作的标准函数,进行了更统一的规范化,取消了WM-COMMNOTIFY消息以及OpenComm,CloseComm,ReadComm,WriteComm,FlushComm等函数,对待串行口操作如同文件一样,其串行设备的打开和关闭操作使用与文件打开与关闭操作相同的函数,如CreatFile,CloseFile,ReadFile,WriteFile,PurgeComm等,由于Windows95与NT中允许用户定义大小的读写缓冲区,这样数据丢失可能性很小,同时使得读写速度很快。在Windows95与NT中支持多线程编程技术,而Delphi3.0为多线程编程和编制构件提供了支持,这样就可以编制串行通信构件了,即建立新的“.pak”文件就行了。
考虑到篇幅,在这个构件中只提供必要且够一般常用的几个属性和当输入缓冲有数据时而产生的事件,这些属性中可视属性为波特率、数据位、效验位、停止位、串行口名、输入缓冲大小(即读缓冲)、输出缓冲大小(即写缓冲)、触发事件方式;运行属性有串口设备句柄、消息窗句柄、事件句柄;运行中的方法有端口打开和端口关闭函数。
构件的设计思想是:可视属性中的数据位、效验位、停止位、触发事件方式用梅举类型定义,编程人员将方便地选择所需的值就行了,可视属性中波特率、串行口名、输入缓冲大小、输出缓冲大小由编程人员输入设定;触发事件方式有每收一字符触发和一队列收到后触发。在构件的创建过程中将可视属性赋缺省值,当程序运行构件的端口打开函数(ComPortOpen )时,将串口按构件可视属性设定值进行端口初始化及创建监视串口线程并返回端口句柄(hCommFile);监视线程的作用是,按触发事件方式监视串口,当串口有数据时就向窗函数发出自定义的WM_COMMNOTIFY消息,窗函数收到WM_COMMNOTIFY消息后触发OnComm事件;当执行端口关闭函数(comPortClose)时,该函数关闭端口并撤消监视线程。程序流程图为图1。
图 1
2 应用说明
当执行ComPortOpen函数(即方法)时,用CreatFile()打开串行口,此时fdwShareMode,参数必须是零,打开独占访问的资源。FdwCreate参数必须是指定的OPEN_EXISTING标志,hTemplateFile参数必须是Nil,用GetCommState设置通信参数,用CreateEvent()创建事件对象,用AllocateHWnd()得到窗口数构柄;利用Delphi3.0创建多线工具建立一个监视线程的对象TmyCommWacth;在监视线程中用ResetEVent()设置事件句柄,用WaitForSingleObject()指定对象处于信号或超时状态时返回,用PostMessage()向指定窗发送消息; 窗函数收到消息后用ClearCommError()清除错误,用自定的过程 OnCommData(PChar(msg.LParam), msg.WParam )触发事件OnComm,当执行端口关闭函数comPortClose时 ,用CloseMyComThread撤消监视线程,用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;{触发事件对像}
Type{创建监视线程类}
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{创建构件对象}
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();{监视线程创建}
begin
inherited Create(False);
FreeOnTerminate:=True;
end;
{监视线程执行}
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; {监视线程结束}
{建立通信构件}
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 (’不能创建事件’ )
end;
try
MyComThread:=TMyCommWacth.Create();
except
MyComThread := nil;
CloseHandle( hCloseEvent );
CloseHandle( hCommFile );
hCommFile := 0;
raise ECommsError.Create(’不能建立监视线程’)
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;
{撤消监视线程}
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.