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

news/2023/12/9 3:14:02
  
这段时间用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读写串口是很繁琐的,并且效率较低,有时间可以根据这写代码写一个紧凑有效的串口读写类(有兴趣的朋友也可以实现,到时候可别忘了发给我一份:)。




http://www.niftyadmin.cn/n/3655775.html

相关文章

javaScript 内存泄露

什么是内存泄露 只要程序提出要求 &#xff0c;运行时就需要提供内存 如果是持续性的服务进程&#xff0c;如果不及时释放不需要的内存&#xff0c;就会影响系统新能&#xff0c;甚至直接会崩溃。 不及时释放的内存 就叫内存泄露 什么情况下会引起&#xff1f; 1、意外的…

CSDN技术大会场记

1、和太阳一起出发2、远观大运村外景3、北京丽亭华苑酒店&#xff08;CSDN技术大会场地&#xff09;4、热闹的CSDN技术大会会场真正的CSDN英雄们 -- 名人堂&#xff0c;MVP&#xff0c;MVB这里面最早见过面的是孟宪会&#xff0c;是在我初次当选MVP在微软第一次聚会时认识的&am…

CSDN技术大会

其实在上周就收到了csdn相关人员的参会确认电话&#xff0c;今天早上查收邮件没有收到相关信息&#xff0c;没想到来了一个电话&#xff0c;提醒别忘了周五参会&#xff0c;她说补发邮件已不大可能&#xff0c;可以带名片过去&#xff0c;那边有名单可确认。也好&#xff0c;只…

JavaScript 作用域链

作用域链是什么 函数在使用一个变量的时候&#xff0c;会查找这个变量&#xff0c;如果自己内部没有&#xff0c;就会向上找&#xff0c;这个查找的过程就叫 作用域链 普通函数的作用域链 1、创建一个函数fn()时&#xff0c;会创建一个包含全局变量的作用域链&#xff0c;保存…

2007微软MVP获得连任

今天早上收到了这封邮件&#xff1a;“[MVP] 热烈祝贺&#xff01;您已经获得 Microsoft MVP 奖励”&#xff0c;心情还是满不错的&#xff0c;在blog上留个纪念。----------------------------------------------------------------------------------------------尊敬的 Hong…

JavaScript 事件冒泡和事件捕获

什么是事件 JavaScript和HTML之间的交互是通过事件实现的。 事件&#xff1a;就是文档或浏览器窗口发生的一些特定的交互瞬间。 监听器&#xff08;或事件处理程序&#xff09;&#xff1a;预定事件&#xff0c;以便事件发生时执行相应的代码。 通俗的说&#xff0c;这种模型其…

初涉龙芯I/龙芯II

最近想开发一款高性能的嵌入式多媒体系统&#xff0c;目前Arm9系列的嵌入式平台多媒体性能欠佳。最近龙芯在嵌入式方面投入了很大的精力&#xff08;毕竟做通用PC&#xff0c;龙芯的性能还是远远不够&#xff0c;目前的性能不过相当于奔腾800&#xff09;&#xff0c;听我们老总…

javaScript 的 splice和slice,map和forEach、 filter()、reduce()的区别

map和forEach 参数&#xff1a;都是&#xff08;当前值&#xff0c;当前对应的index&#xff0c;当前调用的array&#xff09; 可以不写全 map 根据每个元素的转换&#xff0c;组成新数组原数组不变返回新数组注意一个笔试题&#xff1a;参考回调函数 ["1", &qu…

.net精简框架集下的ini文件读取(C#)

无论是.net框架集还是.net精简框架集都能非常完美的支持XML文件&#xff0c;并且微软也强烈建议用xml文件取代ini文件&#xff0c;但是在工控领域大部分的工程都是采用ini文件配置的系统信息的。以前的隧道管理系统是采用ini文件配置信息的&#xff0c;为了实现兼容&#xff0c…

什么事堆?什么是栈? 它们之间有什么区别和联系

堆和栈的概念存在于数据结构中和操作系统内存中。 在数据结构中&#xff0c;栈中数据的存取方式为先进后出。 堆是一个优先队列&#xff0c;是按优先级来进行排序的&#xff0c;优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。 在操作系统中&#xff0c;内存被分…

也谈正则表达式

其实很早就知道了正则表达式&#xff0c;在集成VBScript脚本的时候&#xff0c;就看到了该功能&#xff0c;不过那时觉得很难&#xff0c;觉得也派不上什么用场&#xff0c;所以也没有过多关注。最近看了孟岩老师的关于正则表达式讲解&#xff0c;有一种学习正则表达式的冲动&a…

内部属性[[class]]是什么?

所有的 typeof 返回值为 “object” 的对象&#xff08;如数组&#xff09;都包含一个内部属性 [[class]] (我们可以把它看作一个内部的分类)&#xff0c;而非传统的面向对象意义上的类&#xff09;。 这个属性无法直接被访问&#xff0c;一般通过 object。prototype。toStrin…

WinCE5.0平台下的Moxa DA66x设备应用开发心得

最近金日隧道广告系统的通信系统要升级&#xff0c;用Moxa的DA66x设备取代原先的Moxa5630通信模块&#xff0c;由于DA66x内嵌了WinCE5.0系统&#xff0c;系统的功能可以有很大的发挥余地。DA66x这款产品功能很强&#xff0c;可以说WinCE5.0在工业通信领域被用到了极致&#xff…

介绍 js 有哪些内置对象?

涉及的知识点 js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函 数对象。一般我们经常用到的如全局变量值 NaN、undefined&#xff0c;全局函数如 parseInt()、parseFloat() 用来实例化对象的构 造函数…

Windows Vista不兼容VS2005(需打补丁)

在Windows Vista平台上直接安装VS2005&#xff0c;没有想到安装程序直接就提示Vista与VS2005存在已知的兼容问题&#xff0c;看来需要打SP1补丁(该补丁虽然下载了&#xff0c;但是在XP安装巨慢&#xff0c;不知道在vista上品行如何&#xff09;&#xff0c;此外SQL Server 2005…

解构赋值笔记

数组的解构赋值 let arr [1,2,3,4,5] let [item1,item2] arr console.log(item1,item2) // 1,2 数据2之后的345被垃圾回收机制回收 let [item1, item2,...list] arr console.log(list) // 3,4,5 利用... 把其他的数据赋值到list上 ""..."" 用于取出…
最新文章