Send IOCTL to Windows device driver - CreateFile fails - c++

I want to send an IOCTL command to a PC/SC reader connected to my computer (win7 64 bit).
In order to send an IOCTL command I need a HANDLE to the device, which I'm unable to create.
The device is listed as "OMNIKEY 1021" in the device manager, the physical device object name is "\Device\USBPDO-15". Using the "WinObj" tool, I can detect 2 symlinks:
USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
My problem: I cannot create a valid handle to this device with the CreateFile function:
I found several possible formats on MSDN/Google to use as the lpFileName param of the CreateFile function, but none of them seem to work:
\\?\Device\USBPDO-15
\\.\Device\USBPDO-15
\\GLOBAL??\Device\USBPDO-15
\GLOBAL??\Device\USBPDO-15
\\.\USBPDO-15
\\?\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\.\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\?\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\\.\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Code sample:
#include <iostream>
#include <Windows.h>
int main (int argc, char* argv[])
{
HANDLE handle = CreateFile (
L"\\\\.\\Device\\USBPDO-15",
0,
FILE_SHARE_READ, //FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0, //FILE_FLAG_OVERLAPPED,
NULL
);
if (handle == INVALID_HANDLE_VALUE)
std::cout << "INVALID HANDLE" << std::endl;
else
std::cout << "HANDLE: " << std::hex << handle << std::endl;
}
Notes:
The returned handle is always invalid
Always running as Administrator, so the privileges should not be a problem
edit:
Solution:
The PC/SC service takes exclusive ownership of the devices, so any attempt to call 'CreateFile' will always fail.
The solution is a kernel space driver, this allows you to pass IRP's to the driver. (I was able to implement a KMDF filter driver to alter data sent/received to/from the device)

Try it my way. I'm using Setup API to enumerate all USB active devices in the system and get paths. That way you can find out whether it's the path or other arguments that CreateFile doesn't like.
I'll add some comments a bit later, if anyone's interested.
HDEVINFO hDevInfo = SetupDiGetClassDevs( &_DEVINTERFACE_USB_DEVICE, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if(hDevInfo == INVALID_HANDLE_VALUE)
{
return ERR_FAIL;
}
std::vector<SP_INTERFACE_DEVICE_DATA> interfaces;
for (DWORD i = 0; true; ++i)
{
SP_DEVINFO_DATA devInfo;
devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
BOOL succ = SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo);
if (GetLastError() == ERROR_NO_MORE_ITEMS)
break;
if (!succ) continue;
SP_INTERFACE_DEVICE_DATA ifInfo;
ifInfo.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
if (TRUE != SetupDiEnumDeviceInterfaces(hDevInfo, &devInfo, &(_DEVINTERFACE_USB_DEVICE), 0, &ifInfo))
{
if (GetLastError() != ERROR_NO_MORE_ITEMS)
break;
}
interfaces.push_back(ifInfo);
}
std::vector<SP_INTERFACE_DEVICE_DETAIL_DATA*> devicePaths;
for (size_t i = 0; i < interfaces.size(); ++i)
{
DWORD requiredSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &(interfaces.at(i)), NULL, NULL, &requiredSize, NULL);
SP_INTERFACE_DEVICE_DETAIL_DATA* data = (SP_INTERFACE_DEVICE_DETAIL_DATA*) malloc(requiredSize);
assert (data);
data->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &(interfaces.at(i)), data, requiredSize, NULL, NULL))
{
continue;
}
devicePaths.push_back(data);
}

