I want a function to enumerate serial (COM) ports on Windows. For this purpose I mostly copied code from http://www.codeguru.com/cpp/w-p/system/hardwareinformation/article.php/c5721/Determining-What-Serial-Ports-Are-Available-on-a-Windows-Machine.htm
in header file:
#include "SerialPort.h"
#include <list>
#include <objbase.h>
#include <initguid.h>
#include <Setupapi.h>
typedef std::list<SerialPort> PortList;
class SerialConnection
{
private:
static PortList availible_ports;
public:
static void enumerateSerialPorts(bool);
static const PortList& getPortList(){ return availible_ports; }
}
implementation:
void SerialConnection::enumerateSerialPorts(bool check)
{
availible_ports.clear();
CString strErr;
// Create a device information set that will be the container for
// the device interfaces.
GUID *guidDev = (GUID*)&GUID_CLASS_COMPORT;
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL;
try {
hDevInfo = SetupDiGetClassDevs(guidDev,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
strErr.Format(_T("SetupDiGetClassDevs failed. (err=%lx)"),
GetLastError());
throw strErr;
}
// Enumerate the serial ports
BOOL bOk = TRUE;
SP_DEVICE_INTERFACE_DATA ifcData;
DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA*) new char[dwDetDataSize];
// This is required, according to the documentation. Yes,
// it's weird.
ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
for (DWORD ii = 0; bOk; ii++) {
bOk = SetupDiEnumDeviceInterfaces(hDevInfo,
NULL, guidDev, ii, &ifcData);
if (bOk) {
// Got a device. Get the details.
SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
bOk = SetupDiGetDeviceInterfaceDetail(hDevInfo,
&ifcData, pDetData, dwDetDataSize, NULL, &devdata);
if (bOk) {
CString strDevPath(pDetData->DevicePath);
// Got a path to the device. Try to get some more info.
TCHAR fname[256];
TCHAR desc[256];
BOOL bSuccess = SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL,
(PBYTE)fname, sizeof(fname), NULL);
bSuccess = bSuccess && SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_DEVICEDESC, NULL,
(PBYTE)desc, sizeof(desc), NULL);
BOOL bUsbDevice = FALSE;
TCHAR locinfo[256];
if (SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_LOCATION_INFORMATION, NULL,
(PBYTE)locinfo, sizeof(locinfo), NULL))
{
// Just check the first three characters to determine
// if the port is connected to the USB bus. This isn't
// an infallible method; it would be better to use the
// BUS GUID. Currently, Windows doesn't let you query
// that though (SPDRP_BUSTYPEGUID seems to exist in
// documentation only).
// bUsbDevice = (strncmp(locinfo, "USB", 3) == 0);
}
if (bSuccess) {
// Add an entry to the array
SerialPort sp;
sp.full_path = strDevPath;
sp.port_name = fname;
sp.full_name = desc;
availible_ports.push_back(sp);
}
}
else {
strErr.Format(_T("SetupDiGetDeviceInterfaceDetail failed. (err=%lx)"),
GetLastError());
throw strErr;
}
}
else {
DWORD err = GetLastError();
if (err != ERROR_NO_MORE_ITEMS) {
strErr.Format(_T("SetupDiEnumDeviceInterfaces failed. (err=%lx)"), err);
throw strErr;
}
}
}
}
catch (CString strCatchErr) {
strErr = strCatchErr;
}
if (pDetData != NULL)
delete[](char*)pDetData;
if (hDevInfo != INVALID_HANDLE_VALUE)
SetupDiDestroyDeviceInfoList(hDevInfo);
if (!strErr.IsEmpty())
throw strErr;
if (check)
{
for (auto it = availible_ports.begin(); it != availible_ports.end(); it++)
{
HANDLE hCom = CreateFile(it->full_path,
GENERIC_READ | GENERIC_WRITE,
0, /* comm devices must be opened w/exclusive-access */
NULL, /* no security attrs */
OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
0, /* not overlapped I/O */
NULL /* hTemplate must be NULL for comm devices */
);
if (hCom == INVALID_HANDLE_VALUE) {
// It can't be opened; remove it.
auto erase_it = it;
it++;
availible_ports.erase(erase_it);
}
else {
// It can be opened! Close it and add it to the list
::CloseHandle(hCom);
}
}
}
this code comiles filne, but if I try to use it like
#include "SerialConnection.h"
int main()
{
SerialConnection::enumerateSerialPorts(false);
auto cl = SerialConnection::getPortList();
return 0;
}
error LNK2019: unresolved external symbol __imp__SetupDiDestroyDeviceInfoList@4 referenced in function __catch$?enumerateSerialPorts@SerialConnection@@SAX_N@Z$0
error LNK2019: unresolved external symbol __imp__SetupDiEnumDeviceInterfaces@20 referenced in function "public: static void __cdecl SerialConnection::enumerateSerialPorts(bool)" (?enumerateSerialPorts@SerialConnection@@SAX_N@Z)
error LNK2019: unresolved external symbol __imp__SetupDiGetDeviceInterfaceDetailW@24 referenced in function "public: static void __cdecl SerialConnection::enumerateSerialPorts(bool)" (?enumerateSerialPorts@SerialConnection@@SAX_N@Z)
error LNK2019: unresolved external symbol __imp__SetupDiGetClassDevsW@16 referenced in function "public: static void __cdecl SerialConnection::enumerateSerialPorts(bool)" (?enumerateSerialPorts@SerialConnection@@SAX_N@Z)
error LNK2019: unresolved external symbol __imp__SetupDiGetDeviceRegistryPropertyW@28 referenced in function "public: static void __cdecl SerialConnection::enumerateSerialPorts(bool)" (?enumerateSerialPorts@SerialConnection@@SAX_N@Z)
What have I missed? I use MVS 2013
You have to link your project with Setupapi.lib. You can do that in 2 ways:
#pragma comment (lib, "Setupapi.lib")
see also : https://social.msdn.microsoft.com/Forums/vstudio/en-US/e14ce1a2-08b9-4291-b035-f72171bcbdbc/error-lnk2019-unresolved-external-symbol?forum=vclanguage
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With