1

1. What are we trying to achieve (and why)

We're currently trying to communicate with an industrial robot over USB(COM)<->serial(RS232). We would like to control the robot from a C++ application.

2. What setup do we have

We're using Visual Studio C++ 2015 with the built-in C++ compiler. Creating a "Win32 Console Application".

3. What steps have we taken?

We've got the connection working in Processing (Java) using Serial but we would like to implement it in C++.

3.1 Boost ASIO

We're using Boost ASIO (installed with NuGet package manager). At this point we get 2 compile errors indicating the same problem: 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'

I figured that this error is most likely not caused by my code (I haven't changed the library). So I believe the VS21015 C++ compiler is not fully compatible with boost::asio?

I've found two other links/posts with somewhat the same error:

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

And I tried the following define:

#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

With the following define:

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

But it did not resolve the errors. The even caused a lot of random syntax errors and undeclared identifiers (which would indicate missing the include of iterator.

3.2 Windows(base) and C

We've used some C code (and added in a little C++ Debugging) to detect COM ports. But it simply doesn't show them (however it does in device explorer). We even had to convert a LPCWSTR to char array (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 Another Serial.h from the internet

I believe it was from: http://www.codeguru.com/cpp/i-n/network/serialcommunications/article.php/c2503/CSerial--A-C-Class-for-Serial-Communications.htm

Same rules, I include the library, everything compiles fine. (Test written below)

#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;
}

But it still doesn't show my COM ports... (They do show up in device explorer though.)

4 So what is actually working?

This particular piece of code WILL display the right COM port...

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
    }
}
Community
  • 1
  • 1
Paul
  • 675
  • 1
  • 5
  • 26
  • You need to ask 1 question at a time. This is at least 3. You implemented a memory leak in `convertCharArrayToLPCWSTR`. Also, the conversion makes sense because `char` is not `wchar_t`. – sehe Nov 24 '15 at 16:19
  • About the constexpr hint: this is indeed likely because VS2015 started to support constexpr, then. Either you figure out how to configure it (other compilers have used it for ages), or you wait for Boost 1.60 which ***migh*** already know about this new compiler version. – sehe Nov 24 '15 at 16:21
  • Is your port really COM8 or is it just an example ? Also note that you can't open a port that is already opened. Check that you closed everything that use it. Also, you can use `GetDefaultCommConfigA` if you don't want to convert to wide chars. – ElderBug Nov 24 '15 at 16:25
  • @sehe so the boost NuGet package might be out of date, in combination with a "new" compiler? I got the convertCharArrayToLPCWSTR function from the internet, I didn't get what wchar_t was at that point. But it seems to define a ASCII character which is possible to be in the range higher than (decimal) 255? – Paul Nov 24 '15 at 19:14

1 Answers1

1

Maybe you need to update to a more recent version of boost if you have not already.

The issue with the second part is you naming of the COM port. Only COM1 to 4 can be a 'bald' name. You need to format it like this:

\\.\COM9

Clearly take care of escape sequences here:

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

EDIT: Actually you don't need to do that with GetCommConfig, only with CreateFile for opening the port. It should work. I'd suspect your conversion to wide string.

You may also find a performance enhancement of you load the cfgmgr32.dll library first.

Using CreateFile for COM port detection can result in in BSODs on some Windows systems. Particular culprits are some software modems and some bluetooth devices which show up s COM ports. So using GetDefaultCommConfig is the way to go generally though it may not work for all ports.

So what else can you do? Use setupapi.dll. Sadly this is not completely trivial..

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);

You'll need to do a bit of work to make that compilable but it should work. See https://support.microsoft.com/en-us/kb/259695

Pete
  • 4,784
  • 26
  • 33
  • Ah, the Serial library from the internet "secretly" uses files and I might try the "\\\\.\\" option. -Somewhat weird as it takes in an integer.- EDIT: I found this in the documentation :$ `The first argument contains the port number where the valid entries are 1 through 4.` so your answer does really make sense at that point. – Paul Nov 24 '15 at 19:24
  • I tried using another USB port, I even got COM4, but it didn't work with or wihouth the "\\\\.\\" addition – Paul Nov 24 '15 at 20:01
  • I should point out that using CreateFile for COM port detection can result in in BSODs on some Windows systems. Particular culprits are some software modems and some bluetooth devices which show up s COM ports. So using GetDefaultCommConfig is the way to go generally though it may not work for all ports.. I'll update my answer. – Pete Nov 25 '15 at 10:40
  • One of our devs has fixed the error on the ASIO library, he had to "add an exception handler to an error file". But couldn't recall where he got it from. I might add the code to this question when he pushes it. Thanks for your help! Though I think the asio library fits our needs better as the setupapi.dll. – Paul Nov 26 '15 at 13:21
  • Nice thing about using setupapi is that you get the friendly names of the ports. – Pete Nov 30 '15 at 14:04
  • The compiler of Visual Studio 2015 seems to have problems with both boost/ASIO and OpenCV. We're currently using the VS2012 compiler, in the VS2015 IDE using the NuGet packages. This is working fine. – Paul Dec 08 '15 at 19:25