Just try with CreateFile(L"\\\\.\\{GUID}",etc...

Related

time out for USB ReadFile() replacing Serial Port code used with serial communications device

I'm working on extending a receipt printing Serial Port (COM) interface for a thermal receipt printer to use a USB interface without needing a Virtual Serial Port. I have a working prototype that will enumerate over the attached USB devices, locate the USB path for a device with a specific vendor id and product id, and open a connection to the device using CreateFile().
The existing Serial Port code uses the Windows API wrapped in a set of functions. The approach I'm taking is to add additional code using the same set of functions but that depend on a USB connection rather than a Serial Port connection. I have previously used the same approach to allow the use of a kitchen printer over either a Serial Port or over a WiFi/LAN connection with minimal changes to existing code successfully.
Unfortunately the existing code that uses the function library depends on the functions to use ReadFile() with a time out specified so that if the thermal printer does not respond to a status request within a reasonable time, the application can mark it as down and allow operations to continue or to use a backup or secondary printer.
How do I specify a time out for a ReadFile() on a file handle from CreateFile() that opens a connection to a communications devices using a USB pathname?
A consideration is this is multi-threaded code used for more than one serial communications device (receipt printer, kitchen printer, scale, etc.) however a thread will have exclusive access to a particular device (kitchen printing functionality opens serial port to kitchen printer only, scale reading functionality opens serial port to scale only, etc.).
In the existing Serial Port code, the function used to set timeouts, SetCommTimeouts(), for a Serial Port connection opened with CreateFile() does not work for a USB connection opened with CreateFile() (see SetupComm, SetCommState, SetCommTimeouts fail with USB device). This means some other mechanism is needed to provide a way to allow for an I/O failure due to a time out when using a USB device pathname.
We are using the following code segment to open a Serial Port, whether to a hardware COM port or a Virtual Serial Port emulating a hardware COM port:
// see Microsoft document HOWTO: Specify Serial Ports Larger than COM9.
// https://support.microsoft.com/en-us/kb/115831
// CreateFile() can be used to get a handle to a serial port. The "Win32 Programmer's Reference" entry for "CreateFile()"
// mentions that the share mode must be 0, the create parameter must be OPEN_EXISTING, and the template must be NULL.
//
// CreateFile() is successful when you use "COM1" through "COM9" for the name of the file;
// however, the value INVALID_HANDLE_VALUE is returned if you use "COM10" or greater.
//
// If the name of the port is \\.\COM10, the correct way to specify the serial port in a call to
// CreateFile() is "\\\\.\\COM10".
//
// NOTES: This syntax also works for ports COM1 through COM9. Certain boards will let you choose
// the port names yourself. This syntax works for those names as well.
wsprintf(wszPortName, TEXT("\\\\.\\COM%d"), usPortId);
/* Open the serial port. */
/* avoid to failuer of CreateFile */
for (i = 0; i < 10; i++) {
hHandle = CreateFile (wszPortName, /* Pointer to the name of the port, PifOpenCom() */
GENERIC_READ | GENERIC_WRITE, /* Access (read-write) mode */
0, /* Share mode */
NULL, /* Pointer to the security attribute */
OPEN_EXISTING,/* How to open the serial port */
0, /* Port attributes */
NULL); /* Handle to port with attribute */
/* to copy */
/* If it fails to open the port, return FALSE. */
if ( hHandle == INVALID_HANDLE_VALUE ) { /* Could not open the port. */
dwError = GetLastError ();
if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_INVALID_NAME || dwError == ERROR_ACCESS_DENIED) {
LeaveCriticalSection(&g_SioCriticalSection);
// the COM port does not exist. probably a Virtual Serial Communications Port
// from a USB device which was either unplugged or turned off.
// or the COM port or Virtual Serial Communications port is in use by some other application.
return PIF_ERROR_COM_ACCESS_DENIED;
}
PifLog (MODULE_PIF_OPENCOM, LOG_ERROR_PIFSIO_CODE_01);
PifLog (MODULE_ERROR_NO(MODULE_PIF_OPENCOM), (USHORT)dwError);
PifLog(MODULE_DATA_VALUE(FAULT_AT_PIFOPENCOM), usPortId);
PifSleep(500);
} else {
break;
}
}
if ( hHandle == INVALID_HANDLE_VALUE ) { /* Could not open the port. */
wsprintf(wszDisplay, TEXT("CreateFile, COM%d, Last Error =%d\n"), usPortId, dwError);
OutputDebugString(wszDisplay);
LeaveCriticalSection(&g_SioCriticalSection);
return PIF_ERROR_COM_ERRORS;
}
/* clear the error and purge the receive buffer */
dwError = (DWORD)(~0); // set all error code bits on
ClearCommError(hHandle, &dwError, NULL);
PurgeComm( hHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
The ReadFile() is wrapped within a function and looks like:
fResult = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, NULL);
if (PifSioCheckPowerDown(usPort, aPifSioTable) == TRUE) {
return PIF_ERROR_COM_POWER_FAILURE;
}
if (fResult) {
if (!dwBytesRead) return PIF_ERROR_COM_TIMEOUT;
return (SHORT)dwBytesRead;
} else {
SHORT sErrorCode = 0; // error code from PifSubGetErrorCode(). must call after GetLastError().
dwError = GetLastError();
PifLog (MODULE_PIF_READCOM, LOG_ERROR_PIFSIO_CODE_06);
PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)dwError);
sErrorCode = PifSubGetErrorCode(hHandle);
PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)abs(sErrorCode));
PifLog (MODULE_DATA_VALUE(MODULE_PIF_READCOM), usPort);
return (sErrorCode);
}
I found a number of similar posted questions which involved pipes however the same approach of using overlapped I/O applies.
Breaking ReadFile() blocking - Named Pipe (Windows API)
Win32 API: ReadFile timeout
Device driver: Windows ReadFile function timeout
I also found the following article on the web Peter's blog: Getting a handle on usbprint.sys which provides code and a description of how to find a USB pathname for a USB connected device. I have used some of that code sample in the class below.
I also found an article on codeproject.com, Enumerating windows device by Chuan-Liang Teng which contained an example of enumerating over the connected USB devices and interrogating various settings and details about the devices. The code from that article, though old, was helpful though not necessary for this particular application.
I have a prototype C++ class using overlapped I/O which seems to be replicating the behavior seen with a Serial Port connection using a USB connection to a thermal printer. The full source and Visual Studio 2017 solution and project files are in my GitHub repository https://github.com/RichardChambers/utilities_tools/tree/main/UsbWindows as this snip has the most pertinent parts.
I have done a simple test with modified code in the point of sale application and am now in the process of integrating this into the existing thermal receipt printer source code which already works with a Serial Port.
#include <windows.h>
#include <setupapi.h>
#include <initguid.h>
#include <iostream>
// This is the GUID for the USB device class.
// It is defined in the include file Usbiodef.h of the Microsoft Windows Driver Kit.
// See also https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85).aspx which
// provides basic documentation on this GUID.
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
class UsbSerialDevice
{
public:
// See https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-server-using-overlapped-i-o?redirectedfrom=MSDN
// to implement time outs for Write and for Read.
UsbSerialDevice(const wchar_t* wszVendorIdIn = nullptr);
~UsbSerialDevice();
int CreateEndPoint(const wchar_t* wszVendorId = nullptr, DWORD dwDesiredAccess = (GENERIC_READ | GENERIC_WRITE));
void CloseEndPoint(void);
int ListEndPoint(const wchar_t* wszVendorIdIn);
int ReadStream(void* bString, size_t nBytes);
int WriteStream(void* bString, size_t nBytes);
DWORD SetWriteTimeOut(DWORD msTimeout);
DWORD SetReadTimeOut(DWORD msTimeout);
DWORD m_dwError; // GetLastError() for last action
DWORD m_dwErrorWrite; // GetLastError() for last write
DWORD m_dwErrorRead; // GetLastError() for last read
DWORD m_dwBytesWritten; // number of bytes last write
DWORD m_dwBytesRead; // number of bytes last read
DWORD m_dwWait; // WaitForSingleObject() return value
private:
HANDLE m_hFile;
OVERLAPPED m_oOverlap;
COMMTIMEOUTS m_timeOut;
const unsigned short m_idLen = 255;
wchar_t m_wszVendorId[255 + 1] = { 0 };
};
UsbSerialDevice::UsbSerialDevice(const wchar_t* wszVendorIdIn) :
m_dwError(0),
m_dwErrorWrite(0),
m_dwErrorRead(0),
m_dwBytesWritten(0),
m_dwBytesRead(0),
m_dwWait(0),
m_hFile(INVALID_HANDLE_VALUE)
{
memset(&m_oOverlap, 0, sizeof(m_oOverlap));
m_oOverlap.hEvent = INVALID_HANDLE_VALUE;
if (wszVendorIdIn != nullptr) ListEndPoint(wszVendorIdIn);
}
void UsbSerialDevice::CloseEndPoint(void )
{
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile);
if (m_oOverlap.hEvent && m_oOverlap.hEvent != INVALID_HANDLE_VALUE) CloseHandle(m_oOverlap.hEvent);
}
UsbSerialDevice::~UsbSerialDevice()
{
CloseEndPoint();
}
/*
* Returns: -1 - file handle is invalid
* 0 - write failed. See m_dwErrorWrite for GetLastError() value
* 1 - write succedded.
*/
int UsbSerialDevice::WriteStream(void* bString, size_t nBytes)
{
SetLastError(0);
m_dwError = m_dwErrorWrite = 0;
m_dwBytesWritten = 0;
m_dwWait = WAIT_FAILED;
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
BOOL bWrite = WriteFile(m_hFile, bString, nBytes, 0, &m_oOverlap);
m_dwError = m_dwErrorWrite = GetLastError();
if (!bWrite && m_dwError == ERROR_IO_PENDING) {
SetLastError(0);
m_dwError = m_dwErrorWrite = 0;
m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.WriteTotalTimeoutConstant);
BOOL bCancel = FALSE;
switch (m_dwWait) {
case WAIT_OBJECT_0: // The state of the specified object is signaled.
break;
case WAIT_FAILED: // The function has failed. To get extended error information, call GetLastError.
m_dwError = m_dwErrorWrite = GetLastError();
bCancel = CancelIo(m_hFile);
break;
case WAIT_TIMEOUT: // The time-out interval elapsed, and the object's state is nonsignaled.
case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
bCancel = CancelIo(m_hFile);
m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
break;
}
bWrite = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
}
return bWrite; // 0 or FALSE if failed, 1 or TRUE if succeeded.
}
return -1;
}
/*
* Returns: -1 - file handle is invalid
* 0 - read failed. See m_dwErrorRead for GetLastError() value
* 1 - read succedded.
*/
int UsbSerialDevice::ReadStream(void* bString, size_t nBytes)
{
SetLastError(0);
m_dwError = m_dwErrorRead = 0;
m_dwBytesRead = 0;
m_dwWait = WAIT_FAILED;
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
BOOL bRead = ReadFile(m_hFile, bString, nBytes, &m_dwBytesRead, &m_oOverlap);
m_dwError = m_dwErrorRead = GetLastError();
if (!bRead && m_dwError == ERROR_IO_PENDING) {
SetLastError(0);
m_dwError = m_dwErrorRead = 0;
m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.ReadTotalTimeoutConstant);
BOOL bCancel = FALSE;
switch (m_dwWait) {
case WAIT_OBJECT_0: // The state of the specified object is signaled.
break;
case WAIT_FAILED: // The function has failed. To get extended error information, call GetLastError.
m_dwError = m_dwErrorWrite = GetLastError();
bCancel = CancelIo(m_hFile);
break;
case WAIT_TIMEOUT: // The time-out interval elapsed, and the object's state is nonsignaled.
case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
bCancel = CancelIo(m_hFile);
m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
break;
}
bRead = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
}
return bRead; // 0 or FALSE if failed, 1 or TRUE if succeeded.
}
return -1;
}
int UsbSerialDevice::ListEndPoint(const wchar_t* wszVendorIdIn)
{
m_dwError = ERROR_INVALID_HANDLE;
if (wszVendorIdIn == nullptr) return 0;
HDEVINFO hDevInfo;
// we need to make sure the vendor and product id codes are in lower case
// as this is needed for the CreateFile() function to open the connection
// to the USB device correctly. this lower case conversion applies to
// any alphabetic characters in the identifier.
//
// for example "VID_0FE6&PID_811E" must be converted to "vid_0fe6&pid_811e"
wchar_t wszVendorId[256] = { 0 };
for (unsigned short i = 0; i < 255 && (wszVendorId[i] = towlower(wszVendorIdIn[i])); i++);
// We will try to get device information set for all USB devices that have a
// device interface and are currently present on the system (plugged in).
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
DWORD dwMemberIdx;
BOOL bContinue = TRUE;
SP_DEVICE_INTERFACE_DATA DevIntfData;
// Prepare to enumerate all device interfaces for the device information
// set that we retrieved with SetupDiGetClassDevs(..)
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
dwMemberIdx = 0;
// Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
// function causes GetLastError() to return ERROR_NO_MORE_ITEMS. With each
// call the dwMemberIdx value needs to be incremented to retrieve the next
// device interface information.
for (BOOL bContinue = TRUE; bContinue; ) {
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
SP_DEVINFO_DATA DevData;
DWORD dwSize;
dwMemberIdx++;
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);
if (GetLastError() == ERROR_NO_MORE_ITEMS) break;
// As a last step we will need to get some more details for each
// of device interface information we are able to retrieve. This
// device interface detail gives us the information we need to identify
// the device (VID/PID), and decide if it's useful to us. It will also
// provide a DEVINFO_DATA structure which we can use to know the serial
// port name for a virtual com port.
DevData.cbSize = sizeof(DevData);
// Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
// a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
// of zero, and a valid RequiredSize variable. In response to such a call,
// this function returns the required buffer size at dwSize.
SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
// Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
// deallocate it later!
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
{
if (wcsstr(DevIntfDetailData->DevicePath, wszVendorId)) {
wcscpy_s(m_wszVendorId, DevIntfDetailData->DevicePath);
}
}
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
return 0;
}
int UsbSerialDevice::CreateEndPoint(const wchar_t* wszVendorIdIn, DWORD dwDesiredAccess)
{
if (wszVendorIdIn) {
ListEndPoint(wszVendorIdIn);
}
m_dwError = ERROR_INVALID_HANDLE;
// Finally we can start checking if we've found a useable device,
// by inspecting the DevIntfDetailData->DevicePath variable.
//
// The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
// \\?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
//
// The VID for a particular vendor will be the same for a particular vendor's equipment.
// The PID is variable for each device of the vendor.
//
// As you can see it contains the VID/PID for the device, so we can check
// for the right VID/PID with string handling routines.
// See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h
// See https://blog.peter.skarpetis.com/archives/2005/04/07/getting-a-handle-on-usbprintsys/
// which describes a sample USB thermal receipt printer test application.
SetLastError(0);
m_hFile = CreateFile(m_wszVendorId, dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
if (m_hFile == INVALID_HANDLE_VALUE) {
m_dwError = GetLastError();
// wprintf(_T(" CreateFile() failed. GetLastError() = %d\n"), m_dwError);
}
else {
m_oOverlap.hEvent = CreateEvent(
NULL, // default security attribute
TRUE, // manual-reset event
TRUE, // initial state = signaled
NULL); // unnamed event object
m_timeOut.ReadIntervalTimeout = 0;
m_timeOut.ReadTotalTimeoutMultiplier = 0;
m_timeOut.ReadTotalTimeoutConstant = 5000;
m_timeOut.WriteTotalTimeoutMultiplier = 0;
m_timeOut.WriteTotalTimeoutConstant = 5000;
m_dwError = 0; // GetLastError();
return 1;
}
return 0;
}

Bluetooth low energy low rate on Windows?

I have a device with a custom service which sends sensor data in a very high rate using the BLE notification feature.
I'm using the the following API on a Windows 10 machine: https://msdn.microsoft.com/en-us/library/windows/hardware/jj159880(v=vs.85).aspx
I'm searching the device by the custom service ID using SetupDi API, and then "connect" to it using CreateFile.
When I pair the device with Windows for the first time it immediately shows "Connected" in the Bluetooth Settings window, and then when I run my app it works perfectly fine (I receive data at high rate). If I close my app it changes the status in the Settings window to "Paired" instead of connected (Which I assume is fine). When I open my app again it connects and changes the status in the Settings to "Connected" again but now I receive the data at a much lower rate for some reason. (the data itself is correct). If I disconnect it via the Bluetooth Settings windows by clicking "Remove Device" and then pair it again like I did before it works again at an high rate for the first time.
I know it's not a problem with the device itself because it works fine with Android and other BLE supported platforms.
Any idea what might causing this issue?
Here is the code I'm using:
GUID serviceGuid = StringToGUID(GEM_SERVICE_GUID);
HDEVINFO info = SetupDiGetClassDevs(&guid, 0, 0, DIGCF_DEVICEINTERFACE);
SP_DEVICE_INTERFACE_DATA data;
data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
int i = 0;
while (SetupDiEnumDeviceInterfaces(info, NULL, &guid, i, &data))
{
i++;
}
if (GetLastError() != ERROR_NO_MORE_ITEMS)
{
// TODO throw
}
DWORD requiredSize;
if (!SetupDiGetDeviceInterfaceDetail(info, &data, NULL, 0, &requiredSize, NULL))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
// TODO throw
}
}
PSP_DEVICE_INTERFACE_DETAIL_DATA details = (PSP_DEVICE_INTERFACE_DETAIL_DATA)std::malloc(requiredSize);
details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(info, &data, details, requiredSize, NULL, NULL))
{
// TODO throw
}
m_service = CreateFile(details->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_service == INVALID_HANDLE_VALUE)
{
// TODO throw
return;
}
BTH_LE_GATT_CHARACTERISTIC combinedDataChar = FindCharacteristicByUUID(m_service, COMBINED_DATA_CHAR_HANDLE);
BTH_LE_GATT_DESCRIPTOR desc = FindDescriptorByType(m_service, &combinedDataChar, ClientCharacteristicConfiguration);
BTH_LE_GATT_DESCRIPTOR_VALUE val;
RtlZeroMemory(&val, sizeof(val));
val.DescriptorType = ClientCharacteristicConfiguration;
val.ClientCharacteristicConfiguration.IsSubscribeToNotification = TRUE;
HRESULT res = BluetoothGATTSetDescriptorValue(m_service, &desc, &val, BLUETOOTH_GATT_FLAG_NONE);
if (res != S_OK)
{
// TODO throw
}
BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION chars;
chars.NumCharacteristics = 1;
chars.Characteristics[0] = combinedDataChar;
res = BluetoothGATTRegisterEvent(m_service, CharacteristicValueChangedEvent, &chars, OnValueChanged, NULL, &m_registrationHandle, BLUETOOTH_GATT_FLAG_NONE);
if (res != S_OK)
{
// TODO throw
}
EDIT:
The code for the OnValueChanged callback:
void OnValueChanged(BTH_LE_GATT_EVENT_TYPE eventType, PVOID eventOutParameter, PVOID context)
{
BLUETOOTH_GATT_VALUE_CHANGED_EVENT* e = (BLUETOOTH_GATT_VALUE_CHANGED_EVENT*)eventOutParameter;
std::cout << e->CharacteristicValue->DataSize << std::endl;
}
I believe you receive data through OnValueChanged callback? It's not provided in your code, and the problem could be somewhere inside it. If you hesitate to provide its code, I suggest that you perform the following experiments during the 'bad' session:
Remove all of the code from it except for incrementing some counter to know the data rate.
Measure CPU load. If it's nearing a full core load, you're CPU-bound.
Use a profiler of your choice to check where your code spends time.
Now that the code for OnValueChanged is available:
Output to console at high rate could be the bottleneck. I suggest that you only count events and output count once a few seconds, like that:
static DWORD lastTicks = GetTickCount();
static DWORD count = 0;
count++;
DWORD ticksElapsed = GetTickCount() - lastTicks;
if (ticksElapsed > 5000)
{
std::cout << "Rate: " << (double(count) / ticksElapsed) << " / sec" << std::endl;
lastTicks = GetTickCount();
count = 0;
}
Do more tests (for example, 5 pairings, 5 connects after each pairing) and provide the event rates. It could happen that the drop in rate is actually related to something else, not re-pairing.

