编写delphi串口类

自己动手编写 delphi 的串口类

    --date=2020-03-28

    --group="笔记草稿"

---------

TODO 当前状态=玩具状态 --date=2020-05-24

   1 unit uSerialPort;
   2 
   3 { 串口
   4   ---------
   5   NOTE
   6   从行为上来看, 这个串口类的读写貌似是阻塞的, 所以使用 Overlapped 有必要么,
   7   如果需要非阻塞的行为, 需要怎样组织呢
   8 
   9   没有对 C# 中的 Handshake 作处理, 所有注意到的与 Handshake 有关的地方都按None处理
  10   也有其他地方按默认值处理的, 但忘了有哪些了
  11   目前在端口 Open 状态时, 修改波特率什么的没效果, C# 中这些常用参数看上去是可以修改的
  12   还忽略了许多其他事情, 需要具体对比 C# 才知道还有哪些
  13   仅使用 USB-RS485 转换器测试收发了 10 几个字节,
  14   所以仅能在玩具程序中使用, 要想能够真正的使用还有很长的路
  15   ---------
  16   TODO
  17   关于 GetLastError 返回的结果, 至少做一下说明, 不然单纯的数字根本不知道发生了什么
  18   ---------
  19   Windows API 参考:
  20   https://docs.microsoft.com/zh-cn/windows/win32/devio/communications-resources
  21   https://docs.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-createfilea
  22 
  23   关于 \. 和 \?: https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file
  24   Namespaces 节的 Win32 Device Namespaces
  25 
  26   同步和异步IO
  27   https://docs.microsoft.com/zh-cn/windows/win32/fileio/synchronous-and-asynchronous-i-o
  28 
  29   学习串口工具的编写请参阅:
  30   https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.cs
  31   https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs
  32 }
  33 interface
  34 
  35 uses
  36   System.Generics.Collections,
  37   System.SysUtils,
  38   WinApi.Windows;
  39 
  40 type
  41   TSerialPort = class
  42   // 串口
  43     // @Section private type
  44     private
  45       type TEventLoop = class
  46         // 处理 WaitCommEvent
  47         // TODO 这个事件循环现在什么都没做, 有空再处理
  48         private
  49           FHandle : THandle;
  50         public
  51           constructor Create(AHandle : THandle);
  52           destructor Destroy; override;
  53 
  54           procedure Start();
  55           procedure Stop();
  56       end;
  57 
  58     // @Section public type
  59 
  60     // @Section public const
  61 
  62     // @Section private
  63     private
  64       FCommHandle : THandle;   // 串口句柄
  65 
  66       FCommName : String;    // COM口名称 COM1
  67       FBaudRate : Integer;   // 波特率
  68       FParity   : Integer;   // 奇偶校验
  69       FDataBits : Integer;   // 数据位
  70       FStopBit  : Integer;   // 停止位
  71 
  72       // 超时, 毫秒, 取值范围[0,MAX Integer], 如果设置为 0 表示不使用超时
  73       FReadTimeOut : Integer;
  74       FWriteTimeOut : Integer;
  75 
  76       // 读写缓冲区大小
  77       FReadBufferSize : Integer;
  78       FWriteBufferSize : Integer;
  79 
  80       FParityReplace : AnsiChar;
  81 
  82       // TODO 考虑是否需要这些结构
  83       // DCB TimeOuts 等结构
  84       // FDcb : TDCB;
  85       // FTimeouts : TCommTimeouts;
  86 
  87       FEventLoop : TEventLoop;
  88 
  89       // for property
  90       procedure SetPropCommName(const ACommName : String);
  91       procedure SetPropBaudRate(const ABaudRate : Integer);
  92       procedure SetPropParity(const AParity : Integer);
  93       procedure SetPropDataBits(const ADataBits : Integer);
  94       procedure SetPropStopBit(const AStopBit : Integer);
  95 
  96       procedure SetPropReadTimeOut(const ATimeOut : Integer);
  97       procedure SetPropWriteTimeOut(const ATimeOut : Integer);
  98 
  99       procedure SetPropReadBufferSize(const ASize : Integer);
 100       procedure SetPropWriteBufferSize(const ASize : Integer);
 101 
 102       // about comm config
 103       procedure ConfigEvents();
 104       procedure ConfigDCB(ACommProp : TCommProp);
 105       procedure ConfigTimeouts();
 106       procedure ConfigBufferSizes();
 107 
 108       // about DCB.Flags
 109       function  GetDcbFlag(const ADcb : TDCB; AWhichFlag : Integer) : Integer;
 110       procedure SetDcbFlag(var ADcb : TDCB; AWhichFlag : Integer; ASetting : Integer);
 111 
 112       procedure SetHandleInvalid();
 113       function  IsInvalidHandle(const AHandle : THandle) : Boolean;
 114 
 115       function CheckAndGetComNumber(const AComName : String) : Integer;
 116       procedure CheckReadWriteArguments(const AArr : TArray<Byte>; const AOffset, ACount : Integer);
 117 
 118     // @Section public
 119     public
 120       constructor Create(); overload;
 121       constructor Create(const AComName : String); overload;
 122       constructor Create(const AComName : String;
 123               const ABaudRate, AParity, ADataBits, AStopBit : Integer); overload;
 124 
 125       destructor Destroy(); override;
 126 
 127       function IsOpen() : Boolean;
 128 
 129       procedure Open();
 130       procedure Close();
 131 
 132       function  ReadBytes(ABuff : TArray<Byte>; const AOffset, ACount : Integer) : Integer;
 133       procedure WriteBytes(ABuff : TArray<Byte>; const AOffset, ACount : Integer);
 134 
 135       property PortName : String read FCommName write SetPropCommName;
 136       property BaudRate : Integer read FBaudRate write SetPropBaudRate;
 137       property Parity : Integer read FParity write SetPropParity;
 138       property DataBits : Integer read FDataBits write SetPropDataBits;
 139       property StopBit : Integer read FStopBit write SetPropStopBit;
 140 
 141       property ReadTimeOut : Integer read FReadTimeOut write SetPropReadTimeOut;
 142       property WriteTimeOut : Integer read FWriteTimeOut write SetPropWriteTimeOut;
 143 
 144       property ReadBufferSize : Integer read FReadBufferSize write SetPropReadBufferSize;
 145       property WriteBufferSize : Integer read FWriteBufferSize write SetPropWriteBufferSize;
 146 
 147       property ParityReplace : AnsiChar read FParityReplace write FParityReplace;
 148 
 149     // @Section public class
 150     public
 151       class function GetPortNames() : TArray<String>;
 152   end;
 153 
 154 
 155   // 串口异常
 156   ESerialPort = class(Exception);
 157 
 158   // 串口读或写超时
 159   ESerialPortTimeOut = class(ESerialPort);
 160 
 161 
 162   // 别名, 辅助用
 163   TBaudRateTool = record
 164     public const
 165       // aliases
 166       BR_110  = CBR_110;
 167       BR_300  = CBR_300;
 168       BR_600  = CBR_600;
 169       BR_1200 = CBR_1200;
 170       BR_2400 = CBR_2400;
 171       BR_4800 = CBR_4800;
 172       BR_9600 = CBR_9600;
 173       BR_14400  = CBR_14400;
 174       BR_19200  = CBR_19200;
 175       BR_38400  = CBR_38400;
 176       BR_56000  = CBR_56000;
 177       BR_57600  = CBR_57600;
 178       BR_115200 = CBR_115200;
 179       BR_128000 = CBR_128000;
 180       BR_256000 = CBR_256000;
 181 
 182       SupportedValues : array[0..14] of Integer
 183                       = (BR_110,   BR_300,   BR_600,    BR_1200,   BR_2400,
 184                          BR_4800,  BR_9600,  BR_14400,  BR_19200,  BR_38400,
 185                          BR_56000, BR_57600, BR_115200, BR_128000, BR_256000);
 186     public
 187       class function IsSupportedBaudRate(const ABaudRate : Integer) : Boolean; static;
 188   end;
 189 
 190   TParityTool = record
 191     public const
 192       // aliases
 193       None = NOPARITY;   // 无校验
 194       Odd  = ODDPARITY;  // 奇校验
 195       Even = EVENPARITY; // 偶校验
 196 
 197       SupportedValues : array[0..2] of Integer = (None, Odd, Even);
 198     public
 199       class function IsSupportedParity(const AParity : Integer) : Boolean; static;
 200   end;
 201 
 202   TDataBitsTool = record
 203     public const
 204       SupportedValues : array[0..3] of Integer = (5, 6, 7, 8);
 205     public
 206       class function IsSupportedDataBits(const ADataBits : Integer) : Boolean; static;
 207   end;
 208 
 209   TStopBitTool = record
 210     public const
 211       // aliases
 212       One  = ONESTOPBIT;    // 1
 213       One5 = ONE5STOPBITS;  // 1.5
 214       Two  = TWOSTOPBITS;   // 2
 215 
 216       SupportedValues : array[0..2] of Integer = (One, One5, Two);
 217     public
 218       class function IsSupportedStopBit(const AStopBit : Integer) : Boolean; static;
 219   end;
 220 
 221 
 222 implementation
 223 
 224 uses
 225   System.Classes,
 226   System.Math,
 227   System.Win.Registry;
 228 
 229 
 230 
 231 // --- types from C# ---
 232 
 233 type
 234   DCBFlags = class
 235   // 没仔细看, 想来应该是偏移量
 236   // --see-also=https://github.com/dotnet/runtime/blob/master/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DCB.cs
 237     public
 238       const FBINARY      = 0;
 239       const FPARITY      = 1;
 240       const FOUTXCTSFLOW = 2;
 241       const FOUTXDSRFLOW = 3;
 242       const FDTRCONTROL  = 4;
 243       const FDSRSENSITIVITY = 6;
 244       const FOUTX = 8;
 245       const FINX  = 9;
 246       const FERRORCHAR = 10;
 247       const FNULL = 11;
 248       const FRTSCONTROL = 12;
 249       const FABORTONOERROR = 14;
 250       const FDUMMY2 = 15;
 251   end;
 252 
 253   DCBDTRFlowControl = class
 254     public
 255       const DTR_CONTROL_DISABLE = $00;
 256       const DTR_CONTROL_ENABLE  = $01;
 257   end;
 258 
 259   DCBRTSFlowControl = class
 260     public
 261       const RTS_CONTROL_DISABLE   = $00;
 262       const RTS_CONTROL_ENABLE    = $01;
 263       const RTS_CONTROL_HANDSHAKE = $02;
 264       const RTS_CONTROL_TOGGLE    = $03;
 265   end;
 266 
 267   TDCBTool = class
 268     public
 269       const EOFCHAR = AnsiChar(26);
 270 
 271       const DEFAULTXONCHAR  = AnsiChar(17);
 272       const DEFAULTXOFFCHAR = AnsiChar(19);
 273   end;
 274 
 275 
 276 
 277 // --- TSerialPort ---
 278 
 279 // --- class functions
 280 
 281 class function TSerialPort.GetPortNames() : TArray<String>;
 282 // 获取当前计算机的串行端口名的数组
 283 var
 284   LRegistry : TRegistry;
 285   LValNames : TStrings;  // 注册表键下值的名称
 286   LIndex : Integer;
 287 begin
 288   LRegistry := nil;
 289   LValNames := nil;
 290   try
 291     LValNames := TStringList.Create();
 292     LRegistry := TRegistry.Create();
 293 
 294     LRegistry.RootKey := HKEY_LOCAL_MACHINE;
 295     if not LRegistry.OpenKeyReadOnly('HARDWAREDEVICEMAPSERIALCOMM') then
 296     begin
 297       Result := nil;
 298       Exit;
 299     end;
 300 
 301     LRegistry.GetValueNames(LValNames);
 302 
 303     SetLength(Result, LValNames.Count);
 304 
 305     for LIndex := 0 to (LValNames.Count - 1) do begin
 306       Result[LIndex] := LRegistry.ReadString(LValNames[LIndex]);
 307     end;
 308   finally
 309     FreeAndNil(LRegistry);
 310     FreeAndNil(LValNames);
 311   end;
 312 end;
 313 
 314 
 315 // --- functions
 316 
 317 constructor TSerialPort.Create();
 318 begin
 319   Create('COM1');
 320 end;
 321 
 322 constructor TSerialPort.Create(const AComName: string);
 323 // 默认 9600波特率 无校验 8数据位 1停止位
 324 begin
 325   Create(AComName, CBR_9600, NOPARITY, 8, ONESTOPBIT);
 326 end;
 327 
 328 constructor TSerialPort.Create(const AComName: string; const ABaudRate, AParity, ADataBits, AStopBit: Integer);
 329 const
 330   LDefaultBufferSize = 2048;
 331   LDefaultParityReplace = '?';
 332 begin
 333   inherited Create();
 334 
 335   self.SetHandleInvalid();
 336 
 337   self.FEventLoop := nil;
 338 
 339   self.FReadBufferSize  := LDefaultBufferSize;
 340   self.FWriteBufferSize := LDefaultBufferSize;
 341 
 342   self.FReadTimeOut  := 0;
 343   self.FWriteTimeOut := 0;
 344 
 345   self.FParityReplace := LDefaultParityReplace;
 346 
 347   self.SetPropCommName(AComName);
 348   self.SetPropBaudRate(ABaudRate);
 349   self.SetPropParity(AParity);
 350   self.SetPropDataBits(ADataBits);
 351   self.SetPropStopBit(AStopBit);
 352 end;
 353 
 354 
 355 destructor TSerialPort.Destroy();
 356 begin
 357   if self.IsOpen() then begin
 358     try
 359       self.Close();
 360     except
 361       // 如果执行到了这里, 能做什么呢
 362     end;
 363   end;
 364 
 365   inherited;
 366 end;
 367 
 368 
 369 
 370 function TSerialPort.IsOpen() : Boolean;
 371 // 判断端口是否已被打开
 372 begin
 373   Result := not self.IsInvalidHandle(self.FCommHandle);
 374 end;
 375 
 376 procedure TSerialPort.SetHandleInvalid();
 377 // 将串口句柄设置为无效句柄
 378 begin
 379   self.FCommHandle := INVALID_HANDLE_VALUE;
 380 end;
 381 
 382 function TSerialPort.IsInvalidHandle(const AHandle: NativeUInt) : Boolean;
 383 // 判断串口句柄是否有效
 384 begin
 385   Result := (AHandle = INVALID_HANDLE_VALUE);
 386 end;
 387 
 388 
 389 
 390 procedure TSerialPort.Open();
 391 // 打开端口, 如果有问题会抛出异常
 392 var
 393   LPortNumber : Integer;
 394   LTmpHandle : THandle;
 395   LErrCode  : Cardinal;
 396   LFileType : Integer;
 397   LErrors   : Cardinal;
 398   LCommProp : TCommProp;
 399   // only for function parameter
 400   LPinStatus : Cardinal;
 401   LComStat  : ComStat;
 402 begin
 403   if self.IsOpen() then begin
 404     raise ESerialPort.Create('SerialPort is already open');
 405   end;
 406 
 407   LPortNumber := self.CheckAndGetComNumber(self.FCommName);
 408 
 409   // 创建句柄, 使用 tmpHandle 保存
 410   LTmpHandle := CreateFile(
 411             PChar('\?COM' + Integer.ToString(LPortNumber)),
 412             GENERIC_READ or GENERIC_WRITE,  // 读写访问
 413             0,                              // comm devices must be opened w/exclusive-access
 414             nil,                            // 安全属性 default security attributes
 415             OPEN_EXISTING,                  // comm devices must use OPEN_EXISTING
 416             FILE_FLAG_OVERLAPPED,           // 异步
 417             0);                             // hTemplate must be NULL for comm devices
 418 
 419   if self.IsInvalidHandle(LTmpHandle) then begin
 420     LErrCode := GetLastError();
 421     raise ESerialPort.CreateFmt('Open port failed invalied_handle_value, caused by error %d', [LErrCode]);
 422   end;
 423 
 424   try
 425     LFileType := GetFileType(LTmpHandle);
 426 
 427     // Allowing FILE_TYPE_UNKNOWN for legitimate serial device such as USB to serial adapter device
 428     if ((LFileType <> FILE_TYPE_CHAR) and (LFileType <> FILE_TYPE_UNKNOWN)) then begin
 429       raise ESerialPort.CreateFmt('The given port name (%s) does not resolve to a valid serial port',
 430                                   [self.FCommName]);
 431     end;
 432 
 433     // 把 tmpHandle 的值赋到 字段 FCommHandle 上来,
 434     // 但 tmpHandle 的值不动, 上面抛出异常或下面发生错误时 close tmpHandle
 435     self.FCommHandle := LTmpHandle;
 436 
 437     if   (not(GetCommProperties(LTmpHandle, LCommProp)))
 438        or(not(GetCommModemStatus(LTmpHandle, LPinStatus)))
 439     then begin
 440       // If the portName they have passed in is a FILE_TYPE_CHAR but not a serial port,
 441       // for example "LPT1", this API will fail.  For this reason we handle the error message specially.
 442       LErrCode := GetLastError();
 443       if ((LErrCode = ERROR_INVALID_PARAMETER) or (LErrCode = ERROR_INVALID_HANDLE)) then begin
 444         raise ESerialPort.CreateFmt('The given port name (%s) is invalid. It may be a valid port, but not a serial port.', [self.FCommName]);
 445       end
 446       else begin
 447         // Win32Marshal.GetExceptionForWin32Error(errorCode, string.Empty);
 448         raise ESerialPort.CreateFmt('Open port failed, caused by error %d', [LErrCode]);
 449       end;
 450     end;
 451 
 452     if ((LCommProp.dwMaxBaud <> 0) and (Cardinal(self.BaudRate) > LCommProp.dwMaxBaud)) then begin
 453       raise ESerialPort.CreateFmt('The maximum baud rate for the device is %d.', [LCommProp.dwMaxBaud]);
 454     end;
 455 
 456     self.ConfigDCB(LCommProp);
 457     self.ConfigEvents();
 458     self.ConfigTimeouts();
 459     self.ConfigBufferSizes();
 460 
 461     // TODO process errors
 462     PurgeComm(self.FCommHandle, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR);
 463     ClearCommError(self.FCommHandle, LErrors, @LComStat);
 464 
 465     // 启动事件循环
 466     self.FEventLoop := TSerialPort.TEventLoop.Create(self.FCommHandle);
 467     self.FEventLoop.Start();
 468   except
 469     self.SetHandleInvalid();
 470 
 471     CloseHandle(LTmpHandle);
 472     raise;
 473   end;
 474 end;
 475 
 476 
 477 procedure TSerialPort.Close();
 478 // 关闭串口
 479 var
 480   LTmpHandle : THandle;
 481 begin
 482   if not self.IsOpen() then begin
 483     Exit;
 484   end;
 485 
 486   // 停止事件循环
 487   self.FEventLoop.Stop();
 488   FreeAndNil(self.FEventLoop);
 489 
 490   // 处理串口句柄
 491   LTmpHandle := self.FCommHandle;
 492   self.SetHandleInvalid();
 493 
 494   // TODO  process errors
 495   SetCommMask(LTmpHandle, 0); // 禁止所有事件
 496   EscapeCommFunction(LTmpHandle, CLRDTR); // 清除信号
 497   // 丢弃未完成的内容, 终止所有操作
 498   PurgeComm(LTmpHandle, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR);
 499 
 500   CloseHandle(LTmpHandle);
 501 end;
 502 
 503 
 504 // --- read and write
 505 
 506 function TSerialPort.ReadBytes(ABuff: TArray<Byte>; const AOffset: Integer; const ACount: Integer) : Integer;
 507 // 读操作
 508 // TODO 方法的实现需要仔细检查
 509 var
 510   LReadResult : Boolean;
 511   LLenReaded  : Cardinal;
 512   LLastErr : Cardinal;
 513   LWaitResult : Cardinal;
 514   LReadOv : TOverlapped;
 515 begin
 516   if not self.IsOpen() then begin
 517     raise ESerialPort.Create('The serialPort is closed');
 518   end;
 519 
 520   self.CheckReadWriteArguments(ABuff, AOffset, ACount);
 521 
 522   if (ACount = 0) then begin
 523     Result := 0;
 524     Exit;
 525   end;
 526 
 527   FillChar(LReadOv, SizeOf(LReadOv), 0);
 528   LReadOv.hEvent := CreateEvent(nil, True, False, nil);
 529 
 530   if (LReadOv.hEvent = 0) then begin
 531     LLastErr := GetLastError();
 532     raise ESerialPort.CreateFmt('Create event failed in read bytes, %d', [LLastErr]);
 533   end;
 534 
 535   LReadResult := ReadFile(self.FCommHandle,
 536                           ABuff[AOffset],
 537                           ACount,
 538                           LLenReaded,
 539                           @LReadOv);
 540 
 541   if LReadResult then begin
 542     Result := LLenReaded;
 543     Exit;
 544   end;
 545 
 546   LLastErr := GetLastError();
 547   if not(LLastErr = ERROR_IO_PENDING) then begin
 548     // TODO error description
 549     raise ESerialPort.CreateFmt('Read failed, caused by %d', [LLastErr]);
 550   end;
 551 
 552   // TODO 观察 C# 对这里超时是怎样处理的
 553   LWaitResult := WaitForSingleObject(LReadOv.hEvent, INFINITE);
 554 
 555   // TODO 考虑下面需要做哪些事情
 556   if (LWaitResult = WAIT_OBJECT_0) then begin
 557     if GetOverlappedResult(self.FCommHandle, LReadOv, LLenReaded, False) then begin
 558       Result := LLenReaded;
 559       //Break;
 560     end
 561     else begin
 562       Result := 0;
 563       //Break;
 564     end;
 565   end
 566   else if (LWaitResult = WAIT_TIMEOUT) then begin
 567     CancelIO(self.FCommHandle);
 568 
 569     Result := 0;
 570     // TODO timeout
 571   end
 572   else begin
 573     Result := 0;
 574   end;
 575 end;
 576 
 577 procedure TSerialPort.WriteBytes(ABuff: TArray<System.Byte>; const AOffset: Integer; const ACount: Integer);
 578 // 写操作
 579 // TODO 方法的实现需要仔细检查,
 580 // 当前的实现不能保证把所有的数据都发送出去, 所以这里还需要更多处理
 581 var
 582   LWriteResult : Boolean;
 583   LWriteOv : TOverlapped;
 584   LLenSent : Cardinal;
 585   LWriteErr   : Cardinal;
 586   LWaitResult : Cardinal;
 587 begin
 588   if not self.IsOpen() then begin
 589     raise ESerialPort.Create('The serialPort is closed');
 590   end;
 591 
 592   self.CheckReadWriteArguments(ABuff, AOffset, ACount);
 593 
 594   FillChar(LWriteOv, SizeOf(LWriteOv), 0);
 595 
 596   LWriteOv.hEvent := CreateEvent(nil, True, False, nil);
 597 
 598   if (LWriteOv.hEvent = 0) then begin
 599     raise ESerialPort.CreateFmt('Write failed, cuased by create event error %d.', [GetLastError()]);
 600   end;
 601 
 602   try
 603     LWriteResult := WriteFile( self.FCommHandle,
 604                                ABuff[AOffSet],
 605                                ACount,
 606                                LLenSent,
 607                                @LWriteOv);
 608 
 609     if LWriteResult then begin
 610       Exit;  // 发送完, 退出
 611     end;
 612 
 613     LWriteErr := GetLastError();
 614     if (LWriteErr <> ERROR_IO_PENDING) then begin
 615       // TODO error description
 616       raise ESerialPort.CreateFmt('Write failed, error %d.', [LWriteErr]);
 617     end;
 618 
 619     // TODO configure wait timeout
 620     LWaitResult := WaitForSingleObject(LWriteOv.hEvent, INFINITE);
 621 
 622     if (LWaitResult = WAIT_OBJECT_0) then begin
 623       if GetOverlappedResult(self.FCommHandle, LWriteOv, LLenSent, False) then begin
 624         Exit;
 625       end
 626       else begin
 627         // TODO type an exception
 628         raise ESerialPort.CreateFmt('Write failed, error %d.', [GetLastError()]);
 629       end;
 630     end
 631     else if (LWaitResult = WAIT_TIMEOUT) then begin
 632       // TODO timeout
 633       raise ESerialPort.Create('Write failed, timeout.');
 634     end
 635     else begin
 636       raise ESerialPort.CreateFmt('Write failed, Wait result %d.', [LWaitResult]);
 637     end;
 638     // write finished
 639   finally
 640     CloseHandle(LWriteOv.hEvent);
 641   end;
 642 end;
 643 
 644 
 645 procedure TSerialPort.CheckReadWriteArguments(const AArr: TArray<System.Byte>; const AOffset, ACount: Integer);
 646 // 检查读写操作的输入参数
 647 var
 648   LLen : Integer;
 649 begin
 650   LLen := Length(AArr);
 651   if (LLen <= 0) then begin
 652     raise ESerialPort.Create('Null bytes buffer');
 653   end;
 654 
 655   if (AOffset < 0) then begin
 656     raise ESerialPort.Create('Non-negative number required, offset');
 657   end;
 658 
 659   if (ACount < 0) then begin
 660     raise ESerialPort.Create('Non-negative number required, count');
 661   end;
 662 
 663   if (LLen - AOffset < ACount) then begin
 664     raise ESerialPort.Create('Offset and length were out of bounds for the array '
 665             + 'or count is greater than the number of elements from index to the end of the source collection');
 666   end;
 667 end;
 668 
 669 
 670 function TSerialPort.CheckAndGetComNumber(const AComName: string) : Integer;
 671 // 检查和串口名是不是 COM后面跟着数字 的格式, 如果是则返回数字, 否则抛出异常
 672 const
 673   LStrInvalidPortNameFmt = 'The given port name (%s) does not resolve to a valid serial port';
 674 begin
 675   if (not AComName.StartsWith('COM', True))
 676       or (not Integer.TryParse(AComName.Substring(3), Result))
 677       or (not Result > 0)
 678   then begin
 679     raise ESerialPort.CreateFmt(LStrInvalidPortNameFmt, [AComName]);
 680   end;
 681 end;
 682 
 683 
 684 // --- about comm config
 685 
 686 procedure TSerialPort.ConfigEvents();
 687 // 配置事件
 688 const
 689   LEV_ALL = (   EV_BREAK or EV_CTS    or EV_DSR    or EV_ERR or EV_RING
 690              or EV_RLSD  or EV_RXCHAR or EV_RXFLAG or EV_TXEMPTY);
 691 var
 692   LErrCode : Cardinal;
 693 begin
 694   // 设置事件, 把所有的事件都设置了, 虽然没有处理这些事件
 695   if not SetCommMask(self.FCommHandle, LEV_ALL) then begin
 696     LErrCode := GetLastError();
 697     raise ESerialPort.CreateFmt('SetCommMask failed, caused by %d', [LErrCode]);
 698   end;
 699 end;
 700 
 701 
 702 procedure TSerialPort.ConfigDCB(ACommProp : TCommProp);
 703 // 配置 设备控制块
 704 // TODO 完善
 705 var
 706   LDcb : TDCB;
 707   LErrCode : Cardinal;
 708 begin
 709   if not GetCommState(self.FCommHandle, LDcb) then begin
 710     LErrCode := GetLastError();
 711     raise ESerialPort.CreateFmt('Get DCB failed, caused by %d', [LErrCode]);
 712   end;
 713 
 714   // TODO others
 715   //LDcb.DCBlength := SizeOf(TDCB);
 716 
 717   LDcb.BaudRate := self.FBaudRate;
 718   LDcb.Parity   := self.FParity;
 719   LDcb.ByteSize := self.FDataBits;
 720   LDcb.StopBits := self.FStopBit;
 721 
 722   // always true for communications resources
 723   SetDcbFlag(LDcb, DCBFlags.FBINARY, 1);
 724   //LDcb.Flags := 1;
 725 
 726   if (self.FParity = TParityTool.None) then begin
 727     SetDcbFlag(LDcb, DCBFlags.FPARITY, 0);
 728   end
 729   else begin
 730     SetDcbFlag(LDcb, DCBFlags.FPARITY, 1);
 731   end;
 732 
 733   // Note-1
 734   // 不支持这个东西, 不了解它, C# 默认Handshake.None
 735   SetDcbFlag(LDcb, DCBFlags.FOUTXCTSFLOW, 0);
 736 
 737   SetDcbFlag(LDcb, DCBFlags.FOUTXDSRFLOW, 0); // dsrTimeout is always set to 0.
 738   SetDcbFlag(LDcb, DCBFlags.FDTRCONTROL, DCBDTRFlowControl.DTR_CONTROL_DISABLE);
 739   SetDcbFlag(LDcb, DCBFlags.FDSRSENSITIVITY, 0); // this should remain off
 740 
 741   // 同 Note-1
 742   SetDcbFlag(LDcb, DCBFlags.FINX, 0);
 743   SetDcbFlag(LDcb, DCBFlags.FOUTX, 0);
 744 
 745 
 746   // if no parity, we have no error character (i.e. ErrorChar = '' or null character)
 747   if (self.FParity = TParityTool.None) then begin
 748     SetDcbFlag(LDcb, DCBFlags.FERRORCHAR, 0);
 749     LDcb.ErrorChar := #0;
 750   end
 751   else begin
 752     if (Ord(self.FParityReplace) = 0) then begin
 753       SetDcbFlag(LDcb, DCBFlags.FERRORCHAR, 0);
 754     end
 755     else begin
 756       SetDcbFlag(LDcb, DCBFlags.FERRORCHAR, 1);
 757     end;
 758 
 759     LDcb.ErrorChar := self.FParityReplace;
 760   end;
 761 
 762   // Note-2 默认 C# 默认 false
 763   SetDcbFlag(LDcb, DCBFlags.FNULL, 0);
 764 
 765   // SerialStream does not handle the fAbortOnError behaviour, so we must make sure it's not enabled
 766   // C# 的 SerialStream
 767   SetDcbFlag(LDcb, DCBFlags.FABORTONOERROR, 0);
 768 
 769   // Setting RTS control, which is RTS_CONTROL_HANDSHAKE if RTS / RTS-XOnXOff handshaking
 770   // used, RTS_ENABLE (RTS pin used during operation) if rtsEnable true but XOnXoff / No handshaking
 771   // used, and disabled otherwise.
 772   // C# 这里有与 Handshake 有关的处理
 773   if (GetDcbFlag(LDcb, DCBFlags.FRTSCONTROL) = DCBRTSFlowControl.RTS_CONTROL_HANDSHAKE) then begin
 774     SetDcbFlag(LDcb, DCBFlags.FRTSCONTROL, DCBRTSFlowControl.RTS_CONTROL_DISABLE);
 775   end;
 776 
 777   LDcb.XonChar  := TDCBTool.DEFAULTXONCHAR;      // may be exposed later but for now, constant
 778   LDcb.XoffChar := TDCBTool.DEFAULTXOFFCHAR;
 779 
 780   // minimum number of bytes allowed in each buffer before flow control activated
 781   // heuristically, this has been set at 1/4 of the buffer size
 782   LDcb.XonLim  := (ACommProp.dwCurrentRxQueue div 4);
 783   LDcb.XoffLim := (ACommProp.dwCurrentRxQueue div 4);
 784 
 785   LDcb.EofChar := TDCBTool.EOFCHAR;
 786 
 787   // OLD MSCOMM: dcb.EvtChar = (byte) 0;
 788   // now changed to make use of RXFlag WaitCommEvent event => Eof WaitForCommEvent event
 789   LDcb.EvtChar := TDCBTool.EOFCHAR;
 790 
 791 
 792   if not SetCommState(self.FCommHandle, LDcb) then begin
 793     LErrCode := GetLastError();
 794     raise ESerialPort.CreateFmt('Set DCB failed, caused by %d.', [LErrCode]);
 795   end;
 796 end;
 797 
 798 
 799 procedure TSerialPort.ConfigTimeouts();
 800 // 配置超时
 801 var
 802   LTimeouts : TCommTimeouts;
 803   LErrCode : Cardinal;
 804 begin
 805   if not GetCommTimeouts(self.FCommHandle, LTimeouts) then begin
 806     LErrCode := GetLastError();
 807     raise ESerialPort.CreateFmt('Get timeouts failed, caused by %d', [LErrCode]);
 808   end;
 809 
 810   if (self.FReadTimeOut = 0) then begin
 811     // 不使用超时, 读操作总是立即返回
 812     LTimeouts.ReadIntervalTimeout := MAXDWORD;
 813     LTimeouts.ReadTotalTimeoutMultiplier := 0;
 814     LTimeouts.ReadTotalTimeoutConstant := 0;
 815   end
 816   else begin
 817     // 固定超时
 818     LTimeouts.ReadIntervalTimeout := MAXDWORD;
 819     LTimeouts.ReadTotalTimeoutMultiplier := MAXDWORD;
 820     LTimeouts.ReadTotalTimeoutConstant := self.FReadTimeOut;
 821   end;
 822 
 823   LTimeouts.WriteTotalTimeoutMultiplier := 0;
 824   // 如果 FWriteTimeout 设置为 0, 则不使用写超时
 825   LTimeouts.WriteTotalTimeoutConstant   := self.FWriteTimeOut;
 826 
 827   if not SetCommTimeouts(self.FCommHandle, LTimeouts) then begin
 828     LErrCode := GetLastError();
 829     raise ESerialPort.CreateFmt('Set timeouts failed, caused by %d.', [LErrCode]);
 830   end;
 831 end;
 832 
 833 
 834 procedure TSerialPort.ConfigBufferSizes;
 835 // 配置缓冲区
 836 var
 837   LErrCode : Cardinal;
 838 begin
 839   if not(SetupComm(self.FCommHandle, self.FReadBufferSize, self.FWriteBufferSize)) then begin
 840     LErrCode := GetLastError();
 841     raise ESerialPort.CreateFmt('Set buffer sizes failed, caused by %d', [LErrCode]);
 842   end;
 843 end;
 844 
 845 
 846 
 847 // --- about Dcb.Flags
 848 
 849 function TSerialPort.GetDcbFlag(const ADcb : TDCB; AWhichFlag : Integer) : Integer;
 850 // from C#
 851 var
 852   LMask : Cardinal;
 853 begin
 854   if ((AWhichFlag = DCBFlags.FDTRCONTROL) or (AWhichFlag = DCBFlags.FRTSCONTROL)) then begin
 855     LMask := $03;
 856   end
 857   else if (AWhichFlag = DCBFlags.FDUMMY2) then begin
 858     LMask := $1FFFF;
 859   end
 860   else begin
 861     LMask := $01;
 862   end;
 863 
 864   Result := ADcb.Flags and (LMask shl AWhichFlag);
 865   Result := Result shr AWhichFlag;
 866 end;
 867 
 868 
 869 procedure TSerialPort.SetDcbFlag(var ADcb : TDCB; AWhichFlag : Integer; ASetting : Integer);
 870 // from C#
 871 var
 872   LMask : Cardinal;
 873 begin
 874   ASetting := ASetting shl AWhichFlag;
 875 
 876   if ((AWhichFlag = DCBFlags.FDTRCONTROL) or (AWhichFlag = DCBFlags.FRTSCONTROL)) then begin
 877     LMask := $03;
 878   end
 879   else if (AWhichFlag = DCBFlags.FDUMMY2) then begin
 880     LMask := $1FFFF;
 881   end
 882   else begin
 883     LMask := $01;
 884   end;
 885 
 886   // clear the region
 887   ADcb.Flags := ADcb.Flags and (not (LMask shl ADcb.Flags));
 888 
 889   // set the region
 890   ADcb.Flags := ADcb.Flags or ASetting;
 891 end;
 892 
 893 
 894 // --- for properties
 895 
 896 procedure TSerialPort.SetPropCommName(const ACommName: string);
 897 begin
 898   if Trim(ACommName) = '' then begin
 899     raise ESerialPort.Create('The port name can not be empty');
 900   end;
 901 
 902   if self.IsOpen() then begin
 903     raise ESerialPort.CreateFmt('"%s" can not be set while the port is open', [self.FCommName]);
 904   end;
 905 
 906   self.FCommName := ACommName;
 907 end;
 908 
 909 procedure TSerialPort.SetPropBaudRate(const ABaudRate: Integer);
 910 begin
 911   // if not TBaudRateTool.IsSupportedBaudRate(ABaudRate) then begin
 912   if (ABaudRate <= 0) then begin
 913     raise ESerialPort.CreateFmt('Unsupported bardrate %d', [ABaudRate]);
 914   end;
 915 
 916   self.FBaudRate := ABaudRate;
 917 end;
 918 
 919 procedure TSerialPort.SetPropParity(const AParity: Integer);
 920 begin
 921   if not TParityTool.IsSupportedParity(AParity) then begin
 922     raise ESerialPort.CreateFmt('Unsupported parity %d', [AParity]);
 923   end;
 924 
 925   self.FParity := AParity;
 926 end;
 927 
 928 procedure TSerialPort.SetPropDataBits(const ADataBits: Integer);
 929 begin
 930   if not TDataBitsTool.IsSupportedDataBits(ADataBits) then begin
 931     raise ESerialPort.CreateFmt('Unsupported dataBits %d', [ADataBits]);
 932   end;
 933 
 934   self.FDataBits := ADataBits;
 935 end;
 936 
 937 procedure TSerialPort.SetPropStopBit(const AStopBit: Integer);
 938 begin
 939   if not TStopBitTool.IsSupportedStopBit(AStopBit) then begin
 940     raise ESerialPort.CreateFmt('Unsupported stopBit %d', [AStopBit]);
 941   end;
 942 
 943   self.FStopBit := AStopBit;
 944 end;
 945 
 946 
 947 procedure TSerialPort.SetPropReadTimeOut(const ATimeOut: Integer);
 948 // 设置读超时
 949 // timeout == 0 表示不使用超时, 无论有没有数据总是立即返回
 950 var
 951   LOldTimeout : Integer;
 952 begin
 953   if (ATimeOut < 0) then begin
 954     raise ESerialPort.CreateFmt('ReadTimeout %d out of range, timeout can not less than 0.', [ATimeOut]);
 955   end;
 956 
 957   LOldTimeOut := self.FReadTimeOut;
 958   try
 959     self.FReadTimeOut := ATimeOut;
 960 
 961     if self.IsOpen() then begin
 962       self.ConfigTimeouts();
 963     end;
 964   except
 965     self.FReadTimeOut := LOldTimeout;
 966     raise;
 967   end;
 968 end;
 969 
 970 procedure TSerialPort.SetPropWriteTimeOut(const ATimeOut: Integer);
 971 // 设置写超时
 972 // timeout == 0 表示不使用写超时
 973 var
 974   LOldTimeOut : Integer;
 975 begin
 976   if (ATimeOut < 0) then begin
 977     raise ESerialPort.CreateFmt('ReadTimeout %d out of range, timeout can not less than 0.', [ATimeOut]);
 978   end;
 979 
 980   LOldTimeOut := self.FWriteTimeOut;
 981   try
 982     self.FWriteTimeOut := ATimeOut;
 983 
 984     if self.IsOpen() then begin
 985       self.ConfigTimeouts();
 986     end;
 987   except
 988     self.FWriteTimeOut := LOldTimeout;
 989     raise;
 990   end;
 991 end;
 992 
 993 
 994 procedure TSerialPort.SetPropReadBufferSize(const ASize: Integer);
 995 // 设置 读缓冲区
 996 begin
 997   if (ASize <= 0) then begin
 998     raise ESerialPort.Create('ReadBufferSize must greater than 0.');
 999   end;
