对比SerialCommunication和微软的SerialPort,向SerialPort看齐

摘要:
接下来,我将介绍串行端口相对于串行通信的优势。首先,比较一些基本字段。

  SerialCommunication是我综合网上看到的代码稍作修改而成的串口通信类,而SerialPort则是C#的System类库的IO目录Ports子目录下的串口通信类。SerialCommunication只有区区的二百多行,而SerialPort则有几千行。下面我将介绍SerialPort相对于SerialCommunication好在哪里。

  首先是一些基本字段的对比。

/// <summary>
/// 波特率
/// </summary>
public int BaudRate;
/// <summary>
/// 数据位
/// </summary>
public byte ByteSize = 8;
/// <summary>
/// 奇偶校验,0-4=no,odd,even,mark,space
/// </summary>
public byte Parity = 1;
/// <summary>
/// 串口号
/// </summary>
public int PortNum;
/// <summary>
/// 读超时
/// </summary>
public UInt32 ReadTimeout = 1000;
/// <summary>
/// 停止位,0,1,2 = 1, 1.5, 2 
/// </summary>
public byte StopBits;
public SerialPort()
{
    this.baudRate = 0x2580;
    this.dataBits = 8;
    this.stopBits = System.IO.Ports.StopBits.One;
    this.portName = "COM1";
    this.encoding = System.Text.Encoding.ASCII;
    this.decoder = System.Text.Encoding.ASCII.GetDecoder();
    this.maxByteCountForSingleChar = System.Text.Encoding.ASCII.GetMaxByteCount(1);
    this.readTimeout = -1;
    this.writeTimeout = -1;
    this.receivedBytesThreshold = 1;
    this.parityReplace = 0x3f;
    this.newLine = "
";
    this.readBufferSize = 0x1000;
    this.writeBufferSize = 0x800;
    this.inBuffer = new byte[0x400];
    this.oneChar = new char[1];
}
public SerialPort(IContainer container)
{
    this.baudRate = 0x2580;
    this.dataBits = 8;
    this.stopBits = System.IO.Ports.StopBits.One;
    this.portName = "COM1";
    this.encoding = System.Text.Encoding.ASCII;
    this.decoder = System.Text.Encoding.ASCII.GetDecoder();
    this.maxByteCountForSingleChar = System.Text.Encoding.ASCII.GetMaxByteCount(1);
    this.readTimeout = -1;
    this.writeTimeout = -1;
    this.receivedBytesThreshold = 1;
    this.parityReplace = 0x3f;
    this.newLine = "
";
    this.readBufferSize = 0x1000;
    this.writeBufferSize = 0x800;
    this.inBuffer = new byte[0x400];
    this.oneChar = new char[1];
    container.Add(this);
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SerialPort(string portName) : this(portName, 0x2580, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One)
{
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SerialPort(string portName, int baudRate) : this(portName, baudRate, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One)
{
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity) : this(portName, baudRate, parity, 8, System.IO.Ports.StopBits.One)
{
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity, int dataBits) : this(portName, baudRate, parity, dataBits, System.IO.Ports.StopBits.One)
{
}
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity, int dataBits, System.IO.Ports.StopBits stopBits)
{
    this.baudRate = 0x2580;
    this.dataBits = 8;
    this.stopBits = System.IO.Ports.StopBits.One;
    this.portName = "COM1";
    this.encoding = System.Text.Encoding.ASCII;
    this.decoder = System.Text.Encoding.ASCII.GetDecoder();
    this.maxByteCountForSingleChar = System.Text.Encoding.ASCII.GetMaxByteCount(1);
    this.readTimeout = -1;
    this.writeTimeout = -1;
    this.receivedBytesThreshold = 1;
    this.parityReplace = 0x3f;
    this.newLine = "
";
    this.readBufferSize = 0x1000;
    this.writeBufferSize = 0x800;
    this.inBuffer = new byte[0x400];
    this.oneChar = new char[1];
    this.PortName = portName;
    this.BaudRate = baudRate;
    this.Parity = parity;
    this.DataBits = dataBits;
    this.StopBits = stopBits;
}

  可以看到我有一个明显的不足,就是用了public的字段,这貌似是违反了面向对象的理念的,按照面向对象的理念,外部类应该是不能直接访问操作其他类的内部字段的,不过有些人就认为写private字段,public属性太麻烦,过度设计,能不用属性就不用属性。这里就不讨论用属性的优点了。除此之外还可以看到SerialPort有个明显的优点,使用了大量的重载方法,那些重载方法之间又用了继承,最终执行了参数最多的那个重载方法,该方法里面又将那些参数赋值给了对应的属性,各属性赋值时候又可以进行各种判断,这就是C#的经典套路,也是比较好的套路,只是这样写代码会长很多。

  然后看串口打开方法

public void Open()
{
    // OPEN THE COMM PORT. 
    _hComm = CreateFile("COM" + PortNum, GenericRead | GenericWrite, 0, 0, OpenExisting, 0, 0);
    // IF THE PORT CANNOT BE OPENED, BAIL OUT. 
    if (_hComm == InvalidHandleValue)
    {
		throw (new Exception("Port Open Failure"));
    }
    CommTimeouts ctoCommPort = new CommTimeouts { ReadTotalTimeoutConstant = ReadTimeout };
    if (!SetCommTimeouts(_hComm, ref ctoCommPort))
    {
		throw (new Exception("Bad Timeout Settings"));
    }
    Dcb dcb = new Dcb();
    GetCommState(_hComm, ref dcb);
    dcb.BaudRate = BaudRate;
    dcb.Parity = Parity;
    dcb.ByteSize = ByteSize;
    dcb.StopBits = StopBits;
    if (!SetCommState(_hComm, ref dcb))
    {
		throw (new Exception("Bad Com Settings"));
    }
}
public void Open()
{
    if (this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_already_open"));
    }
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
    this.internalSerialStream = new SerialStream(this.portName, this.baudRate, this.parity, this.dataBits, this.stopBits, this.readTimeout, this.writeTimeout, this.handshake, this.dtrEnable, this.rtsEnable, this.discardNull, this.parityReplace);
    this.internalSerialStream.SetBufferSizes(this.readBufferSize, this.writeBufferSize);
    this.internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(this.CatchErrorEvents);
    this.internalSerialStream.PinChanged += new SerialPinChangedEventHandler(this.CatchPinChangedEvents);
    this.internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(this.CatchReceivedEvents);
}
internal SerialStream(string portName, int baudRate, System.IO.Ports.Parity parity, int dataBits, System.IO.Ports.StopBits stopBits, int readTimeout, int writeTimeout, System.IO.Ports.Handshake handshake, bool dtrEnable, bool rtsEnable, bool discardNull, byte parityReplace)
{
    int dwFlagsAndAttributes = 0x40000000;
    if (Environment.OSVersion.Platform == PlatformID.Win32Windows)
    {
		dwFlagsAndAttributes = 0x80;
		this.isAsync = false;
    }
    if ((portName == null) || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase))
    {
		throw new ArgumentException(SR.GetString("Arg_InvalidSerialPort"), "portName");
    }
    SafeFileHandle hFile = Microsoft.Win32.UnsafeNativeMethods.CreateFile(@"\." + portName, -1073741824, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, IntPtr.Zero);
    if (hFile.IsInvalid)
    {
		InternalResources.WinIOError(portName);
    }
    try
    {
		int fileType = Microsoft.Win32.UnsafeNativeMethods.GetFileType(hFile);
		if ((fileType != 2) && (fileType != 0))
		{
			throw new ArgumentException(SR.GetString("Arg_InvalidSerialPort"), "portName");
		}
		this._handle = hFile;
		this.portName = portName;
		this.handshake = handshake;
		this.parityReplace = parityReplace;
		this.tempBuf = new byte[1];
		this.commProp = new Microsoft.Win32.UnsafeNativeMethods.COMMPROP();
		int lpModemStat = 0;
		if (!Microsoft.Win32.UnsafeNativeMethods.GetCommProperties(this._handle, ref this.commProp) || !Microsoft.Win32.UnsafeNativeMethods.GetCommModemStatus(this._handle, ref lpModemStat))
		{
			int errorCode = Marshal.GetLastWin32Error();
			switch (errorCode)
			{
				case 0x57:
				case 6:
				throw new ArgumentException(SR.GetString("Arg_InvalidSerialPortExtended"), "portName");
			}
			InternalResources.WinIOError(errorCode, string.Empty);
		}
		if ((this.commProp.dwMaxBaud != 0) && (baudRate > this.commProp.dwMaxBaud))
		{
			throw new ArgumentOutOfRangeException("baudRate", SR.GetString("Max_Baud", new object[] { this.commProp.dwMaxBaud }));
		}
		this.comStat = new Microsoft.Win32.UnsafeNativeMethods.COMSTAT();
		this.dcb = new Microsoft.Win32.UnsafeNativeMethods.DCB();
		this.InitializeDCB(baudRate, parity, dataBits, stopBits, discardNull);
		this.DtrEnable = dtrEnable;
		this.rtsEnable = this.GetDcbFlag(12) == 1;
		if ((handshake != System.IO.Ports.Handshake.RequestToSend) && (handshake != System.IO.Ports.Handshake.RequestToSendXOnXOff))
		{
			this.RtsEnable = rtsEnable;
		}
		if (readTimeout == 0)
		{
			this.commTimeouts.ReadTotalTimeoutConstant = 0;
			this.commTimeouts.ReadTotalTimeoutMultiplier = 0;
			this.commTimeouts.ReadIntervalTimeout = -1;
		}
		else if (readTimeout == -1)
		{
			this.commTimeouts.ReadTotalTimeoutConstant = -2;
			this.commTimeouts.ReadTotalTimeoutMultiplier = -1;
			this.commTimeouts.ReadIntervalTimeout = -1;
		}
		else
		{
			this.commTimeouts.ReadTotalTimeoutConstant = readTimeout;
			this.commTimeouts.ReadTotalTimeoutMultiplier = -1;
			this.commTimeouts.ReadIntervalTimeout = -1;
		}
		this.commTimeouts.WriteTotalTimeoutMultiplier = 0;
		this.commTimeouts.WriteTotalTimeoutConstant = (writeTimeout == -1) ? 0 : writeTimeout;
		if (!Microsoft.Win32.UnsafeNativeMethods.SetCommTimeouts(this._handle, ref this.commTimeouts))
		{
			InternalResources.WinIOError();
		}
		if (this.isAsync && !ThreadPool.BindHandle(this._handle))
		{
			throw new IOException(SR.GetString("IO_BindHandleFailed"));
		}
		Microsoft.Win32.UnsafeNativeMethods.SetCommMask(this._handle, 0x1fb);
		this.eventRunner = new EventLoopRunner(this);
		new Thread(new ThreadStart(this.eventRunner.WaitForCommEvent)) { IsBackground = true }.Start();
    }
    catch
    {
		hFile.Close();
		this._handle = null;
		throw;
    }
}

  打开方法SerialPort比我的复杂很多,我那个只是打开并设置了一下串口,SerialPort除此之外还开启了ErrorReceived、PinChanged和DataReceived事件。两个接收事件和一个貌似是串口端口改变事件吧。前两个不写是因为我觉得有些人串口通信只需要写不需要接收,那么那两个事件也就不需要了吧,如果需要接收的话调用我那个Read方法就可以接收消息了,不过需要调用者自己开线程监听。

  不说这个,就说代码方面的问题吧,我那里有个明显的不太好的地方就是,抛出的异常没有进行分类,异常是有格式的,也可以自定义异常,我那里没有对异常进行分类,全都是抛出Exception就完了,这样的话调用者就不能方便的根据异常的类型进行不同的处理了。除此之外抛出的异常也不够详细,SerialPort的Open方法一开始就有一个Port_already_open端口已打开异常,我那边就没有分的这么细了,只要端口打开失败就返回Port Open Failure,异常分的不够细也是不好的。

  除此之外就是SerialPort用了大量的枚举,如StopBits、Handshake、Parity、SerialData、SerialError和SerialPinChange,用枚举有个很大的好处就是调用者可以清楚的根据枚举来判断每个枚举值的意义,这样子调用者可以在不查文档的情况下就顺利的写完了代码,这可能会节省编码人员很多的时间,与用枚举损失的些须效率,这是完全值得的,我没有用枚举只是贪图方便省事,这是不够好的,应该注意一下。

  接着看串口关闭方法