How to connect to the bluetooth low energy device

I am writing program for Win 8 tablet. I need to connect an external BLE device.
The device is already paired with Windows and I can see it in Device Manager. But I can not figure out how to connect it.
With SetupDiEnumDeviceInfo and SetupDiGetDeviceProperty I can get some information about the BLE-device, but to perform, e.g. BluetoothGATTGetServices
Handle device requires. I do not know where to take it. Perhaps i can use CreateFile, but it is not clear that the substitute as the first argument lpFileName.
Here's a piece of code with which I'm looking for my device.
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
DWORD i;
// Create a HDEVINFO with all present devices.
hDevInfo = SetupDiGetClassDevs(
&BluetoothClassGUID, /* GUID_DEVCLASS_BLUETOOTH */
0, 0, DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
// Insert error handling here.
return ;//1;
}
// Enumerate through all devices in Set.
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
&DeviceInfoData);i++)
{
DWORD DataT;
LPTSTR buffer = NULL;
DWORD buffersize = 0;
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_FRIENDLYNAME,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
// Change the buffer size.
if (buffer) delete(buffer);
// Double the size to avoid problems on
// W2k MBCS systems per KB 888609.
buffer = new wchar_t[buffersize * 2];
}else{
// Insert error handling here.
break;
}
}
/* Here i just compare by name is this my device or not */
...
/* Here i just compare by name is this my device or not */
if (buffer) delete(buffer);
}
if ( GetLastError()!=NO_ERROR &&
GetLastError()!=ERROR_NO_MORE_ITEMS )
{
// Insert error handling here.
return; //1;
}
// Cleanup
SetupDiDestroyDeviceInfoList(hDevInfo);
return;// 0;
I moved a little further, but still i can't get the data from device.
To obtain "Device Interface Path" had to use the other functions:
SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces and SetupDiGetDeviceInterfaceDetail.
Next, with CreateFile I get HANDLE BLE-device.
hComm = CreateFile(pInterfaceDetailData->DevicePath, GENERIC_WRITE | GENERIC_READ,NULL,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
Next using WinAPI BluetoothGATTGetServices and BluetoothGATTGetCharacteristics I get the appropriate structures.
But when trying to get the property value with BluetoothGATTGetCharacteristicsValue, I get ERROR_ACCESS_DENIED.
And then I do not know what to do. What could be wrong?
Andrey, I think the problem is that your device is not connected and BluetoothGATTGetCharacteristicsValue is not triggering a connection.
Try manually to connect your device using Windows tools. I've the following flow that helps me: Unpair device, Pair device -> It should appear as connected ( it worked in my case ;) )
Anyway, If this is not helping, try to run "As Administrator", this helps in some cases.
Good luck!!!
Note: Would be very interested to know how to retrievethe device path for the BTLE device in order to call BluetoothGATTGetServices?
gattServiceGUID is any long form BLE UUID supported by your device.
"{00001803-0000-1000-8000-00805F9B34FB"} can be used to open a handle to the Link Loss service if supported by the device you are trying to open
HDEVINFO hDevInfo = SetupDiGetClassDevs(&gattServiceGUID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
SP_DEVICE_INTERFACE_DATA interfaceData;
ZeroMemory(&interfaceData,sizeof(SP_DEVICE_INTERFACE_DATA));
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for (DWORD dwDeviceIndex = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &gattServiceGUID, dwDeviceIndex, &interfaceData); dwDeviceIndex++)
{
dwDeviceCount++;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &interfaceData, NULL, 0, &dwBytesNeeded, NULL);
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
pInterfaceDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA) new byte[dwBytesNeeded];
SP_DEVINFO_DATA spDeviceInfoData = { sizeof(SP_DEVINFO_DATA) };
ZeroMemory(pInterfaceDetail, sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA));
pInterfaceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
// grab the interface detail
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &interfaceData, pInterfaceDetail, dwBytesNeeded, NULL, &spDeviceInfoData) == TRUE)
{
// request a handle to the GATT service path
m_hGattServiceHandle = CreateFile(pInterfaceDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (m_hGattServiceHandle != INVALID_HANDLE_VALUE)
{
now you can drill down the characteristics and descriptors with the m_hGattServiceHandle
}
}
}
}
}

