C#串口操作

前一阵,从国外网站看到一个用C#来操作串口的类。下载下来试了一下,觉得不错。共享一下。

/*
* Author: Marcus Lorentzon, 2001
*         d98malor@dtek.chalmers.se

* Freeware: Please do not remove this header

* File: SerialStream.cs

* Description: Implements a Stream for asynchronous
*              transfers and COMM. Stream version.
*
* Version: 2.4

*/

#region Using

using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;

#endregion Using

namespace LoMaN.IO {

    
public class SerialStream : Stream {
        
        
#region Attributes

        
private IOCompletionCallback m_IOCompletionCallback;
        
private IntPtr m_hFile = IntPtr.Zero;
        
private string m_sPort;
        
private bool m_bRead;
        
private bool m_bWrite;

        
#endregion Attributes

        
#region Properties

        
public string Port {
            
get {
                
return m_sPort;
            }
            
set {
                
if (m_sPort != value) {
                    Close();
                    Open(value);
                }
            }
        }

        
public override bool CanRead {
            
get {
                
return m_bRead;
            }
        }

        
public override bool CanWrite {
            
get {
                
return m_bWrite;
            }
        }

        
public override bool CanSeek {
            
get {
                
return false;
            }
        }

        
public bool Closed  {
            
get {
                
return m_hFile.ToInt32()  0;
            }
        }

        
public bool Dsr {
            
get {
                
uint status;
                
if (!GetCommModemStatus(m_hFile, out status)) {
                    
throw new Win32Exception();
                }
                
return (status & MS_DSR_ON) > 0;
            }
        }

        
public bool Ring {
            
get {
                
uint status;
                
if (!GetCommModemStatus(m_hFile, out status)) {
                    
throw new Win32Exception();
                }
                
return (status & MS_RING_ON) > 0;
            }
        }

        
public bool Rlsd {
            
get {
                
uint status;
                
if (!GetCommModemStatus(m_hFile, out status)) {
                    
throw new Win32Exception();
                }
                
return (status & MS_RLSD_ON) > 0;
            }
        }

        
#endregion Properties

        
#region Constructors

        
public SerialStream() : this(FileAccess.ReadWrite) {
        }

        
public SerialStream(FileAccess access) {
            m_bRead  
= ((int)access & (int)FileAccess.Read) != 0;
            m_bWrite 
= ((int)access & (int)FileAccess.Write) != 0;
            
unsafe {
                m_IOCompletionCallback 
= new IOCompletionCallback(AsyncFSCallback);
            }
        }

        
public SerialStream(string port) : this(FileAccess.ReadWrite) {
            Open(port);
        }

        
public SerialStream(string port, FileAccess access) : this(access) {
            Open(port);
        }

        
#endregion Constructors

        
#region Methods

        
public void Open(string port) {
            
if (m_hFile != IntPtr.Zero) {
                
throw new IOException("Stream already opened.");
            }
            m_sPort 
= port;
            m_hFile 
= CreateFile(port, (uint)((m_bRead?GENERIC_READ:0)|(m_bWrite?GENERIC_WRITE:0)), 00, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
            
if (m_hFile.ToInt32() == INVALID_HANDLE_VALUE) {
                m_hFile 
= IntPtr.Zero;
                
throw new FileNotFoundException("Unable to open " + port);
            }

            ThreadPool.BindHandle(m_hFile);

            SetTimeouts(
00000);
        }

        
public override void Close() {
            CloseHandle(m_hFile);
            m_hFile 
= IntPtr.Zero;
            m_sPort 
= null;
        }

        
public IAsyncResult BeginRead(byte[] buffer) {
            
return BeginRead(buffer, 0, buffer.Length, nullnull);
        }

        
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) {
            GCHandle gchBuffer 
= GCHandle.Alloc(buffer, GCHandleType.Pinned);
            SerialAsyncResult sar 
= new SerialAsyncResult(this, state, callback, true, gchBuffer);
            Overlapped ov 
= new Overlapped(00, sar.AsyncWaitHandle.Handle.ToInt32(), sar);
            
unsafe {
                NativeOverlapped
* nov = ov.Pack(m_IOCompletionCallback);
                
byte* data = (byte*)((int)gchBuffer.AddrOfPinnedObject() + offset);

                
uint read = 0;
                
if (ReadFile(m_hFile, data, (uint)count, out read, nov)) {
                    sar.m_bCompletedSynchronously 
= true;
                    
return sar;
                }
                
else if (GetLastError() == ERROR_IO_PENDING) {
                    
return sar;
                }
                
else
                    
throw new Exception("Unable to initialize read. Errorcode: " + GetLastError().ToString());
            }
        }

        
public IAsyncResult BeginWrite(byte[] buffer) {
            
return BeginWrite(buffer, 0, buffer.Length, nullnull);
        }

        
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) {
            GCHandle gchBuffer 
= GCHandle.Alloc(buffer, GCHandleType.Pinned);
            SerialAsyncResult sar 
= new SerialAsyncResult(this, state, callback, false, gchBuffer);
            Overlapped ov 
= new Overlapped(00, sar.AsyncWaitHandle.Handle.ToInt32(), sar);
            
unsafe {
                NativeOverlapped
* nov = ov.Pack(m_IOCompletionCallback);
                
byte* data = (byte*)((int)gchBuffer.AddrOfPinnedObject() + offset);

                
uint written = 0;
                
if (WriteFile(m_hFile, data, (uint)count, out written, nov)) {
                    sar.m_bCompletedSynchronously 
= true;
                    
return sar;
                }
                
else if (GetLastError() == ERROR_IO_PENDING) {
                    
return sar;
                }
                
else
                    
throw new Exception("Unable to initialize write. Errorcode: " + GetLastError().ToString());
            }
        }

        
private int EndOperation(IAsyncResult asyncResult, bool isRead) {
            SerialAsyncResult sar 
= (SerialAsyncResult)asyncResult;
            
if (sar.m_bIsRead != isRead)
                
throw new IOException("Invalid parameter: IAsyncResult is not from a " + (isRead ? "read" : "write"));
            
if (sar.EndOperationCalled) {
                
throw new IOException("End" + (isRead ? "Read" : "Write"+ " called twice for the same operation.");
            }
            
else {
                sar.m_bEndOperationCalled 
= true;
            }

            
while (!sar.m_bCompleted) {
                sar.AsyncWaitHandle.WaitOne();
            }

            sar.Dispose();

            
if (sar.m_nErrorCode != ERROR_SUCCESS && sar.m_nErrorCode != ERROR_OPERATION_ABORTED) {
                
throw new IOException("Operation finished with errorcode: " + sar.m_nErrorCode);
            }

            
return sar.m_nReadWritten;
        }
        
        
public override int EndRead(IAsyncResult asyncResult) {
            
return EndOperation(asyncResult, true);
        }
        
        
public override void EndWrite(IAsyncResult asyncResult) {
            EndOperation(asyncResult, 
false);
        }

        
public int EndWriteEx(IAsyncResult asyncResult) {
            
return EndOperation(asyncResult, false);
        }

        
public override int Read(byte[] buffer, int offset, int count) {
            
return EndRead(BeginRead(buffer, offset, count, nullnull));
        }

        
public override void Write(byte[] buffer, int offset, int count) {
            EndWrite(BeginWrite(buffer, offset, count, 
nullnull));
        }

        
public int WriteEx(byte[] buffer, int offset, int count) {
            
return EndWriteEx(BeginWrite(buffer, offset, count, nullnull));
        }

        
public int Read(byte[] buffer) {
            
return EndRead(BeginRead(buffer, 0, buffer.Length, nullnull));
        }

        
public int Write(byte[] buffer) {
            
return EndOperation(BeginWrite(buffer, 0, buffer.Length, nullnull), false);
        }

        
public override void Flush() {
            FlushFileBuffers(m_hFile);
        }

        
public bool PurgeRead() {
            
return PurgeComm(m_hFile, PURGE_RXCLEAR);
        }

        
public bool PurgeWrite() {
            
return PurgeComm(m_hFile, PURGE_TXCLEAR);
        }

        
public bool Purge() {
            
return PurgeRead() && PurgeWrite();
        }

        
public bool CancelRead() {
            
return PurgeComm(m_hFile, PURGE_RXABORT);
        }

        
public bool CancelWrite() {
            
return PurgeComm(m_hFile, PURGE_TXABORT);
        }

        
public bool CancelAll() {
            
return CancelRead() && CancelWrite();
        }

        
public override void SetLength(long nLength) {
            
throw new NotSupportedException("SetLength isn't supported on serial ports.");
        }

        
public override long Seek(long offset, SeekOrigin origin) {
            
throw new NotSupportedException("Seek isn't supported on serial ports.");
        }

        
public void SetTimeouts(int ReadIntervalTimeout,
                                
int ReadTotalTimeoutMultiplier,
                                
int ReadTotalTimeoutConstant, 
                                
int WriteTotalTimeoutMultiplier,
                                
int WriteTotalTimeoutConstant) {
            SerialTimeouts Timeouts 
= new SerialTimeouts(ReadIntervalTimeout,
                                                         ReadTotalTimeoutMultiplier,
                                                         ReadTotalTimeoutConstant, 
                                                         WriteTotalTimeoutMultiplier,
                                                         WriteTotalTimeoutConstant);
            
unsafe { SetCommTimeouts(m_hFile, ref Timeouts); }
        }

        
public bool SetPortSettings(uint baudrate) {
            
return SetPortSettings(baudrate, FlowControl.Hardware);
        }

        
public bool SetPortSettings(uint baudrate, FlowControl flowControl) {
            
return SetPortSettings(baudrate, flowControl, Parity.None);
        }

        
public bool SetPortSettings(uint baudrate, FlowControl flowControl, Parity parity) {
            
return SetPortSettings(baudrate, flowControl, parity, 8, StopBits.One);
        }

        
public bool SetPortSettings(uint baudrate, FlowControl flowControl, Parity parity, byte databits, StopBits stopbits) {
            
unsafe {
                DCB dcb 
= new DCB();
                dcb.DCBlength 
= sizeof(DCB);
                dcb.BaudRate 
= baudrate;
                dcb.ByteSize 
= databits;
                dcb.StopBits 
= (byte)stopbits;
                dcb.Parity 
= (byte)parity;
                dcb.fParity 
= (parity > 0)? 1U : 0U;
                dcb.fBinary 
= dcb.fDtrControl = dcb.fTXContinueOnXoff = 1;
                dcb.fOutxCtsFlow 
= dcb.fAbortOnError = (flowControl == FlowControl.Hardware)? 1U : 0U;
                dcb.fOutX 
= dcb.fInX = (flowControl == FlowControl.XOnXOff)? 1U : 0U;
                dcb.fRtsControl 
= (flowControl == FlowControl.Hardware)? 2U : 1U;
                dcb.XonLim 
= 2048;
                dcb.XoffLim 
= 512;
                dcb.XonChar 
= 0x11// Ctrl-Q
                dcb.XoffChar = 0x13// Ctrl-S
                return SetCommState(m_hFile, ref dcb);
            }
        }

        
public bool SetPortSettings(DCB dcb) {
            
return SetCommState(m_hFile, ref dcb);
        }

        
public bool GetPortSettings(out DCB dcb) {
            
unsafe {
                DCB dcb2 
= new DCB();
                dcb2.DCBlength 
= sizeof(DCB);
                
bool ret = GetCommState(m_hFile, ref dcb2);
                dcb 
= dcb2;
                
return ret;
            }
        }

        
public bool SetXOn() {
            
return EscapeCommFunction(m_hFile, SETXON);
        }

        
public bool SetXOff() {
            
return EscapeCommFunction(m_hFile, SETXOFF);
        }

        
private unsafe void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) {
            SerialAsyncResult sar 
= (SerialAsyncResult)Overlapped.Unpack(pOverlapped).AsyncResult;

            sar.m_nErrorCode 
= errorCode;
            sar.m_nReadWritten 
= (int)numBytes;
            sar.m_bCompleted 
= true;

            
if (sar.Callback != null)
                sar.Callback.Invoke(sar);

            Overlapped.Free(pOverlapped);
        }

        
#endregion Methods

        
#region Constants

        
private const uint PURGE_TXABORT = 0x0001;  // Kill the pending/current writes to the comm port.
        private const uint PURGE_RXABORT = 0x0002;  // Kill the pending/current reads to the comm port.
        private const uint PURGE_TXCLEAR = 0x0004;  // Kill the transmit queue if there.
        private const uint PURGE_RXCLEAR = 0x0008;  // Kill the typeahead buffer if there.

        
private const uint SETXOFF  = 1;    // Simulate XOFF received
        private const uint SETXON   = 2;    // Simulate XON received
        private const uint SETRTS    = 3;    // Set RTS high
        private const uint CLRRTS    = 4;    // Set RTS low
        private const uint SETDTR    = 5;    // Set DTR high
        private const uint CLRDTR    = 6;    // Set DTR low
        private const uint SETBREAK    = 8;    // Set the device break line.
        private const uint CLRBREAK    = 9;    // Clear the device break line.

        
private const uint MS_CTS_ON  = 0x0010;
        
private const uint MS_DSR_ON  = 0x0020;
        
private const uint MS_RING_ON = 0x0040;
        
private const uint MS_RLSD_ON = 0x0080;

        
private const uint FILE_FLAG_OVERLAPPED = 0x40000000;

        
private const uint OPEN_EXISTING = 3;

        
private const int  INVALID_HANDLE_VALUE = -1;

        
private const uint GENERIC_READ = 0x80000000;
        
private const uint GENERIC_WRITE = 0x40000000;

        
private const uint ERROR_SUCCESS = 0;
        
private const uint ERROR_OPERATION_ABORTED = 995;
        
private const uint ERROR_IO_PENDING = 997;

        
#endregion Constants

        
#region Enums

        
public enum Parity {None, Odd, Even, Mark, Space};
        
public enum StopBits {One, OneAndHalf, Two};
        
public enum FlowControl {None, XOnXOff, Hardware};

        
#endregion Enums

        
#region Classes

