初始化USB调制解调器的COM端口

7
我正在使用 GsmComm 连接 USB 调制解调器。当我最初将调制解调器连接到计算机时,在设备管理器中不会显示出我使用的调制解调器的 com 端口,但计算机会将其显示为可移动驱动器。 但是,当我运行调制解调器附带的应用程序时,com 端口会在设备管理器中显示。
因此,每次我想要使用我的应用程序与该设备一起使用时,必须首先将其连接到电脑,运行他们的软件以初始化 com 端口,然后再运行我的应用程序。
但是,是否有任何方法可以使用 C# 从我的应用程序初始化 com 端口?
我已经阅读了关于创建虚拟 com 端口并连接到 usb 设备的内容,但我不知道如何操作。任何帮助或指针将不胜感激。
更新于 2016 年 2 月 14 日:
我遵循 antiduh 的答案,并发现设备在第一次连接时被识别为 CD-ROM。 enter image description here 在运行他们的应用程序后,链接变为 harddiskvolume - enter image description here 并创建了三个新的 com 链接。 enter image description here

2
如果不知道您使用的具体设备,就无法回答这个问题。但是请查看http://www.draisberghof.de/usb_modeswitch以获取有关您的设备正在执行的操作的更多信息。如果它在http://www.draisberghof.de/usb_modeswitch/device_reference.txt中列出,则您也可以在那里找到需要发送到设备的数据。 - AVee
@AVee,这是一个中国品牌。所以我不认为我会在你提供的链接中找到它。甚至品牌名称也没有出现在设备上。 - th1rdey3
3个回答

6
虚拟串口由设备附带的驱动程序模拟。如果在运行其软件之前它们不显示在设备管理器中,那么它要么动态安装设备驱动程序,要么向驱动程序发送秘密握手以告诉它开始模拟端口。
前者需要UAC提升和.sys文件。如果您在运行其软件时没有看到提示,也没有看到可能执行此操作的已安装服务或.sys文件,则可以排除该可能性。后者通常是通过DeviceIoControl()调用完成的,这种调用可以使用过滤驱动程序进行监听,例如WDK附带的实用程序IoSpy。对实用程序使用Dumpbin.exe /imports可以提供有用的实现细节,SysInternals的Process Monitor也是如此。

并不能保证成功,最好向制造商询问详细信息。然而,他们通常不会回电并且在手册中也不包括这些细节。当然,他们更喜欢任何人使用他们的劣质软件。记住,在购买时你只能看到猪尾巴,最好通过退还设备并从其他制造商购买另一个来减少损失。


