C#如何检查COM(串口)端口是否已打开

24

有没有一种简单的编程方法可以检查串口是否已经打开/正在使用?

通常我会使用以下方法:

try
{
    // open port
}
catch (Exception ex)
{
    // handle the exception
}

然而,我希望能够通过编程来检查,以便我可以尝试使用另一个COM端口或类似的东西。

9个回答

16

前段时间我需要寻找一种类似的方法,用于搜索设备。

我获得了可用COM端口的列表,然后简单地迭代这些端口,如果没有出现异常,我尝试与设备通信。有点粗糙,但有效。

var portNames = SerialPort.GetPortNames();

foreach(var port in portNames) {
    //Try for every portName and break on the first working
}

6
我真的希望有一个更加“优雅”的解决方案……但是当其他方法都失败时,“使用任何能使程序正常工作的手段”! - TK.
1
为了让不了解SerialPort行为的人清楚,上面的答案并没有使用port.Open,它只检查调用者是否已经打开了端口,而不是说其他应用程序是否正在使用该端口。要检测这一点,必须尝试通过端口进行通信并抛出异常 - 端口正在使用中,拒绝访问。 - Ken
1
我不喜欢这种“暴力破解”的方法。我认为这会清空所有COM端口上的硬件接收缓冲区。 - Kamil

15