        [StructLayout(LayoutKind.Sequential)]
        
public struct DCB {

            
#region Attributes

            
public int DCBlength;
            
public uint BaudRate;
            
public uint Flags;
            
public ushort wReserved;
            
public ushort XonLim;
            
public ushort XoffLim;
            
public byte ByteSize;
            
public byte Parity;
            
public byte StopBits;
            
public sbyte XonChar;
            
public sbyte XoffChar;
            
public sbyte ErrorChar;
            
public sbyte EofChar;
            
public sbyte EvtChar;
            
public ushort wReserved1;

            
#endregion Attributes

            
#region Properties

            
public uint fBinary { get { return Flags&0x0001; } 
                                  
set { Flags = Flags & ~1U | value; } }
            
public uint fParity { get { return (Flags>>1)&1; }
                                  
set { Flags = Flags & ~(1U >2)&1; }
                                  
set { Flags = Flags & ~(1U >3)&1; }
                                  
set { Flags = Flags & ~(1U >4)&3; }
                                  
set { Flags = Flags & ~(3U >6)&1; }
                                  
set { Flags = Flags & ~(1U >7)&1; }
                                  
set { Flags = Flags & ~(1U >8)&1; }
                                  
set { Flags = Flags & ~(1U >9)&1; }
                                  
set { Flags = Flags & ~(1U >10)&1; }
                                  
set { Flags = Flags & ~(1U >11)&1; }
                                  
set { Flags = Flags & ~(1U >12)&3; }
                                  
set { Flags = Flags & ~(3U >14)&1; }
                                  
set { Flags = Flags & ~(1U << 14| (value << 14); } }

            
#endregion Properties

            
#region Methods

            
public override string ToString() {
                
return "DCBlength: " + DCBlength + "\r\n" +
                    
"BaudRate: " + BaudRate + "\r\n" +
                    
"fBinary: " + fBinary + "\r\n" +
                    
"fParity: " + fParity + "\r\n" +
                    
"fOutxCtsFlow: " + fOutxCtsFlow + "\r\n" +
                    
"fOutxDsrFlow: " + fOutxDsrFlow + "\r\n" +
                    
"fDtrControl: " + fDtrControl + "\r\n" +
                    
"fDsrSensitivity: " + fDsrSensitivity + "\r\n" +
                    
"fTXContinueOnXoff: " + fTXContinueOnXoff + "\r\n" +
                    
"fOutX: " + fOutX + "\r\n" +
                    
"fInX: " + fInX + "\r\n" +
                    
"fErrorChar: " + fErrorChar + "\r\n" +
                    
"fNull: " + fNull + "\r\n" +
                    
"fRtsControl: " + fRtsControl + "\r\n" +
                    
"fAbortOnError: " + fAbortOnError + "\r\n" +
                    
"XonLim: " + XonLim + "\r\n" +
                    
"XoffLim: " + XoffLim + "\r\n" +
                    
"ByteSize: " + ByteSize + "\r\n" +
                    
"Parity: " + Parity + "\r\n" +
                    
"StopBits: " + StopBits + "\r\n" +
                    
"XonChar: " + XonChar + "\r\n" +
                    
"XoffChar: " + XoffChar + "\r\n" +
                    
"EofChar: " + EofChar + "\r\n" +
                    
"EvtChar: " + EvtChar + "\r\n";
            }

            
#endregion Methods
        }

        
private class SerialAsyncResult : IAsyncResult, IDisposable {

            
#region Attributes

            
internal bool m_bEndOperationCalled = false;
            
internal bool m_bIsRead;
            
internal int m_nReadWritten = 0;
            
internal bool m_bCompleted = false;
            
internal bool m_bCompletedSynchronously = false;
            
internal uint m_nErrorCode = ERROR_SUCCESS;

            
private object m_AsyncObject;
            
private object m_StateObject;
            
private ManualResetEvent m_WaitHandle = new ManualResetEvent(false);
            
private AsyncCallback m_Callback;
            
private GCHandle m_gchBuffer;

            
#endregion Attributes

            
#region Properties

            
internal bool EndOperationCalled { get { return m_bEndOperationCalled; } }

            
public bool IsCompleted { get { return m_bCompleted; } }

            
public bool CompletedSynchronously { get { return m_bCompletedSynchronously; } }

            
public object AsyncObject { get { return m_AsyncObject; } }

            
public object AsyncState { get { return m_StateObject; } }

            
public WaitHandle AsyncWaitHandle { get { return m_WaitHandle; } }
            
internal ManualResetEvent WaitHandle { get { return m_WaitHandle; } }

            
public AsyncCallback Callback { get { return m_Callback; } }

            
#endregion Properties

            
#region Constructors

            
public SerialAsyncResult(object asyncObject,
                
object stateObject, 
                AsyncCallback callback, 
                
bool bIsRead, 
                GCHandle gchBuffer) {

                m_AsyncObject 
= asyncObject;
                m_StateObject 
= stateObject;
                m_Callback 
= callback;
                m_bIsRead 
= bIsRead;
                m_gchBuffer 
= gchBuffer;
            }

            
#endregion Constructors

            
#region Methods

            
public void Dispose() {
                m_WaitHandle.Close();
                m_gchBuffer.Free();
            }

            
#endregion Methods
        }

        
#endregion Classes

        
#region Imports

