.Net 平台SerialPort类内部实现探秘

简介:    这段时间用Moxa DA660(WinCE5.0平台)测试16口同时下发数据,发现由于该硬件设备的CPU主频仅有260M赫兹,大于10口同时下发数据就会造成发送延迟,导致下发失败。

  

这段时间用Moxa DA660(WinCE5.0平台)测试16口同时下发数据,发现由于该硬件设备的CPU主频仅有260M赫兹,大于10口同时下发数据就会造成发送延迟,导致下发失败。前次用.net的SerialPort类实现了一个PPC红外口读写数据的小程序(其实就是串口操作),发现该程序在接收大量的数据时,很容易发生崩溃,并且该错误信息,程序本身无法捕捉(用EVC开发的程序就没有这种情况),所以就有了一探SerialPort类的冲动。
用.Net Reflector工具(该工具在《程序员》杂志4月刊有介绍)很容易就可以看到微软.net框架集SerialPort的实现源码,下面从构造函数开始谈起(注:精简框架下的system.dll反射后竟然看不到相关代码,看来微软对精简集进行了加密,只能看非精简框架集的system.dll,其实现我想应该差不太多,但是Wince平台仅能实现同步读写)。
1、通信参数的默认值
    this.baudRate = 9600;               //波特率
    this.dataBits = 8;                  //数据位
    this.stopBits = StopBits.One;       //停止位
    this.portName = "COM1";            //串口号
   this.readTimeout = -1;              //读超时
    this.writeTimeout = -1;             //写超时
    this.receivedBytesThreshold = 1;    //触发事件前接收缓冲区的数据个数
    this.parityReplace = 0x3f;          //数据校验失败,该数据的替换字符
    this.newLine = "/n";                //换行符
    this.readBufferSize = 4096;         //读缓冲区大小
this.writeBufferSize = 2048;        //写缓冲区大小
2、看看微软的代码如何枚举本机串口号(也是通过注册表方式)
   public static string[] GetPortNames()
{
    RegistryKey localMachine = null;
    RegistryKey key2 = null;
string[] textArray = null;
//这里有个断言,判断该注册表项是否存在
    new RegistryPermission(RegistryPermissionAccess.Read, @"HKEY_LOCAL_MACHINE/HARDWARE/DEVICEMAP/SERIALCOMM").Assert();
    try
    {
        localMachine = Registry.LocalMachine;
        key2 = localMachine.OpenSubKey(@"HARDWARE/DEVICEMAP/SERIALCOMM", false);
        if (key2 != null)
        {
            string[] valueNames = key2.GetValueNames();
            textArray = new string[valueNames.Length];
            for (int i = 0; i < valueNames.Length; i++)
            {
                textArray[i] = (string) key2.GetValue(valueNames[i]);
            }
        }
    }
    finally
    {
        if (localMachine != null)
        {
            localMachine.Close();
        }
        if (key2 != null)
        {
            key2.Close();
        }
        CodeAccessPermission.RevertAssert();
    }
    if (textArray == null)
    {
        textArray = new string[0];
    }
    return textArray;
}
   3、核心读代码
   //如果在超时时间内没有获取指定的数据个数就会抛出异常,这种设计方式我不大习惯,如果超时直接返回-1或0即可,没有必要抛出异常。
private int InternalRead(char[] buffer, int offset, int count, int timeout, bool countMultiByteCharsAsOne)
{
    if (count == 0)
    {
        return 0;
    }
    int tickCount = Environment.TickCount;
    int additionalByteLength = this.internalSerialStream.BytesToRead;
this.MaybeResizeBuffer(additionalByteLength);
//用到了流的串口读写,看来还需要继续跟踪
    this.readLen += this.internalSerialStream.Read(this.inBuffer, this.readLen, additionalByteLength);
    if (this.decoder.GetCharCount(this.inBuffer, this.readPos, this.CachedBytesToRead) > 0)
    {
        return this.ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
    }
    if (timeout == 0)
    {
        throw new TimeoutException();
    }
    int maxByteCount = this.Encoding.GetMaxByteCount(count);
    while (true)
    {
        this.MaybeResizeBuffer(maxByteCount);
        this.readLen += this.internalSerialStream.Read(this.inBuffer, this.readLen, maxByteCount);
        int num4 = this.ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
        //只要获取了数据,就返回数据接收的个数
if (num4 > 0)
        {
            return num4;
        }
        //这里可以看出timeout设为-1的用意了
        if ((timeout != -1) && ((timeout - (Environment.TickCount - tickCount)) <= 0))
        {
            throw new TimeoutException();
        }
    }
}
    4、其实核心串口操作是 SerialStream类,这个类的下一层就是相关的API函数了
    //注意:相关API的底层封装在 UnsafeNativeMethods类中,这个类其实就是API相关的罗列。
    看该函数的构造函数中的代码:
   SafeFileHandle hFile = UnsafeNativeMethods.CreateFile(@"//./" + portName, -1073741824, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, IntPtr.Zero);
    注意串口文件名为 @"//./" + portName, @//./表示串口超过9的函数就必须添加该字符串,否则打开串口失败(<10的加与不加一样),但是在操作Moxa的设备时,很奇怪串口名称必须为: "$device//COM" + PortNo.ToString() + "/0"(从这里明白了,为什么当初我把串口名称设为 @//./ COM" + PortNo.ToString()+ :”也一样失败了,因为它已经预先添加了)。
        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;
        }
      从以上代码可以看出,它的超时仅仅是总超时时间,不能设置单个字节之间的超时时间。      
   这是读操作中的一段代码: 
