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 来自网络
相同的规则,我包含库,一切都编译正常。 (测试写在下面)
#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
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 来自网络
相同的规则,我包含库,一切都编译正常。 (测试写在下面)
#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