zoukankan      html  css  js  c++  java
  • Net 平台SerialPort类内部实现探秘[转]

    这段时间用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,@http://www.cnblogs.com/sail/admin/file://./表示串口超过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函数),
  • 相关阅读:
    8.8集训
    8.7集训
    8.6集训
    poj 2492
    埃氏筛法
    并查集板子
    2018级程序能力实训第二次上机考试
    网络流
    活动安排问题
    等价类
  • 原文地址:https://www.cnblogs.com/sail/p/2061933.html
Copyright © 2011-2022 走看看