1000 
1001   if self.IsOpen() then begin
1002     raise  ESerialPort.Create('ReadBufferSize cannot be set while the port is open.');
1003   end;
1004 
1005   self.FReadBufferSize := ASize;
1006 end;
1007 
1008 procedure TSerialPort.SetPropWriteBufferSize(const ASize: Integer);
1009 // 设置 写缓冲区
1010 begin
1011   if (ASize <= 0) then begin
1012     raise ESerialPort.Create('WriteBufferSize must greater than 0.');
1013   end;
1014 
1015   if self.IsOpen() then begin
1016     raise  ESerialPort.Create('WriteBufferSize cannot be set while the port is open.');
1017   end;
1018 
1019   self.FWriteBufferSize := ASize;
1020 end;
1021 
1022 
1023 
1024 // --- TSerialPort.TEventLoop ---
1025 // TODO
1026 
1027 constructor TSerialPort.TEventLoop.Create(AHandle : THandle);
1028 begin
1029   inherited Create();
1030 
1031   self.FHandle := AHandle;
1032   // TODO
1033 end;
1034 
1035 destructor TSerialPort.TEventLoop.Destroy;
1036 begin
1037   // TODO
1038 
1039   inherited;
1040 end;
1041 
1042 
1043 procedure TSerialPort.TEventLoop.Start();
1044 begin
1045   // TODO
1046 end;
1047 
1048 
1049 procedure TSerialPort.TEventLoop.Stop();
1050 begin
1051   // TODO
1052 end;
1053 
1054 
1055 
1056 
1057 // --- unit private sequential search ---
1058 
1059 function Contains(const AItem : Integer; const AArray : array of Integer) : Boolean;
1060 var
1061   LElem : Integer;
1062 begin
1063   for LElem in AArray do begin
1064     if (AItem = LElem) then begin
1065       Result := True;
1066       Exit;
1067     end;
1068   end;
1069 
1070   Result := False;
1071 end;
1072 
1073 
1074 // --- TBaudRateTool ---
1075 
1076 class function TBaudRateTool.IsSupportedBaudRate(const ABaudRate : Integer) : Boolean;
1077 begin
1078   Result := Contains(ABaudRate, SupportedValues);
1079 end;
1080 
1081 // --- TPairtyTool ---
1082 
1083 class function TParityTool.IsSupportedParity(const AParity : Integer) : Boolean;
1084 begin
1085   Result := Contains(AParity, SupportedValues);
1086 end;
1087 
1088 // --- TDataBitsTool ---
1089 
1090 class function TDataBitsTool.IsSupportedDataBits(const ADataBits : Integer) : Boolean;
1091 begin
1092   Result := Contains(ADataBits, SupportedValues);                                     
1093 end;
1094 
1095 // --- TStopBitTool ---
1096 
1097 class function TStopBitTool.IsSupportedStopBit(const AStopBit : Integer) : Boolean;
1098 begin
1099   Result := Contains(AStopBit, SupportedValues);
1100 end;
1101 
1102 
1103 end.

--------- THE END ---------

原文地址:https://www.cnblogs.com/shadow-abyss/p/12585696.html