3
我有一个假设。
你是否见过 Windows Object Manager?它是 Windows 中一个整洁的命名空间,用于连接和公开各种疯狂的小对象,包括将设备作为文件。把它想象成 Window 版本可怕的 `/dev'。
有趣的是,用户空间程序可以通过调用 CreateFile 并使用特殊前缀来访问它。
例如,在 Windows 中打开串口的一种方法是通过调用 CreateFile(@"\\.\COM3")。这是一个映射到 Object Manager 路径 \GLOBAL??\COM3 的路径。
以下是使用 WinObj 查看该路径的截图: Screenshot of the Windows Object Manager program showing OM paths 在我的情况下,您可以看到\GLOBAL??\COM3实际上连接到了\Device\QTUSBSerial0
如果您在特殊软件运行之前和之后观察WinObj,您可能会发现哪个目标设备被符号链接到COMX,然后您可能会找出该真实设备是否始终存在。
天真地说,我认为可能可以将任意对象管理器路径传递给CreateFile以访问对象,而无需依赖于\\.\\GLOBAL??\的映射。然而,似乎有一个问题——根据this answerCreateFile只接受针对对象管理器的\GLOBAL??\部分的参数——它只接受\\.\作为路径前缀,并且不会接受例如\\.\Device\QTUSBSerial0或类似字符串的参数。
还有一种可能的方法:创建一个非常小的设备驱动程序/内核模块,使用IoCreateSymbolicLink创建符号链接。编写一个驱动程序,创建对象管理器符号链接\GLOBAL??\CrazyDevice --> \Device\CrazyDevice,然后使用修改过的SerialPortNet代码调用CreateFile(@"\\.\CrazyDevice")
这可能有点困难,但也许可以解决您的问题。
免责声明:我从未编写过Windows设备驱动程序或操作过对象管理器。我几乎不知道我在这里做什么。

2
看起来已经很久没有使用过Windows了,但是这个概念仍然适用。首先,这个USB调制解调器似乎具有模式切换功能,这意味着它首先将自己标识为CD-ROM,以便您获取驱动程序,然后在安装完成后切换到包含创建3个COM端口符号链接的脚本的大容量存储设备。
要在应用程序中使用它,您需要几件事情,首先是USB PID和VID,以便能够在计算机集线器上枚举设备。其次,您需要该触发符号链接创建的脚本的副本,并且需要在检测到设备(通过枚举VID和PID)后从应用程序中调用该脚本。一旦脚本执行,三个COM端口将自动出现,您应该能够像平常一样访问它们。此外,您可能还需要检查CD-ROM应用程序是否安装了DLL(几乎肯定会这样做,而且第二个脚本在创建COM端口链接之前几乎肯定会检查DLL),因此请确保DLL保持在它们应该在的位置。您还需要将它们与您的应用程序链接起来,以获得它们提供的任何额外功能(但这会打开原生接口的潘多拉魔盒,如果您不熟悉,请不要这样做...我可以在Java中执行/显示示例,但不是C#)。否则,如果只是想使用COM端口,并且您实际上知道如何与设备通信(AT命令),那么请忘记它并打开COM端口并开始操作。最后一点,您将不得不找出C#的本机接口(实际上不是本机接口,只是一个系统调用函数,用于执行bash命令以运行脚本/exe,仅此而已),因此请查找内置在.NET框架中的系统调用函数。
如果您需要进一步的步骤说明,请告诉我。
更新:
对于USB枚举,您可以使用类似于Java的usb4java库,并实现类似于以下函数的功能。
public Device findDevice(short vendorId, short productId)
{
// Read the USB device list
DeviceList list = new DeviceList();// ---> Here is empty device list 
int result = LibUsb.getDeviceList(null, list);// ---> Now list is populated
if (result < 0) throw new LibUsbException("Unable to get device list", result);

try
{
    // Iterate over all devices and scan for the right one
    for (Device device: list)
    {
        DeviceDescriptor descriptor = new DeviceDescriptor();
        result = LibUsb.getDeviceDescriptor(device, descriptor);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
        //Match the VID and PID (function inputs)---> if you find a match, then return success/or the device
        // you can find the VID and PID from the device manager
        if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) 
        return device;
    }
}
finally
{
    // Ensure the allocated device list is freed
    LibUsb.freeDeviceList(list, true);
}

// Device not found
return null;
}

这个函数允许你直接从应用程序访问USB设备。然后,你可以开始初始化序列和批量传输(很可能适用于虚拟串口设备,例如FTDI芯片,但由于这是一个未知的中国芯片,这就是我们在低级别上所能做的,我们必须在提供的基础上构建,并让Windows完成驱动程序的工作)...

此时,你的程序确定USB设备已插入,如果函数返回null,则休眠一秒钟并继续轮询,直到设备插入。

从这一点开始,你需要运行将创建符号链接的脚本,我假设它是一个.exe文件。以下是另一位成员发布的C#代码的复制和粘贴:

using System.Diagnostics;

// Prepare the process to run
ProcessStartInfo start = new ProcessStartInfo();
// Enter in the command line arguments, everything you would enter after  the executable name itself
start.Arguments = arguments; 
// Enter the executable to run, including the complete path
start.FileName = "C:/path/to/your/.exe";
// Do you want to show a console window?
start.WindowStyle = ProcessWindowStyle.Hidden;
start.CreateNoWindow = true;
int exitCode;


// Run the external process & wait for it to finish
using (Process proc = Process.Start(start))
{
 proc.WaitForExit();

 // Retrieve the app's exit code
 exitCode = proc.ExitCode;
}

拥有退出代码非常有用,可以指示脚本是否成功创建了符号链接(希望这位中文开发者遵循了适当的编码实践)。


编辑:

脚本可能会失败。原因很简单:它不知道要枚举和执行初始化的ProductID和VendorID。(99.99999%他们没有为每个单元重新编译简单的初始化脚本以硬编码pid和vid),所以它可能会将pid和vid作为参数接收(最好的情况),或从USB存储隐藏扇区读取(此时如果您从非根位置运行脚本,可能会出现路径问题)...您可能需要使用gdb找出某些参数是否缺失,特别是如果.exe未向stderr输出任何内容。


最后,在此时您可以开始使用类似以下方式的标准C#库查找COM端口列表:

代码来源

var portNames = SerialPort.GetPortNames();

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

当您找到所需的端口时,可以使用以下代码打开它:Code Source。请注意保留HTML标记。
public static void Main()
{
string name;
string message;
StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
Thread readThread = new Thread(Read);

// Create a new SerialPort object with default settings.
_serialPort = new SerialPort();

// Allow the user to set the appropriate properties.
_serialPort.PortName = SetPortName(_serialPort.PortName);
_serialPort.BaudRate = SetPortBaudRate(_serialPort.BaudRate);
_serialPort.Parity = SetPortParity(_serialPort.Parity);
_serialPort.DataBits = SetPortDataBits(_serialPort.DataBits);
_serialPort.StopBits = SetPortStopBits(_serialPort.StopBits);
_serialPort.Handshake = SetPortHandshake(_serialPort.Handshake);

// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;

_serialPort.Open();
_continue = true;
readThread.Start();

Console.Write("Name: ");
name = Console.ReadLine();

Console.WriteLine("Type QUIT to exit");

while (_continue)
{
    message = Console.ReadLine();

    if (stringComparer.Equals("quit", message))
    {
        _continue = false;
    }
    else
    {
        _serialPort.WriteLine(
            String.Format("<{0}>: {1}", name, message));
    }
}

readThread.Join();
_serialPort.Close();
}

public static void Read()
{
while (_continue)
{
    try
    {
        string message = _serialPort.ReadLine();
        Console.WriteLine(message);
    }
    catch (TimeoutException) { }
}
}

希望这能帮助你入门!

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