65.9K
CodeProject 正在变化。 阅读更多。
Home

空闲调制解调器模拟器和 C# - 它们如何学会相处

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2014 年 12 月 1 日

CPOL

2分钟阅读

viewsIcon

19396

一个简单的 C# 应用程序示例,它与空闲调制解调器模拟器 (com0com) 驱动程序接口,允许在运行时创建和配置虚拟串口。

引言

这是一个 C# 应用程序与空调制器 (com0com) 驱动程序交互的简单示例,允许在运行时创建和配置虚拟串行端口。

背景

最初,这在我们环境中是一个硬编码的解决方案。我们使用大量的串行 COM 端口接口来连接各种调制解调器和无线电收发器。作为测试环境的一部分,必须将物理设备连接到测试机器,并将串行通信拆分到第二台机器以监视和解释结果数据。

最近环境变更后,这种基于硬件的拆分变得不可行且维护成本过高。然后我们决定通过 空调制器 实现虚拟串行端口。当前的配置工作正常,但对于非 IT 人员来说,维护和修改环境变得更加困难。

因此,产生了开发统一解决方案的需求,该解决方案将提供一个包罗万象、用户友好的平台来初始化和测试各种硬件和软件。

这引出了本技巧的目的,即一种与空调制器交互、发出命令和读取反馈的简单方法。

Using the Code

可以通过向应用程序发出命令标志来控制空调制器,并附带任何必需的标志。我们可以直接从 C# 启动外部应用程序,并通过该机制控制空调制器。执行后,空调制器会提供有关已执行任务的相关反馈。

向空调制器发送命令

private static string execute_command(string command)
        {
            // Start the child process.
            Process p = new Process();
            //Set parameters to redirect input/output from the application
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardInput = true;
            //Hide the application window
            p.StartInfo.CreateNoWindow = true;
            
            //Set the Application Path and Working to directory to the location of setupc.exe 
            p.StartInfo.WorkingDirectory = "c:\\NullModem\\";
            p.StartInfo.FileName = "c:\\NullModem\\setupc.exe";
            
            //Append command and flags and execute the process
            p.StartInfo.Arguments = " " + command;
            p.Start();

            string output = "";

            output += p.StandardOutput.ReadToEnd() + "\r\n";
            p.WaitForExit();
            return output
       }

示例

列出所有可用的空调制器端口

execute_command("list");
来自空调制器的输出
             CNCA0 PortName=COM25
             CNCB0 PortName=COM26

安装一个新的端口对

execute_command("install PortName=COM26,EmuBR=yes,EmuOverrun=yes,
cts=ropen PortName=COM25,EmuBR=yes,EmuOverrun=yes,cts=ropen");
来自空调制器的输出
       CNCA2 PortName=COM100,EmuBR=yes,EmuOverrun=yes,cts=ropen
       CNCB2 PortName=COM101,EmuBR=yes,EmuOverrun=yes,cts=ropen
ComDB: COM100 - logged as "in use"
ComDB: COM101 - logged as "in use"

结果解读

以下方法将从空调制器接收到的行分组到一个逻辑端口对中。解析每一行并生成一个 NullModemPort 对象。

private static List<NullModemPair> Parse_NME_Data(string data)
        {
            var lstNMP = new List<NullModemPair>();

            var nmp = new NullModemPair();

            foreach(var x in data.Split('\n'))
            {
                var p = Parse_NME_Line(x);
                if (!string.IsNullOrEmpty(p.nme_Name))
                {
                    if (p.is_A)
                    {
                        nmp.Index = p.Index;
                        nmp.A = p;
                    }else if (nmp.Index == p.Index)
                    {
                        nmp.B = p;

                        lstNMP.Add(new NullModemPair(nmp.Index, nmp.A, nmp.B));

                        nmp = new NullModemPair();
                    }
                }
            }

            return lstNMP;
        }

为了解析来自空调制器的单独行,我们使用正则表达式查找结果中的熟悉模式。

private static NullModemPort Parse_NME_Line(string line)
        {
            /* REGEX Formulas                               
             * ---------------------------------------------*
             * Get CNC Name:        (CNC[AB][0-9]*)         *
             * Get A/B grouping:    CNC([AB])[0-9]*.        *
             * Get Index:           CNC[AB]([0-9]*)         *
             * Get Portname:        PortName=(.*?),|\n      *
             * Get Parameters:      [A-Z][0-9]?.*?,(.*)     *
             * ---------------------------------------------*
            */

            var p = new NullModemPort();

            //Get CNC Name
            Regex rgx = new Regex("(CNC[AB][0-9]*)", RegexOptions.IgnoreCase);
            MatchCollection matches = rgx.Matches(line);
            foreach (Match match in matches)
                p.nme_Name = match.Groups[1].Value;

            //Get A/B
            rgx = new Regex("CNC([AB])[0-9]*. ", RegexOptions.IgnoreCase);
            matches = rgx.Matches(line);
            foreach (Match match in matches)
            {
                if (match.Groups[1].Value.Contains("A"))
                    p.is_A = true;
                else
                    p.is_A = false;
            }

            //Get Index
            rgx = new Regex("CNC[AB]([0-9]*)", RegexOptions.IgnoreCase);
            matches = rgx.Matches(line);
            foreach (Match match in matches)
                int.TryParse(match.Groups[1].Value, out p.Index);

            //Get Portname
            rgx = new Regex("PortName=(.*?),|\n", RegexOptions.IgnoreCase);
            matches = rgx.Matches(line);
            foreach (Match match in matches)
                p.PortName = match.Groups[1].Value;
            
            //Get Parameters
            string pars = "";
            rgx = new Regex("[A-Z][0-9]?.*?,(.*)", RegexOptions.IgnoreCase);
            matches = rgx.Matches(line);
            foreach (Match match in matches)
                pars = match.Groups[1].Value;

            p.EmulateBaudRate = false;
            p.BufferOverrun = false;
            p.CTS_Open = false;

            if (pars.Contains("EmuBR=yes"))
                p.EmulateBaudRate = true;

            if (pars.Contains("EmuOverrun=yes"))
                p.BufferOverrun = true;

            if (pars.Contains("cts=ropen"))
                p.CTS_Open = true;

            return p;
        }

将端口对象返回到分组方法。然后该方法配对并存储信息。

这将使我们能够轻松访问程序其余部分中的信息。

关注点

整个示例应用程序的结构非常松散,仅用于演示我们可以访问空调制器并向其发出命令的机制。

可以对调用过程和检索结果的方式进行大量优化。

此外,NullModemInterface 的结构以及由此产生的层次结构应重新设计,以适应应用程序的要求。

为了演示的目的,已省略错误处理。应认真考虑并添加适当的异常处理到应用程序中。

© . All rights reserved.