【问题标题】:C++ - Serial (COM) port - asio | VS2015 error(s)C++ - 串行 (COM) 端口 - asio | VS2015 错误
【发布时间】:2015-11-25 18:58:28
【问题描述】:

1.我们正在努力实现什么(以及为什么)

我们目前正在尝试通过 USB(COM)serial(RS232) 与工业机器人通信。我们想通过 C++ 应用程序控制机器人。

2。我们有什么设置

我们使用带有内置 C++ 编译器的 Visual Studio C++ 2015。创建“Win32 控制台应用程序”。

3.我们采取了哪些措施?

我们已经使用 Serial 在处理 (Java) 中建立了连接,但我们希望在 C++ 中实现它。

3.1 提升 ASIO

我们使用的是 Boost ASIO(安装了 NuGet 包管理器)。 此时,我们得到 2 个编译错误,指示相同的问题: Error C2694 'const char *asio::detail::system_category::name(void) const': overriding virtual function has less restrictive exception specification than base class virtual member function 'const char *std::error_category::name(void) noexcept const'

我认为这个错误很可能不是由我的代码引起的(我没有更改库)。所以我认为VS21015 C++编译器与boost::asio不完全兼容?

我发现另外两个链接/帖子有一些相同的错误:

https://github.com/chriskohlhoff/asio/issues/35

我尝试了以下定义:

#ifndef ASIO_ERROR_CATEGORY_NOEXCEPT
#define ASIO_ERROR_CATEGORY_NOEXCEPT noexcept(true)
#endif // !defined(ASIO_ERROR_CATEGORY_NOEXCEPT)

Error in websocketpp library and boost in windows Visual Studio 2015

具有以下定义:

#define ASIO_ERROR_CATEGORY_NOEXCEPT noexcept(true)
//or
#define ASIO_ERROR_CATEGORY_NOEXCEPT 1

