首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 底層串口通訊類—— Win32 API 直接操作 COM 口

     編輯:關於C#

.NET SerialPort 類雖方便,但在高併發、特殊波特率、精確超時控制、Windows CE/嵌入式平台上仍有局限。本文提供基於 kernel32.dll 的完整封裝:CreateFile、DCB 結構體配置、COMMTIMEOUTS、ReadFile/WriteFile 異步重疊 I/O、十六進制工具類,適合工業級穩定通訊。

一、核心 P/Invoke 簽名與結構體

using System;
using System.Runtime.InteropServices;

namespace JustinIO
{
    public class CommPort
    {
        // 公開配置屬性
        public string PortName;      // "COM1"
        public int BaudRate = 9600;
        public byte ByteSize = 8;
        public byte Parity = 0;      // 0=None,1=Odd,2=Even,3=Mark,4=Space
        public byte StopBits = 0;    // 0=1, 1=1.5, 2=2
        public int ReadTimeout = 1000;

        private int _hComm = -1;
        public bool IsOpen => _hComm != -1;

        // Win32 常量
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const int OPEN_EXISTING = 3;
        private const int INVALID_HANDLE_VALUE = -1;

        // DCB 結構體(按順序佈局,對應 C 結構體)
        [StructLayout(LayoutKind.Sequential)]
        public struct DCB
        {
            public int DCBlength;
            public int BaudRate;
            public int fBinary; public int fParity; public int fOutxCtsFlow; public int fOutxDsrFlow;
            public int fDtrControl; public int fDsrSensitivity; public int fTXContinueOnXoff;
            public int fOutX; public int fInX; public int fErrorChar; public int fNull;
            public int fRtsControl; public int fAbortOnError; public int fDummy2;
            public uint flags;
            public ushort wReserved;
            public ushort XonLim; public ushort XoffLim;
            public byte ByteSize; public byte Parity; public byte StopBits;
            public char XonChar; public char XoffChar; public char ErrorChar;
            public char EofChar; public char EvtChar;
            public ushort wReserved1;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct COMMTIMEOUTS
        {
            public int ReadIntervalTimeout;
            public int ReadTotalTimeoutMultiplier;
            public int ReadTotalTimeoutConstant;
            public int WriteTotalTimeoutMultiplier;
            public int WriteTotalTimeoutConstant;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct OVERLAPPED
        {
            public int Internal; public int InternalHigh; public int Offset; public int OffsetHigh; public int hEvent;
        }

        // API 導入
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern int CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode,
            int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);

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

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

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

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool GetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);

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

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool ReadFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToRead,
            ref int lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToWrite,
            ref int lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(int hObject);

        [DllImport("kernel32.dll")]
        private static extern uint GetLastError();
    }
}

二、打開/配置/關閉串口

