朋友给我的资料,供大家参考,大家帮帮我,看看我那个问题该如何解决?
-------------------------------------------------
1、串口控制的方法:
a、打开串口的方法:
打开串口的方法与你说的一样。用 CreateFile 函数定议如下:
HANDLE CreateFile(
PChar lpszName, //指定要打开的串口的逻辑名:用字串表示:如 'COM1','COM2'
DWORD fdwAcess, //访问类型,读: GENERIC_READ 写:GENERIC_WRITE
DWORD fdwShareMode, //共享模式;必须为0。串口不能共享
LPSECURITY_ATTRIBUTES lpsa, //指向一个安全属性结构 设为Nil 。
DWORD fdwCreate, //创建方法:串口操作为:OPEN_EXISTING
DWROD fdwAttrsAndFlags, //串口操作的属性
HANDLE hTemplateFile //模板文件的句柄
);
注: LPSECURITY_ATTRIBUTES lpsa 这个参数的功能我在所有的参考书上都没有找到具体的
解释。一般都设为nil
★★★注意:fdwAttrsAndFlags 是一个比较容易被忽略的参数,那就是 FILE_FLAG_OVERLAPPED
在 Win98 下可以不要。在 2000/XP 下,如果要写成多线程的,那必须加上这个属性。
这在很多参考书上都没有的,是我在实际开发时,总结出来的。
加上 FILE_FLAG_OVERLAPPED 就是说串口以异步方式操作。异步是相对于同步的。比如:
你向串口写一个字串,一直等到它写完你才返回。这就是同步。而异步就不同,异步时:
你写了数据马上返回。而这时数据可能还没有发送出去。你可以在以后查询。
b、分配缓冲区
用函数 SetupComm, 定义如下:
BOOL SetupComm(
Handle hFile, // 串口句柄
DWORD dwInQueue, // 输入缓冲区
DWORD dwOutQueue // 输出缓冲区
);
c、串口参数配置
用函数 GetCommState 获取当前串口的配置
用函数 SetCommState 设置当前串口的配置
函数定义:
BOOL GetCommState(
HANDLE hFile, // 句柄
LPDCB lpDCB // 指向一个DCB 结构
);
BOOL SetCommState(
HANDLE hFile, // 句柄
LPDCB lpDCB // 指向一个DCB 结构
);
DCB 的定义参见 Delphi.
★★★注意:Delphi 里的 DCB 定义与微软文档上的定义不太一样,要少了很多参数。我
也不太明白是什么原因。我曾经在 DelphiBBS 上问过但没人回答。
d、读串口操作:
用 ReadFile 函数。也可以用 ReadFileEx
BOOL ReadFile(
HANDLE hFile, //句柄
LPVOID lpBuffer, //指向一个缓冲区
DWORD nNumberOfBytesToRead, //要读取的字节数
LPDWORD lpNumberOfBytesRead,//实际读取的字节数
LPOVERLAPPED lpOverLapped //一个_OVERLAPPED 结构,用于异步操作。
);
★★★ lpBuffer 指向的缓冲区要预先分配。这个参数在 Delphi 的定义很奇怪 var Buffer
没有指定类型。开始在用的时候有一个奇怪的情况,如果定义一个静态的数组,就
没有问题,如定义一个动态数组就不行。我在 DelphiBBS上问也没人回答我。最后我
在Delphi 的源码里找到一点思路:最后写成 Pointer(@Bufer[0])^ 就通过了。一个
很奇怪的格式。这点与C不同。
★★★ lpOverLapped 如果在打开端口时没有用 FILE_FLAG_OVERLAPPED 参数就要用 nil 。
如果指定了 FILE_FLAG_OVERLAPPED 。就必须有 LpOverLapped 参数。方法见后。
如指定了 FILE_FLAG_OVERLAPPED ,返回值没用。如没有其它错误用 GetLastError
得到的错误值为: ERROR_IO_PENDING 或 ERROR_IO_INCOMPLETE。
e、写串口的方法;
用 WriteFile 或 WriteFileEx 。
BOOL WriteFile(
HANDLE hFile, // 句柄
LPVOID lpBuffer, // 指向缓冲区
DWORD nNumberOfBytesToWrite, // 想要写入的字节数
LPDWORD lpNumberOfBytesWritten,// 实际写入的字节数
LPOVERLAPPED lpOverlapped // 一个 _OVERLAPPED 结构
);
★★★ lpBuffer 的使用方法与 ReadFile 一样。
f、异步操作方法:
OVERLAPPED 在 Delphi 中的定义如下:
_OVERLAPPED = record
Internal: DWORD;
InternalHigh: DWORD;
Offset: DWORD;
OffsetHigh: DWORD;
hEvent: THandle;
end;
在实际编程时只有:hEvent 要用到。
hEvent 是一个信号量。信号量是一个 Windows 的类型。它有两个状态。一为有信号状态,
一是无信号状态,类似于 BOOL 值,但它于 BOOL 不同的是,这是互斥的。
使用前我先用 CreateEvent 创建,使用完后要用 CloseHandle 释放它。
用 ResetEvent 设为无信号 用 SetEvent 设为有信号
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 指向一个安全属性 一般为 nil
BOOL bManualReset, // 是否手动重置
BOOL bInitialState, // 初始状态
LPCTSTR lpName // 对象名称 一般为 nil
);
bManualReset ;True : 在手动用 ResetEvent 设置为 无信号状态
false : 不用手动重置,它会自动设置为 无信号状态
bInitialState :最初状态 True 为有信号 false 为无信号。
BOOL ResetEvent(
HANDLE hEvent // handle to event
);
BOOL SetEvent(
HANDLE hEvent // handle to event
);
函数 GetOverlappedResult 返回异步操作结果。
BOOL GetOverlappedResult(
HANDLE hFile, // 句柄
LPOVERLAPPED lpOverlapped, // OVERLAPPED 结构
LPDWORD lpNumberOfBytesTransferred, // 写入或读取的数据
BOOL bWait
);
★★★ bWait : True 直到异步操作完成才返回。
false 函数返回 false ,GetLastError 返回 ERROR_IO_INCOMPLETE。
注:GetOverlappedResult (bWaite = true) 的实质是等到 lpOverlapped.hEvent 变为
有信号时,(异步IO完成后,由系统设置)。函数返回。否则无限等待。
当然这里也可以用 WaitFor函数集 等待 lpOverlapped.hEvent 变为有信号状态。
例子1:
var
m_ovWrite:TOverLapped;
Buf:array of char;
...
m_ovWrite.hEvent := CreateEvent(nil,false,false,nil); // 初始化无信号
...
//ReSetEvent(m_ovWrite.hEvent); //可删去因为初始化无信号
bRtn := WriteFile(m_hCom,
(@Buf[0])^, // <--- Notic This
len,
n,
@m_ovWrite);
ErrCode := GetLastError;
if (ErrCode = ERROR_IO_PENDING) or (ErrCode =ERROR_IO_INCOMPLETE) then // IO 未完成
bRtn := GetOverlappedResult(m_hCom, // 等待 IO 完成
m_ovWrite,
ovWrite, // 实际上写入的字节数
true);
CloseHandle(m_ovWrite.hEvent); // 关闭信号句柄
if not bRtn then
begin
Raise Exception.Create('写端口时出现错误!'+SysErrorMessage(GetLastError));
Exit;
end;
...
例子2:
var
m_ovRead : TOVERRLAPPED;
Buffer:array of char;
...
m_ovRead.hEvent := CreateEvent(nil,false,false,nil);
//ResetEvent(m_ovRead.hEvent );
bRtn := ReadFile(m_hCom,
Pointer(Buffer[0])^,
iCount,
nReaded,
@m_ovRead);
ErrCode := GetLastError;
if (ErrCode = ERROR_IO_PENDING ) or (ErrCode = ERROR_IO_INCOMPLETE) then
bRtn := GetOverlappedResult(m_hCom,
m_ovRead,
ovRead,
true);
CloseHandle(m_ovRead.hEvent);
if not bRtn then
begin
Raise Exception.Create('ReadFile Error!'+ SysErrorMessage(GetLastError));
Exit;
end;
上面的例子是异步时的。同步时与你写的一样。
g、操作通信事件
可以用 SetCommMask 来指定要监视的通信事件。
BOOL SetCommMask(
HANDLE hFile,
DWORD dwEvtMask
);
dwEvtMask 是常数的组合;常数的定义与含义如下:
EV_BREAK 检测到输入的终止
EV_CTS CTS 信号改变
EV_DSR DSR 信号改变
EV_ERR 线路错误
EV_RING 检测到振铃
EV_RLSD RLSD 信号改变
EV_RXCHAE 接收到一个字符并放入缓冲区
EV_RXFLAG 接到事件字符DCB 中的EvtChar 。
EV_TXEMPTY 发送缓冲区空
例:
var
EvtMask
WORD;
....
EvtMask := EV_CTS or EV_DSR or EV_ERR or EV_EXCHAE or EV_RING;
SetCommMask(m_hComm,EvtMask);
....
h、★★★ 监视通信事件 ★★★
这是关键的地方,函数 WaitCommEvent 的使用。
定义:
BOOL WaitCommEvent(
HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped //异步操作时使用。
);
例:
...
bRtn:=WaitCommEvent(m_hCom,EvtMask,nil);
if (EvtMask and EV_RXCHAR) >0 then
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;
if (EvtMask and EV_CTS) >0 then
begin
if Assigned(FOnCtsChange) then FOnCtsChange(Self);
end;
....
★★★这个函数不太好用,有很多串口控件都没有用这个函数。但用好了就很方便了。
这个函数难用,就是因为它是一个阻塞函数。就是你一使用这个函数就要一直等到有事件
发生时,它才返回。如果用单线程的程序就不行,因为一运行到这里,它就会像死了一样。
所以就必须写成多线程的。
//-------------------------------------------------------------------------
2、类的框架
把上面的看了之后应该想一下如何写类了。要写一个多线程的。在 Delphi 里可以用
TThread 继承。TThread 是一个抽象类。就是扩展它的 Execute 函数。Execute 就是
一线程体。类的框架如下:
你可以先写一个同步的类,也就是不用 OVERLAPPED,在 Win98 下可以正常工作了,
再加入异步,在2000/XP 测试,如通过,则大功告成了。
(以下是以同步I/O讲解:)
类主要成员说明:
Private
m_PortOpen :Boolean; //表示端口是否处于打开状态,True:打开,False:关闭
m_Baud :Longword; //波特率 缺省值:9600
m_Parity:byte; //是否奇偶校检 0~4 分别为 No,ODD,Even,Mark,Space
m_StopBits :byte; //停止位位数 取值:0、1、2 ,分别为 1,1.5,2
m_hCom:longword; //端口句柄
m_InBufferSize:longword; //输入缓冲大小:
m_OutBufferSize:longword;//输出缓冲大小:
m_ErrorChar:Char; //校检错时的填充字符
m_ByteSize: Byte; //数据据位数 5、6、7、8
m_CommDcb:TDCB; //端口DCB结构
m_TimeOuts:TCOMMTIMEOUTS;//超时结构
m_ErrorNumber
WORD; //出错时的错误号
FOnReceive:TNotifyEvent; //接收字符时引用的函数句柄 WaitCommEvent
FOnErr:TNotifyEvent; //发生出错事件时函数句柄 WaitCommEvent
FOnCtsChange:TNotifyEvent;//当CTS状态变化时引用的函数句柄
FOnDsrChange:TNotifyEvent;//当DSR状态变化时引用的函数句柄
FOnRing:TNotifyEvent; //当收到响铃消息时引用的函数句柄
FOnCDChange:TNotifyEvent; //当CD状态变化时引用的函数句柄
FOnTxEmpty:TNotifyEvent; //当发送缓冲区空时引用函数句柄
Public
property CommPort:longint read m_CommPort Write SetCommPort;
//设置端口号
property PortOpen:Boolean read m_PortOpen write SetPortOpen;
//读取端口状态,或打开/关闭端口
property Baud:longword read m_Baud write SetBaud;
//设置/读取波特率,端口打开后只读
property ByteSize:byte read m_ByteSize write SetByteSize;
//设置/读取数据位数,端口打开后只读
property Parity:byte read m_Parity write SetParity;
//校验方法 。端口打开后只读
property StopBits:Byte read m_StopBits write SetStop;
//设置停止位
property InBufferSize:longword read m_InBufferSize write m_InBufferSize;
//输入缓冲大小,端口打开后只读
property OutBufferSize:Longword read m_OutBufferSize Write m_OutBufferSize;
//输出缓冲大注,端口打开后只读
property OnReceive:TNotifyEvent write FOnReceive;
//函数句柄
property OnCDChange:TNotifyEvent write FOnCDChange;
property OnCtsChange:TNotifyEvent write FOnCtsChange;
property OnDsrChange:TNotifyEvent write FOnDsrChange;
property OnErr:TNotifyEvent write FOnErr;
property OnRing:TNotifyEvent write FOnRing;
property OnTxEmpty:TNotifyEvent write FOnTxEmpty;
★function Read(Buffer
ointer;iCount:longint):longint;
//读取
★procedure Write(Buf
Char;l:longint=0);
//输出
★procedure Open;
★procedure Close;
procedure Execute;override;
constructor Create;
//类的创建过程
destructor Destroy;override;
//类的析构函数
类主要功能实现的方法:
1、先继承 TThread 类的 Execute 方法。在 TThread 中 Execute 是一个抽象过程,
定义为 virtual ; abstract 是个抽象虚过程,需要在子类中实现的。
2、从 TThread 中继承到的 Execute 实际上是线程的主体部份。需要把 WaitCommEvent
放到线程体中,线程体是一个循环结构。直到线程被消毁。但如果端口没有打开时,
则不能执行 WaitCommEvent 。结构如下:
while not Terminated do
begin
if not m_PortOpen then countinue; //循环等待端口被打开
WaitCommEvent(m_hCom,EvtMask,nil); //等待事件发生
if (EvtMask and EV_RXCHAR) > 0 then //是不是EV_RXCHAR事件
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;
.
.
.
end; //end while
3、在类创建时挂起线程, 因为在线程体Execute中,如果没有打开端口不能执行 WaitCommEvent 。
部份代码如下:
m_PortOpen := false;
inherited Create(true); //开始执行线程,而不是Suspend
.
.
.
4、在打开端口的过程中先用CreateFile打开端口,(我使用同步方式,没有用Overlapped)
再用SetCommState设定端口工作方式,再用SetCommTimeouts设定超时结构,主要是
读超时的设置。在用SetCommMask设置事件掩码。最后才把m_PortOpen 设为True;
这时线程将执行 WaitCommEvent 等待事件发生。
5、在关闭端口时,先设置 m_PortOpen :=False 再用 SetCommMask(m_hCom,0)指令让线程
从 WaitCommEvent 处返回。此处很重要。这样线程才能退出 WaitCommEvent 。不然线
程很容易丢失。
6、在类被消毁时要处理的事:也就是写在 Destroy 过程中的代码。
先判断端口是否是开启状态。如果是。先消毁线程,再用 SetCommMask(m_hCom,0);
这句很重要不然线程永远停在 WaitCommEvent 处。如果端口是关闭了的。直接销毁
线程就可以了。
代码如下:
Terminate;
if m_PortOpen then
begin
SetCommMask(m_hCom,0); //让 线程中的 WaitCommEvent 返回
CloseHandle(m_hCom);
end ;
inherited;
主要代码如下:
//★★★★★★创建时:
constructor TRedCom.Create;
begin
m_PortOpen := false;
inherited Create(true); //挂起线程
m_CommPort := 1;
....
end;
//线程体 ★★★★★★ ( 同步I/O )
procedure TRedCom.Execute;
var
EvtMask
WORD;
n
WORD;
bRtn:Bool;
ErrCode
WORD;
begin
while (not Terminated ) do //线程未被销毁
begin
if not m_PortOpen then continue;
EvtMask :=0;
bRtn:=WaitCommEvent(m_hCom,EvtMask,nil);
if not bRtn then //出现错误
begin
Raise Exception.Create(SysErrorMessage(GetLastError));
end;
if (EvtMask and EV_RXCHAR) >0 then
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;
if (EvtMask and EV_CTS) >0 then
begin
if Assigned(FOnCtsChange) then FOnCtsChange(Self);
end;
.....
end; // end while
end;
//★★★★★★类销毁时
destructor TRedCom.Destroy;
begin
if Suspended = true then //如果线程挂起时,唤醒它
Resume;
Terminate; // 销毁线程
if m_PortOpen then // 如果端口已打开
begin
SetCommMask(m_hCom,0); // 让线程从 WaitCommEvent 处返回
CloseHandle(m_hCom); // 关闭端口
end ;
WaitFor; // 等待线程结束。这句很重要,不然程序运行不稳定
inherited;
end;
// ★★★★★★打开端口 (同步方式)
procedure TRedCom.Open;
var
sCom:String;
bResult :Boolean;
EvtMask:longword;
begin
if m_PortOpen then Exit;
if not ( m_CommPort in [1..4] ) then Exit;
sCom:='COM'+ IntToStr(m_CommPort);
m_hCom := CreateFile(pChar(sCom),
GENERIC_READ or GENERIC_WRITE ,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL ,
0);
if m_hCom = INVALID_HANDLE_VALUE then
begin
Raise Exception.Create('打开端口时出错!'+
SysErrorMessage(GetLastError));
m_PortOpen := false;
Exit;
end;
SetupParams; //设置参数 自写函数
EvtMask := EvtMask or EV_RXCHAR
or EV_CTS or EV_DSR
or EV_ERR or EV_RING
or EV_RLSD or EV_TXEMPTY;
SetCommMask(m_hCom,EvtMask);
m_PortOpen := true;
Resume; // 唤醒线程,进行监听
end;
//关闭端口
procedure TRedCom.Close;
var
bResult :Bool;
begin
if m_PortOpen = false then Exit;
m_PortOpen := false;
SetCommMask(m_hCom,0);
Suspend; // 挂起线程
CloseHandle(m_hCom);
end;
//读取端口数据
function TRedCom.Read(Buffer
ointer; iCount: Integer): longint;
var
nReaded:longword;
bRtn:bool;
ovRead
WORD;
ErrCode
WORD;
begin
if not m_PortOpen then
begin
Result := 0;
Exit;
end;
bRtn := ReadFile(m_hCom,
Buffer^, // 注意这里的格式
iCount,
nReaded,
nil);
if not bRtn then
begin
Raise Exception.Create('ReadFile Error!'+ SysErrorMessage(GetLastError));
Exit;
end;
Result:= nReaded;
end;
//写端口数据
procedure TRedCom.OutPort(Buf: PChar;l:longint=0);
var
len:Longword;
bRtn:Boolean;
n
WORD;
ovWrite
WORD;
ErrCode
WORD;
begin
if l = 0 then
begin
len := length(buf);
if len = 0 then exit;
end
else begin
len := l;
end;
bRtn := WriteFile(m_hCom,
(@Buf[0])^,
len,
n,
nil);
if not bRtn then
begin
Raise Exception.Create('写端口时出现错误!'+SysErrorMessage(GetLastError));
Exit;
end;
end;
现在,这个类的框架都在这里了,你把它完善一下,就可以了。
还有上次我给你的那个 TComPort 控件是个好东东,多看一下它的源码。会对你有帮助的。
Good Luck!
//--------------------------end of file --------------------------------------