這段時間用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,如需轉載請自行聯系原作者