我是这样做的:

      [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttrs, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

后来

        int dwFlagsAndAttributes = 0x40000000;

        var portName = "COM5";

        var isValid = SerialPort.GetPortNames().Any(x => string.Compare(x, portName, true) == 0);
        if (!isValid)
            throw new System.IO.IOException(string.Format("{0} port was not found", portName));

        //Borrowed from Microsoft's Serial Port Open Method :)
        SafeFileHandle hFile = CreateFile(@"\\.\" + portName, -1073741824, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, IntPtr.Zero);
        if (hFile.IsInvalid)
            throw new System.IO.IOException(string.Format("{0} port is already open", portName));

        hFile.Close();


        using (var serialPort = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One))
        {
            serialPort.Open();
        }

3
使用这个相较于在尝试使用SerialPort.Open时捕获异常有什么好处? - Joel McBeth
11
因为您不想让异常控制应用程序的流程。事先尝试处理问题而不是抛出异常并进行处理会更加清晰。 - Jeff
7
@Jeff: 但是你现在存在风险,如果有人在你检查串口和打开串口之间的瞬间打开了串口,你的程序就会崩溃。使用异常处理可以避免这种潜在灾难。 - whatsisname
2
我非常赞同@jcmcbeth的观点:这种方法存在竞态条件。 - vines
1
如果 hFile.IsInvalid 属性为 true 而不是抛出异常,是否有替代方法?如果我想继续并打开串口,该怎么办? - Alessio
显示剩余2条评论

1

我想打开下一个可用端口,方法如下。 请注意,这不是针对WPF而是Windows Forms的。 我使用可用的com端口填充了一个组合框。 然后我尝试打开第一个端口。如果失败了,我从组合框中选择下一个可用项。如果最终选择的索引没有改变,那么就没有可替代的com端口可用,我们会显示一条消息。

private void GetPortNames()
{
    comboBoxComPort.Items.Clear();
    foreach (string s in SerialPort.GetPortNames())
    {
        comboBoxComPort.Items.Add(s);
    }
    comboBoxComPort.SelectedIndex = 0;
}

private void OpenSerialPort()
{
    try
    {
        serialPort1.PortName = comboBoxComPort.SelectedItem.ToString();
        serialPort1.Open();
    }
    catch (Exception ex)
    {
        int SelectedIndex = comboBoxComPort.SelectedIndex;
        if (comboBoxComPort.SelectedIndex >= comboBoxComPort.Items.Count - 1)
        {
            comboBoxComPort.SelectedIndex = 0;
        }
        else
        {
            comboBoxComPort.SelectedIndex++;
        }
        if (comboBoxComPort.SelectedIndex == SelectedIndex)
        {
            buttonOpenClose.Text = "Open Port";
            MessageBox.Show("Error accessing port." + Environment.NewLine + ex.Message, "Port Error!!!", MessageBoxButtons.OK);
        }
        else
        {
            OpenSerialPort();
        }
    }

    if (serialPort1.IsOpen)
    {
        StartAsyncSerialReading();
    }
}

1

对于那些无法使用SerialPort.GetPortNames();的人,因为他们没有针对.net framework(就像我一样,我正在使用.Net Core而不是.Net Framework),这是我最终做的事情:

在命令提示符中,如果您键入mode,会得到类似于此的内容:

enter image description here

mode是一个可执行文件,位于C:\Windows\System32\mode.com。只需使用以下正则表达式解析该可执行文件的结果:

// Code that answers the question

var proc = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = @"C:\Windows\System32\mode.com",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

proc.Start();
proc.WaitForExit(4000); // wait up to 4 seconds. It usually takes less than a second

// get ports being used
var output = proc.StandardOutput.ReadToEnd();

现在,如果您想解析输出,这是我的做法:
List<string> comPortsBeingUsed = new List<string>();
Regex.Replace(output, @"(?xi) status [\s\w]+? (COM\d) \b ", regexCapture =>
{
    comPortsBeingUsed.Add(regexCapture.Groups[1].Value);
    return null;
});

foreach(var item in comPortsBeingUsed)
{
    Console.WriteLine($"COM port {item} is in use");
}

1
好主意。我今天才知道 mode.com。但是请注意,对于多语言使用,因为 mode.com 是本地化的,所以正则表达式可能会在 Windows 安装时失败,当“status”的翻译不同时。 (在德语中,它是相同的单词。) - Beauty
在我的命令行测试中,我得到了一个相反的结果。未使用的COM端口由mode.com列出,而已使用的COM端口则不可见。 - Beauty

0

我已经与这个问题斗争了几周了。感谢这里和https://www.dreamincode.net/forums/topic/91090-c%23-serial-port-unauthorizedaccessexception/网站上的建议。

最终,我想出了一个看起来可行的解决方案。

我正在开发的应用程序允许用户连接到USB设备并显示其中的数据。

我所遇到的问题。除了我正在编写的应用程序之外,我还使用另一个串行终端应用程序进行测试。有时我会忘记断开其他应用程序上使用的COM端口。如果我这样做,并尝试连接我正在编写的应用程序,我会收到“未经授权的访问异常”错误。除此之外,还会出现一些副作用,例如输出双倍的数据行以及关闭时应用程序锁定。

我的解决方案

感谢这里和其他网站上的建议,这是我的解决方案。

        private void checkAndFillPortNameList()
        {
           SerialPort _testingSerialPort;


            AvailablePortNamesFound.Clear();
            List<string> availablePortNames = new List<string>();//mySerial.GetAvailablePortNames();

            foreach (string portName in SerialPortDataAccess.GetAvailablePortNames())
            {
                try
                {
                    _testingSerialPort = new SerialPort(portName);
                    _testingSerialPort.Open();

                    if (_testingSerialPort.IsOpen)
                    {
                        availablePortNames.Add(portName);
                        _testingSerialPort.Close();
                    }
                }
                catch (Exception ex)
                {
 
                }
            }
            availablePortNames.Sort();
            AvailablePortNamesFound = new ObservableCollection<string>(availablePortNames);
        }

这个程序连接到一个组合框,其中包含可供选择的串口。如果某个串口已经被其他应用程序使用,则该端口名称将不会出现在组合框中。


0
  public void MobileMessages(string ComNo, string MobileMessage, string MobileNo)
    {
        if (SerialPort.IsOpen )
            SerialPort.Close();
        try
        {
            SerialPort.PortName = ComNo;
            SerialPort.BaudRate = 9600;
            SerialPort.Parity = Parity.None;
            SerialPort.StopBits = StopBits.One;
            SerialPort.DataBits = 8;
            SerialPort.Handshake = Handshake.RequestToSend;
            SerialPort.DtrEnable = true;
            SerialPort.RtsEnable = true;
            SerialPort.NewLine = Constants.vbCrLf;
            string message;
            message = MobileMessage;

            SerialPort.Open();
            if (SerialPort.IsOpen  )
            {
                SerialPort.Write("AT" + Constants.vbCrLf);
                SerialPort.Write("AT+CMGF=1" + Constants.vbCrLf);
                SerialPort.Write("AT+CMGS=" + Strings.Chr(34) + MobileNo + Strings.Chr(34) + Constants.vbCrLf);
                SerialPort.Write(message + Strings.Chr(26));
            }
            else
                ("Port not available");
            SerialPort.Close();
            System.Threading.Thread.Sleep(5000);
        }
        catch (Exception ex)
        {

                message.show("The port " + ComNo + " does not exist, change port no ");
        }
    }

0

SerialPort类有一个Open方法,会抛出一些异常。上面的参考文献包含了详细的例子。

另外,请参阅IsOpen属性。

一个简单的测试:

using System;
using System.IO.Ports;
using System.Collections.Generic;
using System.Text;

namespace SerPort1
{
class Program
{
    static private SerialPort MyPort;
    static void Main(string[] args)
    {
        MyPort = new SerialPort("COM1");
        OpenMyPort();
        Console.WriteLine("BaudRate {0}", MyPort.BaudRate);
        OpenMyPort();
        MyPort.Close();
        Console.ReadLine();
    }

    private static void OpenMyPort()
    {
        try
        {
            MyPort.Open();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error opening my port: {0}", ex.Message);
        }
    }
  }
}

12
只有调用过Open()方法,IsOpen才会为真。它不会表明端口是否已被其他应用程序打开。 - Mark Cidade
如果com1不存在,你如何跳过它? - Arthur Mamou-Mani
该示例仅使用COM1作为说明。不存在的设备将导致SerialPort构造函数引发IOException异常:“指定的端口无法找到或打开”。 - gimel
如果你这样做 - 所有端口上现有的硬件缓冲区中的数据将被移动到软件缓冲区(默认为4096字节),然后在关闭端口时被销毁。 - Kamil

0

分享一下我用过的一个简单的辅助方法:

private string portName { get; set; } = string.Empty;

    /// <summary>
    /// Returns SerialPort Port State (Open / Closed)
    /// </summary>
    /// <returns></returns>
    internal bool HasOpenPort()
    {
        bool portState = false;

        if (portName != string.Empty)
        {
            using (SerialPort serialPort = new SerialPort(portName))
            {
                foreach (var itm in SerialPort.GetPortNames())
                {
                    if (itm.Contains(serialPort.PortName))
                    {
                        if (serialPort.IsOpen) { portState = true; }
                        else { portState = false; }
                    }
                }
            }
        }

        else { System.Windows.Forms.MessageBox.Show("Error: No Port Specified."); }

        return portState;
}


注意事项:
- 对于更高级的技术,建议使用ManagementObjectSearcher Class
更多信息请参见此处
- 对于Arduino设备,建议保持端口开放。
- 如果需要捕获异常,请使用Try Catch块。
- 还要检查:"TimeoutException"
- 有关如何获取SerialPort(Open)异常的更多信息,请参见此处


-2
您可以尝试以下代码来检查端口是否已经打开。我假设您不知道要检查哪个特定的端口。
foreach (var portName in Serial.GetPortNames()
{
  SerialPort port = new SerialPort(portName);
  if (port.IsOpen){
    /** do something **/
  }
  else {
    /** do something **/
  }
}

11
如果人们能说出为什么要给负评,那其他人就能知道错在哪里了。我猜这就是原因:如果未曾调用Open()方法,则IsOpen只能为false。它不会说明该端口是否已被其他应用程序打开。- Mark Cidade 于2008年10月12日下午2:28发表。 - Joel McBeth
如果串口完全不存在,比如说端口20,你仍然可以通过返回false来检查它是否打开,这可能会误导开发人员尝试打开该端口...最好在假定其为开放或关闭之前先检查该端口是否存在。 - bl4kh4k
1
它被downvote的原因是因为这个解决方案只适用于在自己的应用程序中打开的端口,而不是系统范围内。因此它并没有真正帮助。不幸的是,我也在寻找一个好的检查方式,但似乎除了try..catch块之外没有更多的方法。 - AllDayPiano
正如@AllDayPiano所说,请检查备注部分:https://learn.microsoft.com/en-us/dotnet/api/system.io.ports.serialport.isopen?view=netframework-4.7.2 - mehdi.loa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接