public void Close()
{
    if (_hComm != InvalidHandleValue)
    {
		CloseHandle(_hComm);
    }
}
public void Close()
{
    base.Dispose();
}
public void Dispose()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
		lock (this)
		{
			if ((this.site != null) && (this.site.Container != null))
			{
				this.site.Container.Remove(this);
			}
			if (this.events != null)
			{
				EventHandler handler = (EventHandler) this.events[EventDisposed];
				if (handler != null)
				{
					handler(this, EventArgs.Empty);
				}
			}
		}
    }
}

  关闭方法就完全不同了,我那关闭方法只是把串口关闭,SerialPort则貌似是把整个类Dispose了,这两种做法萝卜青菜各有所爱吧。不过我那个类貌似没有继承自IDisposable貌似不太好,比较大的类应该要提供Dispose才好的吧。

  最后看看读写方法吧

public byte[] Read(int numBytes)
{
    if (_hComm == InvalidHandleValue)
    {
		throw (new Exception("Comm Port Not Open"));
    }
    int bytesRead = 0;
    byte[] bufBytes = new byte[numBytes];
    ReadFile(_hComm, bufBytes, numBytes, ref bytesRead, ref _ovlCommPort);
    byte[] outBytes = new byte[bytesRead];
    Array.Copy(bufBytes, outBytes, bytesRead);
    return outBytes;
}
public void Write(byte[] writeBytes)
{
    if (_hComm == InvalidHandleValue)
    {
		throw (new Exception("Comm Port Not Open"));
    }
    WriteFile(_hComm, writeBytes, writeBytes.Length, ref _bytesCount, ref _ovlCommPort);
}
public int Read(byte[] buffer, int offset, int count)
{
    if (!this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_not_open"));
    }
    if (buffer == null)
    {
		throw new ArgumentNullException("buffer", SR.GetString("ArgumentNull_Buffer"));
    }
    if (offset < 0)
    {
		throw new ArgumentOutOfRangeException("offset", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if (count < 0)
    {
		throw new ArgumentOutOfRangeException("count", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if ((buffer.Length - offset) < count)
    {
		throw new ArgumentException(SR.GetString("Argument_InvalidOffLen"));
    }
    int num = 0;
    if (this.CachedBytesToRead >= 1)
    {
		num = Math.Min(this.CachedBytesToRead, count);
		Buffer.BlockCopy(this.inBuffer, this.readPos, buffer, offset, num);
		this.readPos += num;
		if (num == count)
		{
			if (this.readPos == this.readLen)
			{
				this.readPos = this.readLen = 0;
			}
			return count;
		}
		if (this.BytesToRead == 0)
		{
			return num;
		}
    }
    this.readLen = this.readPos = 0;
    int num2 = count - num;
    num += this.internalSerialStream.Read(buffer, offset + num, num2);
    this.decoder.Reset();
    return num;
}
public int Read(char[] buffer, int offset, int count)
{
    if (!this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_not_open"));
    }
    if (buffer == null)
    {
		throw new ArgumentNullException("buffer", SR.GetString("ArgumentNull_Buffer"));
    }
    if (offset < 0)
    {
		throw new ArgumentOutOfRangeException("offset", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if (count < 0)
    {
		throw new ArgumentOutOfRangeException("count", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if ((buffer.Length - offset) < count)
    {
		throw new ArgumentException(SR.GetString("Argument_InvalidOffLen"));
    }
    return this.InternalRead(buffer, offset, count, this.readTimeout, false);
}
public void Write(string text)
{
    if (!this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_not_open"));
    }
    if (text == null)
    {
		throw new ArgumentNullException("text");
    }
    if (text.Length != 0)
    {
		byte[] bytes = this.encoding.GetBytes(text);
		this.internalSerialStream.Write(bytes, 0, bytes.Length, this.writeTimeout);
    }
}
public void Write(byte[] buffer, int offset, int count)
{
    if (!this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_not_open"));
    }
    if (buffer == null)
    {
		throw new ArgumentNullException("buffer", SR.GetString("ArgumentNull_Buffer"));
    }
    if (offset < 0)
    {
		throw new ArgumentOutOfRangeException("offset", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if (count < 0)
    {
		throw new ArgumentOutOfRangeException("count", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if ((buffer.Length - offset) < count)
    {
		throw new ArgumentException(SR.GetString("Argument_InvalidOffLen"));
    }
    if (buffer.Length != 0)
    {
		this.internalSerialStream.Write(buffer, offset, count, this.writeTimeout);
    }
}
public void Write(char[] buffer, int offset, int count)
{
    if (!this.IsOpen)
    {
		throw new InvalidOperationException(SR.GetString("Port_not_open"));
    }
    if (buffer == null)
    {
		throw new ArgumentNullException("buffer");
    }
    if (offset < 0)
    {
		throw new ArgumentOutOfRangeException("offset", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if (count < 0)
    {
		throw new ArgumentOutOfRangeException("count", SR.GetString("ArgumentOutOfRange_NeedNonNegNumRequired"));
    }
    if ((buffer.Length - offset) < count)
    {
		throw new ArgumentException(SR.GetString("Argument_InvalidOffLen"));
    }
    if (buffer.Length != 0)
    {
		byte[] buffer2 = this.Encoding.GetBytes(buffer, offset, count);
		this.Write(buffer2, 0, buffer2.Length);
    }
}
public void WriteLine(string text)
{
    this.Write(text + this.NewLine);
}

  对比可以得出我那个读写方法没有SerialPort那么多,我那里只提供了byte[]的读写,这是相对来说比较不够用的,不过这个不是问题。问题是我那里读写时的异常判断太少,只判断了串口关闭的情况,这样有些异常就不能在收发之前被判断出来了。

  总结来说就是自己写的代码面向对象得还不够,代码不够规范,考虑的不够全面等,希望以后可以学着向“SerialPort”看齐!

免责声明:文章转载自《对比SerialCommunication和微软的SerialPort,向SerialPort看齐》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇常用的js跳转页面方法实现汇总Netty(一) SpringBoot 整合长连接心跳机制下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

iOS开发(Swift):创建UINavigationView的三种方法

,表示window值我们会赋值。然后创建一个根视图控制器rootViewController,一个导航控制器navigationController。)-˃Bool{//Overridepointforcustomizationafterapplicationlaunch.window=UIWindowwindow.makeKeyAndVisible()ro...

Github仓库重命名

1.在Github上重命名仓库,转到您自己的仓库,找到Setting标记,然后单击Options中的Settings以设置Repositoryname。2.修改本地仓库信息。由于远程仓库名称已更改,因此本地对应的仓库名称也应更改。1.检查当前远程仓库的信息$gitremote-v列出了所有远程仓库信息,包括网站地址。2.修改本地对应远程仓库的地址。修改后,使...

C#Win32API编程之PostMessage

本文以C#调用Win32API函数PostMessage完成指定表单的后台鼠标和键盘模拟为例,大致解释了C#调用非托管代码和Window的消息处理机制。我们可以将PostMessage用于函数。成功与否在很大程度上取决于我们传达的信息是否真实。消息表明消息是什么。请原谅我先讲故事。我希望先解释一下PostMessage函数。这是一个异步操作,如下图所示:调用...

面试了一个 31岁的iOS开发者,思绪万千,30岁以上的程序员还有哪些出路?

前言之前HR给了我一份简历,刚看到简历的第一眼,31岁?31岁,iOS开发工程师,工作经历7年,5年左右都在外包公司,2年左右在创业公司。iOS开发工程师这块,还是很少遇到30岁以上的开发,正好,来了一个30岁的开发,说实话,对我来说,还是蛮期待的,希望对我有所启示。这样的过程持续了半个小时那么年过350岁的程序员还有出路吗?作为一个8年的iOS开发,而且几...

Android 帧动画使用

本文介绍使用AnimationDrawable类来实现动画效果。oneshot="false",表示让动画一直循环播放下去。.backgroundasAnimationDrawableani.start()当动画正在播放时,调用start()方法是不会影响当前播放的。˃android:oneshot="true",动画播放1次后就会自行停止并保持在最后一帧。...

webstorm关闭烦人的eslint语法检查

使用eslint语法检查后,我们发现JS代码中到处都是红线。通过右键菜单中的fixeslint problems选项,我们可以发现页面代码格式完全被eslint包装。只需关闭exlint语法检查。看不见,想不起来。反向关闭不会影响代码开发,但相反,它会影响代码开发。关闭eslint位置:文件--˃设置--˃语言和框架--˃CodeQualityTools--...