int num = 0;
    if (this.isAsync)
    {
        IAsyncResult asyncResult = this.BeginReadCore(array, offset, count, null, null);
        num = this.EndRead(asyncResult);
    }
    Else   //对我们来说,仅关心同步操作即可
    {
        int hr;
        num = this.ReadFileNative(array, offset, count, null, out hr);
        if (num == -1)
        {
            InternalResources.WinIOError();
        }
    }
    if (num == 0)
    {
        throw new TimeoutException();
}
这是读写操作的函数,看给直接用API读没有什么区别:
fixed (byte* numRef = bytes) //用到了指针
    {
        if (this.isAsync)
        {
            num = UnsafeNativeMethods.ReadFile(this._handle, numRef + offset, count, IntPtr.Zero, overlapped);
        }
        else
        {
            num = UnsafeNativeMethods.ReadFile(this._handle, numRef + offset, count, out numBytesRead, IntPtr.Zero);
        }
    }
   注:写代码与上面的类似
   从以上代码可以粗浅的看出(SerialPort类->SerialStream类->UnsafeNativeMethods类->API函数),采用SerialPort读写串口是很繁琐的,并且效率较低,有时间可以根据这写代码写一个紧凑有效的串口读写类(有兴趣的朋友也可以实现,到时候可别忘了发给我一份:)。
相关文章
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
518 8
|
JSON 安全 API
.net 自定义日志类
在.NET中,创建自定义日志类有助于更好地管理日志信息。示例展示了如何创建、配置和使用日志记录功能,包括写入日志文件、设置日志级别、格式化消息等。注意事项涵盖时间戳、日志级别、JSON序列化、线程安全、日志格式、文件处理及示例使用。请根据需求调整代码。
271 13
|
Linux API C#
基于 .NET 开发的多功能流媒体管理控制平台
基于 .NET 开发的多功能流媒体管理控制平台
288 9
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
550 2
|
机器学习/深度学习 人工智能 Cloud Native
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台。本文深入解析 .NET 的核心优势,探讨其在企业级应用、Web 开发及移动应用等领域的应用案例,并展望未来在人工智能、云原生等方面的发展趋势。
401 3
|
存储 设计模式 编解码
.NET 8.0 通用管理平台,支持模块化、WinForms 和 WPF
【11月更文挑战第5天】本文分析了.NET 8.0 通用管理平台在模块化、WinForms 和 WPF 方面的优势。模块化设计提升了系统的可维护性和可扩展性,提高了代码复用性;WinForms 提供了丰富的控件库和简单易用的开发模式,技术成熟稳定;WPF 支持强大的数据绑定和 MVVM 模式,具备丰富的图形和动画功能,以及灵活的布局系统。
779 2
.NET 4.0下实现.NET4.5的Task类相似功能组件
【10月更文挑战第29天】在.NET 4.0 环境下,可以使用 `BackgroundWorker` 类来实现类似于 .NET 4.5 中 `Task` 类的功能。`BackgroundWorker` 允许在后台执行耗时操作,同时不会阻塞用户界面线程,并支持进度报告和取消操作。尽管它有一些局限性,如复杂的事件处理模型和不灵活的任务管理方式,但在某些情况下仍能有效替代 `Task` 类。
248 0
|
缓存 程序员
封装一个给 .NET Framework 用的内存缓存帮助类
封装一个给 .NET Framework 用的内存缓存帮助类
243 1
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
343 0