但它并没有解决错误。甚至导致了很多随机语法错误和未声明的标识符(这表明缺少迭代器的包含。

3.2 Windows(基础)和 C

我们使用了一些 C 代码(并添加了一点 C++ 调试)来检测 COM 端口。但它根本不显示它们(但它在设备资源管理器中显示)。我们甚至不得不将 LPCWSTR 转换为 char 数组(wtf?)。

#include <stdio.h>
#include <cstdio>
#include <iostream>
#include <windows.h>
#include <winbase.h>

wchar_t *convertCharArrayToLPCWSTR(const char* charArray)
{
    wchar_t* wString = new wchar_t[4096];
    MultiByteToWideChar(CP_ACP, 0, charArray, -1, wString, 4096);
    return wString;
}

BOOL COM_exists(int port)
{
    char buffer[7];
    COMMCONFIG CommConfig;
    DWORD size;

    if (!(1 <= port && port <= 255))
    {
        return FALSE;
    }


    snprintf(buffer, sizeof buffer, "COM%d", port);
    size = sizeof CommConfig;

    // COM port exists if GetDefaultCommConfig returns TRUE
    // or changes <size> to indicate COMMCONFIG buffer too small.
    std::cout << "COM" << port << " | " << (GetDefaultCommConfig(convertCharArrayToLPCWSTR(buffer), &CommConfig, &size)
        || size > sizeof CommConfig) << std::endl;

    return (GetDefaultCommConfig(convertCharArrayToLPCWSTR(buffer), &CommConfig, &size)
        || size > sizeof CommConfig);
}

int main()
{
    int i;

    for (i = 1; i < 256; ++i)
    {
        if (COM_exists(i))
        {
            printf("COM%d exists\n", i);
        }
    }
    std::cin.get();
    return 0;
}

3.3 来自互联网的另一个 Serial.h

我相信它来自:http://www.codeguru.com/cpp/i-n/network/serialcommunications/article.php/c2503/CSerial--A-C-Class-for-Serial-Communications.htm

相同的规则,我包含库,一切编译正常。 (测试写在下面)

#include <iostream>
#include <string>
#include "Serial.h"

int main(void)
{

    CSerial serial;
    if (serial.Open(8, 9600))
        std::cout << "Port opened successfully" << std::endl;
    else
        std::cout << "Failed to open port!" << std::endl;

    std::cin.get();
    return 0;
}

但它仍然没有显示我的 COM 端口...(尽管它们确实显示在设备资源管理器中。)

4 那么实际上什么是有效的?

这段特定的代码将显示正确的 COM 端口...

TCHAR lpTargetPath[5000]; // buffer to store the path of the COMPORTS
DWORD test;

for (int i = 0; i<255; i++) // checking ports from COM0 to COM255
{
    CString str;
    str.Format(_T("%d"), i);
    CString ComName = CString("COM") + CString(str); // converting to COM0, COM1, COM2

    test = QueryDosDevice(ComName, lpTargetPath, 5000);

    // Test the return value and error if any
    if (test != 0) //QueryDosDevice returns zero if it didn't find an object
    {
        std::cout << "COM" << i << std::endl; // add to the ComboBox
    }
}

【问题讨论】:

  • 您需要一次提出 1 个问题。这至少是 3。您在 convertCharArrayToLPCWSTR 中实现了内存泄漏。此外,转换是有意义的,因为 char 不是 wchar_t
  • 关于constexpr提示:这确实很可能是因为VS2015开始支持constexpr,然后。要么你弄清楚如何配置它(其他编译器已经使用了很长时间),要么等待 Boost 1.60,migh 已经知道这个新的编译器版本。跨度>
  • 你的端口真的是 COM8 还是只是一个例子?另请注意,您无法打开已打开的端口。检查您是否关闭了所有使用它的东西。另外,如果您不想转换为宽字符,可以使用GetDefaultCommConfigA
  • @sehe 所以 boost NuGet 包可能已经过时,与“新”编译器结合使用?我从互联网上得到了 convertCharArrayToLPCWSTR 函数,当时我没有得到 wchar_t 是什么。但它似乎定义了一个可能在高于(十进制)255 的范围内的 ASCII 字符?

标签: c++ boost serial-port


【解决方案1】:

如果您还没有升级到最新版本的 boost,也许您需要更新。

第二部分的问题是您对 COM 端口的命名。只有 COM1 到 4 可以是“光头”名称。你需要像这样格式化它:

\\.\COM9

这里清楚地处理转义序列:

snprintf(buffer, sizeof(buffer), "\\\\.\\COM%d", port);

编辑:实际上您不需要使用 GetCommConfig 来执行此操作,只需使用 CreateFile 来打开端口即可。它应该工作。我怀疑您转换为宽字符串。

您可能还会发现先加载 cfgmgr32.dll 库的性能增强。

使用 CreateFile 进行 COM 端口检测可能会在某些 Windows 系统上导致蓝屏死机。特别的罪魁祸首是一些软件调制解调器和一些显示 COM 端口的蓝牙设备。因此,尽管它可能不适用于所有端口,但通常使用 GetDefaultCommConfig 是一种可行的方法。

那么你还能做什么呢?使用 setupapi.dll。可悲的是,这并非完全微不足道..

namespace {
    typedef HKEY (__stdcall *OpenDevRegKey)(HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM);
    typedef BOOL (__stdcall *ClassGuidsFromName)(LPCTSTR, LPGUID, DWORD, PDWORD);
    typedef BOOL (__stdcall *DestroyDeviceInfoList)(HDEVINFO);
    typedef BOOL (__stdcall *EnumDeviceInfo)(HDEVINFO, DWORD, PSP_DEVINFO_DATA);
    typedef HDEVINFO (__stdcall *GetClassDevs)(LPGUID, LPCTSTR, HWND, DWORD);
    typedef BOOL (__stdcall *GetDeviceRegistryProperty)(HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD);
} // namespace

typedef std::basic_string<TCHAR> tstring;

    struct PortEntry 
    {
        tstring dev;
        tstring name;

        bool operator== (tstring const& device) const {
            return dev == device; // TODO maybe use case-insentitive compare.
        }

        bool operator!= (tstring const& device) const {
            return !(*this == device);
        }
    };

    typedef std::vector<PortEntry> PortList;

// ...

    DllHandler setupapi; // RAII class for LoadLibrary / FreeLibrary

    if (!setupapi.load(_T("SETUPAPI.DLL"))) {
        throw std::runtime_error("Can\'t open setupapi.dll");
    }

    OpenDevRegKey fnOpenDevRegKey = 
        setupapi.GetProc("SetupDiOpenDevRegKey");

    ClassGuidsFromName fnClassGuidsFromName = 
#ifdef UNICODE
        setupapi.GetProc("SetupDiClassGuidsFromNameW");
#else
        setupapi.GetProc("SetupDiClassGuidsFromNameA");
#endif

    DestroyDeviceInfoList fnDestroyDeviceInfoList = 
        setupapi.GetProc("SetupDiDestroyDeviceInfoList");

    EnumDeviceInfo fnEnumDeviceInfo = 
        setupapi.GetProc("SetupDiEnumDeviceInfo");

    GetClassDevs fnGetClassDevs = 
#ifdef UNICODE
        setupapi.GetProc("SetupDiGetClassDevsW");
#else
        setupapi.GetProc("SetupDiGetClassDevsA");
#endif

    GetDeviceRegistryProperty fnGetDeviceRegistryProperty =
#ifdef UNICODE
        setupapi.GetProc("SetupDiGetDeviceRegistryPropertyW");
#else
        setupapi.GetProc("SetupDiGetDeviceRegistryPropertyA");
#endif

    if ((fnOpenDevRegKey == 0) ||
        (fnClassGuidsFromName == 0) ||
        (fnDestroyDeviceInfoList == 0) ||
        (fnEnumDeviceInfo == 0) ||
        (fnGetClassDevs == 0) ||
        (fnGetDeviceRegistryProperty == 0)
    ) {
        throw std:runtime_error(
            "Could not locate required functions in setupapi.dll"
        );
    }

    // First need to get the GUID from the name "Ports"
    //
    DWORD dwGuids = 0;
    (*fnClassGuidsFromName)(_T("Ports"), NULL, 0, &dwGuids);
    if (dwGuids == 0)
    {
        throw std::runtime_error("Can\'t get GUIDs from \'Ports\' key in the registry");
    }

    // Allocate the needed memory
    std::vector<GUID> guids(dwGuids);

    // Get the GUIDs
    if (!(*fnClassGuidsFromName)(_T("Ports"), &guids[0], dwGuids, &dwGuids))
    {
        throw std::runtime_error("Can\'t get GUIDs from \'Ports\' key in the registry");
    }

    // Now create a "device information set" which is required to enumerate all the ports

    HDEVINFO hdevinfoset = (*fnGetClassDevs)(&guids[0], NULL, NULL, DIGCF_PRESENT);
    if (hdevinfoset == INVALID_HANDLE_VALUE)
    {
        throw std::runtime_error("Can\'t get create device information set.");
    }

    // Finished with the guids.
    guids.clear();

    // Finally do the enumeration
    bool more = true;
    int index = 0;
    SP_DEVINFO_DATA devinfo;

    while (more)
    {
        //Enumerate the current device
        devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
        more = (0 != (*fnEnumDeviceInfo)(hdevinfoset, index, &devinfo));
        if (more)
        {
            PortEntry entry;

            //Did we find a serial port for this device
            bool added = false;

            //Get the registry key which stores the ports settings
            HKEY hdevkey = (*fnOpenDevRegKey)(
                hdevinfoset,
                &devinfo,
                DICS_FLAG_GLOBAL,
                0,
                DIREG_DEV, 
                KEY_QUERY_VALUE
            );

            if (hdevkey)
            {
                //Read in the name of the port
                TCHAR port_name[256];
                DWORD size = sizeof(port_name);
                DWORD type       = 0;

                if ((::RegQueryValueEx(
                        hdevkey,
                        _T("PortName"),
                        NULL,
                        &type,
                        (LPBYTE) port_name,
                        &size
                    ) == ERROR_SUCCESS) &&
                    (type == REG_SZ)
                ) {
                    // If it looks like "COMX" then
                    // add it to the array which will be returned
                    tstring s   = port_name;
                    size_t len  = s.length();

                    String const cmp(s, 0, 3);
                    if (CaseInsensitiveCompareEqual(String("COM"), cmp)) {
                        entry.name  = s;
                        entry.dev   = "\\\\.\\" + s;
                        added       = true;
                    }
                }

                // Close the key now that we are finished with it
                ::RegCloseKey(hdevkey);
            }

            // If the port was a serial port, then also try to get its friendly name
            if (added)
            {
                TCHAR friendly_name[256];
                DWORD size = sizeof(friendly_name);
                DWORD type = 0;
                if ((fnGetDeviceRegistryProperty)(
                        hdevinfoset,
                        &devinfo,
                        SPDRP_DEVICEDESC,
                        &type,
                        (PBYTE)friendly_name,
                        size,
                        &size
                    ) &&
                    (type == REG_SZ)
                ) {
                    entry.name += _T(" (");
                    entry.name += friendly_name;
                    entry.name += _T(")");
                }

                //
                // Add the port to our vector.
                //
                // If we already have an entry for the given device, then
                // overwrite it (this will add the friendly name).
                //
                PortList::iterator i = std::find(
                    ports.begin(),
                    ports.end(),
                    entry.dev
                );
                if (i == ports.end()) {
                    ports.push_back(entry);
                }
                else {
                    (*i) = entry;
                }
            }
        }

        ++index;
    }

    // Free up the "device information set" now that we are finished with it
    (*fnDestroyDeviceInfoList)(hdevinfoset);

您需要做一些工作才能使其可编译,但它应该可以工作。 见https://support.microsoft.com/en-us/kb/259695

【讨论】:

  • 啊,来自互联网的串行库“秘密”使用文件,我可能会尝试“\\\\.\\”选项。 - 有点奇怪,因为它需要一个整数。- 编辑:我在文档中找到了这个:$ The first argument contains the port number where the valid entries are 1 through 4. 所以你的回答在这一点上真的很有意义。
  • 我尝试使用另一个 USB 端口,我什至得到了 COM4,但它无法使用或没有添加“\\\\.\\”
  • 我应该指出,使用 CreateFile 进行 COM 端口检测可能会在某些 Windows 系统上导致蓝屏。特别的罪魁祸首是一些软件调制解调器和一些显示 COM 端口的蓝牙设备。因此,虽然它可能不适用于所有端口,但通常使用 GetDefaultCommConfig 是一种可行的方法。我会更新我的答案。
  • 我们的一位开发人员已经修复了 ASIO 库中的错误,他必须“将异常处理程序添加到错误文件中”。但想不起来他是从哪里弄来的。当他推送它时,我可能会将代码添加到这个问题中。谢谢你的帮助!虽然我认为 asio 库作为 setupapi.dll 更适合我们的需求。
  • 使用 setupapi 的好处是您可以获得端口的友好名称。
猜你喜欢
  • 1970-01-01
  • 2012-08-09
  • 1970-01-01
  • 2013-03-29
  • 2021-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-10
相关资源
最近更新 更多