Find and eject a USB device based on its VID/PID

I want to send an eject command to a specific USB device identified by it's VID and PID. I can find the device by using SetupDiEnumDeviceInfo() and SetupDiGetDeviceRegistryProperty() and matching the VID/PID numbers in the HARDWAREID string but that's as far as I've got.
I have a SP_DEVINFO_DATA struct and a HDEVINFO handle. How would I relate these to a drive letter or volume path so I can send it an eject command?
Well, I figured it out. The CodeProject article linked to by Luke shows how to match the drive letter to a device interface which is half the way there so I'll +1 that answer but it doesn't solve the whole problem.
I needed to figure out how to find the device instance for my USB device and find a way to match that to the device interface. The CM_Locate_DevNode() and CM_Get_Child() functions were the key to this. Finally I can use an IOCTL to eject the device.
The device I am dealing with is a USB CD-ROM drive which is why I have hard-coded the device type to CDROM. I can't believe how much code is required to do what I thought would be a fairly straightforward task (I quoted my client 2 hours to write this code, it's taken me four days to figure it all out!). Here's the final working code which will hopefully save one of you out there from going through the same hell as I just have:
#include <SetupAPI.h>
#include <cfgmgr32.h>
#include <winioctl.h>
// Finds the device interface for the CDROM drive with the given interface number.
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber)
{
const GUID *guid = &GUID_DEVINTERFACE_CDROM;
// Get device interface info set handle
// for all devices attached to system
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(hDevInfo == INVALID_HANDLE_VALUE)
return 0;
// Retrieve a context structure for a device interface of a device information set.
BYTE buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD dwSize;
spdid.cbSize = sizeof(spdid);
// Iterate through all the interfaces and try to match one based on
// the device number.
for(DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL,guid, i, &spdid); i++)
{
// Get the device path.
dwSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
if(dwSize == 0 || dwSize > sizeof(buf))
continue;
pspdidd->cbSize = sizeof(*pspdidd);
ZeroMemory((PVOID)&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd,
dwSize, &dwSize, &spdd))
continue;
// Open the device.
HANDLE hDrive = CreateFile(pspdidd->DevicePath,0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if(hDrive == INVALID_HANDLE_VALUE)
continue;
// Get the device number.
STORAGE_DEVICE_NUMBER sdn;
dwSize = 0;
if(DeviceIoControl(hDrive,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn),
&dwSize, NULL))
{
// Does it match?
if(DeviceNumber == (long)sdn.DeviceNumber)
{
CloseHandle(hDrive);
SetupDiDestroyDeviceInfoList(hDevInfo);
return spdd.DevInst;
}
}
CloseHandle(hDrive);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return 0;
}
// Returns true if the given device instance belongs to the USB device with the given VID and PID.
bool matchDevInstToUsbDevice(DEVINST device, DWORD vid, DWORD pid)
{
// This is the string we will be searching for in the device harware IDs.
TCHAR hwid[64];
_stprintf(hwid, _T("VID_%04X&PID_%04X"), vid, pid);
// Get a list of hardware IDs for all USB devices.
ULONG ulLen;
CM_Get_Device_ID_List_Size(&ulLen, NULL, CM_GETIDLIST_FILTER_NONE);
TCHAR *pszBuffer = new TCHAR[ulLen];
CM_Get_Device_ID_List(NULL, pszBuffer, ulLen, CM_GETIDLIST_FILTER_NONE);
// Iterate through the list looking for our ID.
for(LPTSTR pszDeviceID = pszBuffer; *pszDeviceID; pszDeviceID += _tcslen(pszDeviceID) + 1)
{
// Some versions of Windows have the string in upper case and other versions have it
// in lower case so just make it all upper.
for(int i = 0; pszDeviceID[i]; i++)
pszDeviceID[i] = toupper(pszDeviceID[i]);
if(_tcsstr(pszDeviceID, hwid))
{
// Found the device, now we want the grandchild device, which is the "generic volume"
DEVINST MSDInst = 0;
if(CR_SUCCESS == CM_Locate_DevNode(&MSDInst, pszDeviceID, CM_LOCATE_DEVNODE_NORMAL))
{
DEVINST DiskDriveInst = 0;
if(CR_SUCCESS == CM_Get_Child(&DiskDriveInst, MSDInst, 0))
{
// Now compare the grandchild node against the given device instance.
if(device == DiskDriveInst)
return true;
}
}
}
}
return false;
}
// Eject the given drive.
void ejectDrive(TCHAR driveletter)
{
TCHAR devicepath[16];
_tcscpy(devicepath, _T("\\\\.\\?:"));
devicepath[4] = driveletter;
DWORD dwRet = 0;
HANDLE hVol = CreateFile(devicepath, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if(hVol == INVALID_HANDLE_VALUE)
return;
if(!DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &dwRet, 0))
return;
if(!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, &dwRet, 0))
return;
DeviceIoControl(hVol, IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, &dwRet, 0);
CloseHandle(hVol);
}
// Find a USB device by it's Vendor and Product IDs. When found, eject it.
void usbEjectDevice(unsigned vid, unsigned pid)
{
TCHAR devicepath[8];
_tcscpy(devicepath, _T("\\\\.\\?:"));
TCHAR drivepath[4];
_tcscpy(drivepath, _T("?:\\"));
// Iterate through every drive letter and check if it is our device.
for(TCHAR driveletter = _T('A'); driveletter <= _T('Z'); driveletter++)
{
// We are only interested in CDROM drives.
drivepath[0] = driveletter;
if(DRIVE_CDROM != GetDriveType(drivepath))
continue;
// Get the "storage device number" for the current drive.
long DeviceNumber = -1;
devicepath[4] = driveletter;
HANDLE hVolume = CreateFile(devicepath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if(INVALID_HANDLE_VALUE == hVolume)
continue;
STORAGE_DEVICE_NUMBER sdn;
DWORD dwBytesReturned = 0;
if(DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL))
DeviceNumber = sdn.DeviceNumber;
CloseHandle(hVolume);
if(DeviceNumber < 0)
continue;
// Use the data we have collected so far on our drive to find a device instance.
DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber);
// If the device instance corresponds to the USB device we are looking for, eject it.
if(DevInst)
{
if(matchDevInstToUsbDevice(DevInst, vid, pid))
ejectDrive(driveletter);
}
}
}

