天天看點

.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相關的羅列。

    看該函數的構造函數中的代碼:

        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函數),采用

本文轉自yefanqiu51CTO部落格,原文連結:http://blog.51cto.com/yfsoft/323771,如需轉載請自行聯系原作者

繼續閱讀