空闲调制解调器模拟器和 C# - 它们如何学会相处
一个简单的 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
的结构以及由此产生的层次结构应重新设计,以适应应用程序的要求。
为了演示的目的,已省略错误处理。应认真考虑并添加适当的异常处理到应用程序中。