Locking files using C++ on Windows

I have a program writing/reading from a file, and I want to lock the file for other instances of my application. How can I do it (in c++ visual studio 2003)?
I tried using the _locking() but then also I myself cannot reach the file when trying to read/write (in the same instance).
I know there's an option of LockFile() but have no idea how to set it properly.
Please help me.
You can simply use the Win32 API CreateFile and then specify no sharing rights. This will ensure that no other processes can access the file.
The dwShareMode DWORD specifies the type of sharing you would like, for example GENERIC_READ. If you specify 0 then that means no sharing rights should be granted.
Example:
HANDLE hFile = CreateFile(_T("c:\\file.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
If you want to only lock a certain part of the file you can use LockFile or LockFileEx.
Example:
//Lock the first 1024 bytes
BOOL bLocked = LockFile(hFile, 0, 0, 1024, 0);
For locking on other platforms please see my post here.
You want LockFileEx() (exclusive file locking). Have a look at this discussion from Secure Programming Cookbook for C and C++.
After searching online for a while, I didn't find any good examples.
Here are two calls to CreateFile with the intent of locking the file for the life of a process... I use this along side the CLimitSingleInstance that uses CreateMutex for a global named mutex.
The first call to CreateFile attempts to open it, the second one creates it if necessary.
I have a little bit more thorough implementation. I implemented it in Qt, hence the qCritical() instead of std::cout and the QDir::tempPath() instead of getting that some other way.
class SingleInstance
{
protected:
DWORD m_dwLastError;
HANDLE m_hFile;
public:
SingleInstance(const char *strMutexName) { }
bool attemptToLockTempFile()
{
QString lockFile = QDir::tempPath() + "/My.exe.lock";
m_hFile = CreateFileA(lockFile.toLocal8Bit().data(), GENERIC_READ, 0,
NULL, OPEN_EXISTING, 0, NULL);
DWORD dwLastError = GetLastError();
if(m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
return true;
}
else
{
if(dwLastError == ERROR_FILE_NOT_FOUND )
{
m_hFile = CreateFileA(lockFile.toLocal8Bit().data(), GENERIC_READ,
0, NULL, CREATE_NEW, 0, NULL);
dwLastError = GetLastError();
if(m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
return true;
}
else if(dwLastError == ERROR_SHARING_VIOLATION)
{
qCritical() << "Sharing Violation on My.exe.lock";
}
else
{
qCritical() << "Error reading" << "My.exe.lock" << "-" << dwLastError;
}
}
else if(dwLastError == ERROR_SHARING_VIOLATION)
{
qCritical() << "Sharing Violation on My.exe.lock";
}
else
{
qCritical() << "Unable to obtain file lock -" << dwLastError;
}
return false;
}
}
~SingleInstance()
{
if ( m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
::CloseHandle(m_hFile); //Do as late as possible.
m_hFile = NULL;
}
}
}
Here is what you would have at the top of your main function:
SingleInstance g_SingleInstanceObj(globalId_QA);
// Makes sure that the program doesn't run if there is another
// instance already running
if (g_SingleInstanceObj.IsAnotherInstanceRunning())
{
return 0;
}