        [DllImport(
"kernel32.dll", EntryPoint="CreateFileW",  SetLastError=true,
            CharSet
=CharSet.Unicode, ExactSpelling=true)]
        
static extern IntPtr CreateFile(string filename, uint access, uint sharemode, uint security_attributes, uint creation, uint flags, uint template);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool CloseHandle(IntPtr handle);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern unsafe bool ReadFile(IntPtr hFile, byte* lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern unsafe bool WriteFile(IntPtr hFile, byte* lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, NativeOverlapped* lpOverlapped);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool SetCommTimeouts(IntPtr hFile, ref SerialTimeouts lpCommTimeouts);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool SetCommState(IntPtr hFile, ref DCB lpDCB);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool GetCommState(IntPtr hFile, ref DCB lpDCB);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool BuildCommDCB(string def, ref DCB lpDCB);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern int GetLastError();

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool FlushFileBuffers(IntPtr hFile);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool PurgeComm(IntPtr hFile, uint dwFlags);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool EscapeCommFunction(IntPtr hFile, uint dwFunc);

        [DllImport(
"kernel32.dll", SetLastError=true)]
        
static extern bool GetCommModemStatus(IntPtr hFile, out uint modemStat);

        
#endregion Imports
    }

    [StructLayout(LayoutKind.Sequential)]
    
public struct SerialTimeouts {

        
#region Attributes

        
public int ReadIntervalTimeout;
        
public int ReadTotalTimeoutMultiplier;
        
public int ReadTotalTimeoutConstant;
        
public int WriteTotalTimeoutMultiplier;
        
public int WriteTotalTimeoutConstant;

        
#endregion Attributes

        
#region Constructors

        
public SerialTimeouts(int r1, int r2, int r3, int w1, int w2) {
            ReadIntervalTimeout 
= r1;
            ReadTotalTimeoutMultiplier 
= r2;
            ReadTotalTimeoutConstant 
= r3;
            WriteTotalTimeoutMultiplier 
= w1;
            WriteTotalTimeoutConstant 
= w2;
        }

        
#endregion Constructors

        
#region Methods

        
public override string ToString() {
            
return "ReadIntervalTimeout: " + ReadIntervalTimeout + "\r\n" +
                   
"ReadTotalTimeoutMultiplier: " + ReadTotalTimeoutMultiplier + "\r\n" +
                   
"ReadTotalTimeoutConstant: " + ReadTotalTimeoutConstant + "\r\n" +
                   
"WriteTotalTimeoutMultiplier: " + WriteTotalTimeoutMultiplier + "\r\n" +
                   
"WriteTotalTimeoutConstant: " + WriteTotalTimeoutConstant + "\r\n";
        }

        
#endregion Methods
    }
}



using System;
using System.IO;
using System.Threading;

using LoMaN.IO;

namespace SerialStreamReader {

    
class App {

        
// The main serial stream
        static SerialStream ss;