public void Open()
{
    var dcb = new DCB { DCBlength = Marshal.SizeOf() };
    var timeouts = new COMMTIMEOUTS();

    // 1. 打開設備句柄
    _hComm = CreateFile(PortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
    if (_hComm == INVALID_HANDLE_VALUE)
        throw new ApplicationException($"無法打開串口 {PortName},錯誤碼: {GetLastError()}");

    // 2. 設置超時
    GetCommTimeouts(_hComm, ref timeouts);
    timeouts.ReadTotalTimeoutConstant = ReadTimeout;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    if (!SetCommTimeouts(_hComm, ref timeouts))
        throw new ApplicationException("設置超時失敗");

    // 3. 獲取當前 DCB 並修改
    if (!GetCommState(_hComm, ref dcb))
        throw new ApplicationException("獲取 DCB 失敗");

    dcb.BaudRate = BaudRate;
    dcb.flags = 1; // fBinary = 1
    if (Parity > 0) dcb.flags |= 2; // fParity = 1
    dcb.Parity = Parity;
    dcb.ByteSize = ByteSize;
    dcb.StopBits = StopBits;

    if (!SetCommState(_hComm, ref dcb))
        throw new ApplicationException($"設置 DCB 失敗,錯誤碼: {GetLastError()}");

    // 可選:驗證設置
    // var dcb2 = new DCB(); GetCommState(_hComm, ref dcb2);
}
public void Close()
{
    if (_hComm != INVALID_HANDLE_VALUE)
    {
        CloseHandle(_hComm);
        _hComm = INVALID_HANDLE_VALUE;
    }
}

三、讀寫操作(同步阻塞模式)

public byte[] Read(int count)
{
    if (!IsOpen) throw new InvalidOperationException("串口未打開");
    var buffer = new byte[count];
    var overlapped = new OVERLAPPED();
    int bytesRead = 0;
    if (!ReadFile(_hComm, buffer, count, ref bytesRead, ref overlapped))
        throw new ApplicationException($"讀取失敗,錯誤碼: {GetLastError()}");
    if (bytesRead < count) Array.Resize(ref buffer, bytesRead);
    return buffer;
}

public void Write(byte[] data)
{
    if (!IsOpen) throw new InvalidOperationException("串口未打開");
    var overlapped = new OVERLAPPED();
    int written = 0;
    if (!WriteFile(_hComm, data, data.Length, ref written, ref overlapped))
        throw new ApplicationException($"寫入失敗,錯誤碼: {GetLastError()}");
    if (written != data.Length) throw new ApplicationException("寫入字節數不足");
}

四、十六進制轉換工具類

public static class HexConverter
{
    public static string ToHexString(byte[] bytes, string separator = " ")
    {
        if (bytes == null) return "";
        var sb = new StringBuilder(bytes.Length * 3);
        foreach (byte b in bytes) sb.AppendFormat("{0:X2}{1}", b, separator);
        return sb.ToString().TrimEnd();
    }

    public static byte[] FromHexString(string hex)
    {
        if (string.IsNullOrWhiteSpace(hex)) return Array.Empty();
        var parts = hex.Split(new[] { ' ', '-', ':', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
        var bytes = new byte[parts.Length];
        for (int i = 0; i < parts.Length; i++)
            bytes[i] = Convert.ToByte(parts[i], 16);
        return bytes;
    }
}

五、使用示例

var port = new CommPort
{
    PortName = "COM3",
    BaudRate = 115200,
    ByteSize = 8,
    Parity = 0,
    StopBits = 0,
    ReadTimeout = 2000
};

try
{
    port.Open();
    // 發送指令:01 03 00 00 00 02 CRC
    byte[] cmd = HexConverter.FromHexString("01 03 00 00 00 02 C4 0B");
    port.Write(cmd);

    // 讀取回應(假設 9 字節)
    byte[] resp = port.Read(9);
    Console.WriteLine("收到: " + HexConverter.ToHexString(resp));
}
catch (Exception ex)
{
    Console.WriteLine("錯誤: " + ex.Message);
}
finally
{
    port.Close();
}

六、進階:異步重疊 I/O(高性能)

將 CreateFile 旗標加上 FILE_FLAG_OVERLAPPED (0x40000000),ReadFile/WriteFile 傳入事件句柄的 OVERLAPPED,配合 GetOverlappedResult/WaitForSingleObject 實現真正非阻塞。代碼較長,此處略過,關鍵點:

  • OVERLAPPED.hEvent = CreateEvent(...)
  • 發起 I/O 後立即返回,GetLastError()==ERROR_IO_PENDING (997) 為正常
  • 用 WaitForSingleObject(event, timeout) 等待完成
  • 調用 GetOverlappedResult 獲取實際傳輸字節數

七、總結

Win32 串口類繞過 .NET SerialPort 封裝層,直接控制 DCB、COMMTIMEOUTS、重疊 I/O,適用於:非標準波特率、嚴格超時控制、Windows CE/嵌入式、高可靠工業協議。缺點是代碼量大、需自行管理非托管資源、跨平台困難。現代 .NET Core 3.0+ System.IO.Ports.SerialPort 已大幅改進,一般場景優先使用托管類,特殊場景再落地本類。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved