C++ - 串行 (COM) 端口 - asio | VS2015 错误

C++ - Serial (COM) port - asio | VS2015 error(s)

1.我们想要实现什么(以及为什么)

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

2。我们有什么设置

我们使用 Visual Studio C++ 2015 和内置的 C++ 编译器。创建 "Win32 Console Application".

3。我们采取了哪些步骤?

我们已经使用 Serial 在 Processing (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?

我发现另外两个 links/posts 有一些相同的错误:

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)

定义如下:

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

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

3.2 Windows(基础)和C

我们使用了一些 C 代码(并添加了一些 C++ 调试)来检测 COM 端口。但它根本不显示它们(但它在设备资源管理器中显示)。我们甚至不得不将 LPCWSTR 转换为字符数组(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
    }
}

也许您需要更新到更新版本的 boost(如果您还没有的话)。

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

\.\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