        [STAThread]
        
static void Main(string[] args) {

            
// Create a serial port
            ss = new SerialStream();
            
try {
                ss.Open(
"COM4"); //我对猫进行了调用
            }
            
catch (Exception e) {
                Console.WriteLine(
"Error: " + e.Message);
                
return;
            }

            
// Set port settings
            ss.SetPortSettings(9600);

            
// Set timeout so read ends after 20ms of silence after a response
            ss.SetTimeouts(200000);

            
// Create the StreamWriter used to send commands
            StreamWriter sw = new StreamWriter(ss, System.Text.Encoding.ASCII);

            
// Create the Thread used to read responses
            Thread responseReaderThread = new Thread(new ThreadStart(ReadResponseThread));
            responseReaderThread.Start();

            
// Read all returned lines
            for (;;) {
                
// Read command from console
                string command = Console.ReadLine();

                
// Check for exit command
                if (command.Trim().ToLower() == "exit") {
                    responseReaderThread.Abort();
                    
break;
                }

                
// Write command to modem
                sw.WriteLine(command);
                sw.Flush();
            }
        }

        
// Main loop for reading responses
        static void ReadResponseThread() {
            StreamReader sr 
= new StreamReader(ss, System.Text.Encoding.ASCII);
            
try {
                
for (;;) {
                    
// Read response from modem
                    string response = sr.ReadLine();
                    Console.WriteLine(
"Response: " + response);
                }
            }
            
catch (ThreadAbortException) {
            }
        }
    }
}

 

程序运行后,你可以对设备进行指令操作。(具体设备接受的指令)
 

posted @ 2010-06-07 15:22  逆时针  阅读(2601)  评论(0编辑  收藏  举报