Advertisement
Guest User

Untitled

a guest
Aug 17th, 2018
444
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 15.91 KB | None | 0 0
  1.  
  2. //HeartRateRetriever.h
  3.  
  4. #pragma once
  5. #pragma comment(lib, "SetupAPI")
  6. #pragma comment(lib, "BluetoothApis.lib")
  7. #pragma warning( disable : 4068 )
  8.  
  9. #define NTDDI_VERSION NTDDI_WIN8
  10. #define _WIN32_WINNT _WIN32_WINNT_WIN8
  11. #define TO_SEARCH_DEVICE_UUID "{0000180D-0000-1000-8000-00805F9B34FB}" //we use UUID for an HR BLE device
  12.  
  13. #include <iostream>
  14. #include "Core.h"
  15. #include "AllowWindowsPlatformTypes.h"
  16. #include "Windows/COMPointer.h"
  17. #include "stdio.h"
  18. #include "windows.h"
  19. #include "setupapi.h"
  20. #include "devguid.h"
  21. #include "regstr.h"
  22. #include "bthdef.h"
  23. #include "Bluetoothleapis.h"
  24. #include "HideWindowsPlatformTypes.h"
  25.  
  26. class ANGEL_GREYBOX_API HeartRateRetriever : public FRunnable
  27. {
  28. public:
  29.  
  30.     // Constructor & Destructor
  31.     HeartRateRetriever();
  32.     ~HeartRateRetriever();
  33.  
  34.     // Thread handling functions
  35.     void EnsureCompletion();        // Function for killing the thread
  36.     void PauseThread();             // Function for pausing the thread
  37.     void ContinueThread();          // Function for continuing/unpausing the thread
  38.     bool IsThreadPaused();          // Function to check the state of the thread
  39.  
  40.                                     // FRunnable interface functions
  41.     virtual bool Init();
  42.     virtual uint32 Run();
  43.     virtual void Stop();
  44.  
  45.     // Bluetooth Windows API functions
  46.     HANDLE GetBLEHandle(__in GUID AGuid);
  47.  
  48.     static void SomethingElseHappened(BTH_LE_GATT_EVENT_TYPE EventType, PVOID EventOutParameter, PVOID Context);
  49.  
  50.     // Function to get the heart rate
  51.     int GetHeartRate();
  52.     inline int GetPrevHeartRate() { return prevHeartRate; };
  53.  
  54. private:
  55.  
  56.     // Thread to run the worker FRunnable on
  57.     FRunnableThread* Thread;
  58.  
  59.     FCriticalSection m_mutex;
  60.     FEvent* m_semaphore;
  61.  
  62.     // Thread-safe booleans for changing the state of the thread
  63.     FThreadSafeBool m_kill;
  64.     FThreadSafeBool m_pause;
  65.  
  66.     static int heartRate;
  67.     int prevHeartRate;
  68.  
  69. };
  70.  
  71. // HeartRateRetriever.cpp
  72.  
  73. #include "HeartRateRetriever.h"
  74. #include <String>
  75. #include <EngineGlobals.h>
  76. #include <Runtime/Engine/Classes/Engine/Engine.h>
  77.  
  78. int HeartRateRetriever::heartRate = 0;
  79.  
  80. HeartRateRetriever::HeartRateRetriever()
  81. {
  82.  
  83.     m_kill = false;
  84.     m_pause = false;
  85.  
  86.     // Initialise FEvent
  87.     m_semaphore = FGenericPlatformProcess::GetSynchEventFromPool(false);
  88.  
  89.     // Create the worker thread
  90.     Thread = FRunnableThread::Create(this, TEXT("HRR Thread"), 0, TPri_BelowNormal);
  91.  
  92. }
  93.  
  94. HeartRateRetriever::~HeartRateRetriever()
  95. {
  96.  
  97.     if (m_semaphore)
  98.     {
  99.  
  100.         // Clean up the FEvent
  101.         FGenericPlatformProcess::ReturnSynchEventToPool(m_semaphore);
  102.         m_semaphore = nullptr;
  103.  
  104.     }
  105.  
  106.     if (Thread)
  107.     {
  108.  
  109.         // Clean up the worker thread
  110.         delete Thread;
  111.         Thread = nullptr;
  112.  
  113.     }
  114.  
  115. }
  116.  
  117. bool HeartRateRetriever::Init()
  118. {
  119.  
  120.     return true;
  121.  
  122. }
  123.  
  124. HANDLE HeartRateRetriever::GetBLEHandle(GUID AGuid)
  125. {
  126.     HDEVINFO hDI;
  127.     SP_DEVICE_INTERFACE_DATA did;
  128.     SP_DEVINFO_DATA dd;
  129.     GUID BluetoothInterfaceGUID = AGuid;
  130.     HANDLE hComm = NULL;
  131.  
  132.     hDI = SetupDiGetClassDevs(&BluetoothInterfaceGUID, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
  133.  
  134.     if (hDI == INVALID_HANDLE_VALUE) return NULL;
  135.  
  136.     did.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
  137.     dd.cbSize = sizeof(SP_DEVINFO_DATA);
  138.  
  139.     for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDI, NULL, &BluetoothInterfaceGUID, i, &did); i++)
  140.     {
  141.         SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData;
  142.  
  143.         DeviceInterfaceDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  144.  
  145.         DWORD size = 0;
  146.  
  147.         if (!SetupDiGetDeviceInterfaceDetail(hDI, &did, NULL, 0, &size, 0))
  148.         {
  149.             int err = GetLastError();
  150.  
  151.             if (err == ERROR_NO_MORE_ITEMS) break;
  152.  
  153.             PSP_DEVICE_INTERFACE_DETAIL_DATA pInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)GlobalAlloc(GPTR, size);
  154.  
  155.             pInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  156.  
  157.             if (!SetupDiGetDeviceInterfaceDetail(hDI, &did, pInterfaceDetailData, size, &size, &dd))
  158.                 break;
  159.  
  160.             hComm = CreateFile(
  161.                 pInterfaceDetailData->DevicePath,
  162.                 GENERIC_WRITE | GENERIC_READ,
  163.                 FILE_SHARE_READ | FILE_SHARE_WRITE,
  164.                 NULL,
  165.                 OPEN_EXISTING,
  166.                 0,
  167.                 NULL);
  168.  
  169.             GlobalFree(pInterfaceDetailData);
  170.         }
  171.     }
  172.  
  173.     SetupDiDestroyDeviceInfoList(hDI);
  174.     return hComm;
  175.  
  176. }
  177.  
  178. void HeartRateRetriever::SomethingElseHappened(BTH_LE_GATT_EVENT_TYPE EventType, PVOID EventOutParameter, PVOID Context)
  179. {
  180.  
  181.     PBLUETOOTH_GATT_VALUE_CHANGED_EVENT ValueChangedEventParameters = (PBLUETOOTH_GATT_VALUE_CHANGED_EVENT)EventOutParameter;
  182.  
  183.     HRESULT hr;
  184.     if (0 == ValueChangedEventParameters->CharacteristicValue->DataSize) {
  185.         hr = E_FAIL;
  186.     }
  187.     else {
  188.         // if the first bit is set, then the value is the next 2 bytes.  If it is clear, the value is in the next byte
  189.         //The Heart Rate Value Format bit (bit 0 of the Flags field) indicates if the data format of
  190.         //the Heart Rate Measurement Value field is in a format of UINT8 or UINT16.
  191.         //When the Heart Rate Value format is sent in a UINT8 format, the Heart Rate Value
  192.         //Format bit shall be set to 0. When the Heart Rate Value format is sent in a UINT16
  193.         //format, the Heart Rate Value Format bit shall be set to 1
  194.         //from this PDF https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=239866
  195.         unsigned heart_rate;
  196.         if (0x01 == (ValueChangedEventParameters->CharacteristicValue->Data[0] & 0x01)) {
  197.  
  198.             heart_rate = ValueChangedEventParameters->CharacteristicValue->Data[1] * 256 + ValueChangedEventParameters->CharacteristicValue->Data[2];
  199.  
  200.         }
  201.         else {
  202.  
  203.             heart_rate = ValueChangedEventParameters->CharacteristicValue->Data[1];
  204.  
  205.         }
  206.  
  207.         heartRate = heart_rate;
  208.  
  209.     }
  210.  
  211. }
  212.  
  213. uint32 HeartRateRetriever::Run()
  214. {
  215.  
  216.     FPlatformProcess::Sleep(0.03);
  217.  
  218.     while (m_kill == false)
  219.     {
  220.  
  221.         if (m_pause)
  222.         {
  223.  
  224.             // FEvent->Wait() will sleep the thread until given a signal Trigger()
  225.             m_semaphore->Wait();
  226.  
  227.             if (m_kill)
  228.             {
  229.  
  230.                 return 0;
  231.  
  232.             }
  233.  
  234.         }
  235.  
  236.         else {
  237.  
  238.  
  239.             //Step 1: find the BLE device handle from its GUID
  240.             GUID AGuid;
  241.             //GUID can be constructed from "{xxx....}" string using CLSID
  242.  
  243. #define TEXT(quote) __TEXT(quote)
  244.  
  245.             CLSIDFromString(TEXT(TO_SEARCH_DEVICE_UUID), &AGuid);
  246.  
  247. #define TEXT(x) TEXT_PASTE(x)
  248.  
  249.             //now get the handle
  250.             HANDLE hLEDevice = GetBLEHandle(AGuid);
  251.  
  252.  
  253.             //Step 2: Get a list of services that the device advertises
  254.             // first send 0,NULL as the parameters to BluetoothGATTServices inorder to get the number of
  255.             // services in serviceBufferCount
  256.             USHORT serviceBufferCount;
  257.             ////////////////////////////////////////////////////////////////////////////
  258.             // Determine Services Buffer Size
  259.             ////////////////////////////////////////////////////////////////////////////
  260.  
  261.             HRESULT hr = BluetoothGATTGetServices(
  262.                 hLEDevice,
  263.                 0,
  264.                 NULL,
  265.                 &serviceBufferCount,
  266.                 BLUETOOTH_GATT_FLAG_NONE);
  267.  
  268.             if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
  269.  
  270.             }
  271.  
  272.             PBTH_LE_GATT_SERVICE pServiceBuffer = (PBTH_LE_GATT_SERVICE)
  273.                 malloc(sizeof(BTH_LE_GATT_SERVICE) * serviceBufferCount);
  274.  
  275.             if (NULL == pServiceBuffer) {
  276.  
  277.             }
  278.             else {
  279.                 RtlZeroMemory(pServiceBuffer,
  280.                     sizeof(BTH_LE_GATT_SERVICE) * serviceBufferCount);
  281.             }
  282.  
  283.             ////////////////////////////////////////////////////////////////////////////
  284.             // Retrieve Services
  285.             ////////////////////////////////////////////////////////////////////////////
  286.  
  287.             USHORT numServices;
  288.             hr = BluetoothGATTGetServices(
  289.                 hLEDevice,
  290.                 serviceBufferCount,
  291.                 pServiceBuffer,
  292.                 &numServices,
  293.                 BLUETOOTH_GATT_FLAG_NONE);
  294.  
  295.             if (S_OK != hr) {
  296.  
  297.             }
  298.  
  299.  
  300.             //Step 3: now get the list of charactersitics. note how the pServiceBuffer is required from step 2
  301.             ////////////////////////////////////////////////////////////////////////////
  302.             // Determine Characteristic Buffer Size
  303.             ////////////////////////////////////////////////////////////////////////////
  304.  
  305.             USHORT charBufferSize;
  306.             hr = BluetoothGATTGetCharacteristics(
  307.                 hLEDevice,
  308.                 pServiceBuffer,
  309.                 0,
  310.                 NULL,
  311.                 &charBufferSize,
  312.                 BLUETOOTH_GATT_FLAG_NONE);
  313.  
  314.             if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
  315.  
  316.             }
  317.  
  318.             PBTH_LE_GATT_CHARACTERISTIC pCharBuffer = NULL;
  319.             if (charBufferSize > 0) {
  320.                 pCharBuffer = (PBTH_LE_GATT_CHARACTERISTIC)
  321.                     malloc(charBufferSize * sizeof(BTH_LE_GATT_CHARACTERISTIC));
  322.  
  323.                 if (NULL == pCharBuffer) {
  324.  
  325.                 }
  326.                 else {
  327.                     RtlZeroMemory(pCharBuffer,
  328.                         charBufferSize * sizeof(BTH_LE_GATT_CHARACTERISTIC));
  329.                 }
  330.  
  331.                 ////////////////////////////////////////////////////////////////////////////
  332.                 // Retrieve Characteristics
  333.                 ////////////////////////////////////////////////////////////////////////////
  334.                 USHORT numChars;
  335.                 hr = BluetoothGATTGetCharacteristics(
  336.                     hLEDevice,
  337.                     pServiceBuffer,
  338.                     charBufferSize,
  339.                     pCharBuffer,
  340.                     &numChars,
  341.                     BLUETOOTH_GATT_FLAG_NONE);
  342.  
  343.                 if (S_OK != hr) {
  344.  
  345.                 }
  346.  
  347.                 if (numChars != charBufferSize) {
  348.  
  349.                 }
  350.             }
  351.  
  352.  
  353.             //Step 4: now get the list of descriptors. note how the pCharBuffer is required from step 3
  354.             //descriptors are required as we descriptors that are notification based will have to be written
  355.             //once IsSubcribeToNotification set to true, we set the appropriate callback function
  356.             //need for setting descriptors for notification according to
  357.             //http://social.msdn.microsoft.com/Forums/en-US/11d3a7ce-182b-4190-bf9d-64fefc3328d9/windows-bluetooth-le-apis-event-callbacks?forum=wdk
  358.             PBTH_LE_GATT_CHARACTERISTIC currGattChar;
  359.             for (int ii = 0; ii < charBufferSize; ii++) {
  360.                 currGattChar = &pCharBuffer[ii];
  361.                 USHORT charValueDataSize;
  362.                 PBTH_LE_GATT_CHARACTERISTIC_VALUE pCharValueBuffer;
  363.  
  364.  
  365.                 ///////////////////////////////////////////////////////////////////////////
  366.                 // Determine Descriptor Buffer Size
  367.                 ////////////////////////////////////////////////////////////////////////////
  368.                 USHORT descriptorBufferSize;
  369.                 hr = BluetoothGATTGetDescriptors(
  370.                     hLEDevice,
  371.                     currGattChar,
  372.                     0,
  373.                     NULL,
  374.                     &descriptorBufferSize,
  375.                     BLUETOOTH_GATT_FLAG_NONE);
  376.  
  377.                 if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
  378.  
  379.                 }
  380.  
  381.                 PBTH_LE_GATT_DESCRIPTOR pDescriptorBuffer;
  382.                 if (descriptorBufferSize > 0) {
  383.                     pDescriptorBuffer = (PBTH_LE_GATT_DESCRIPTOR)
  384.                         malloc(descriptorBufferSize
  385.                             * sizeof(BTH_LE_GATT_DESCRIPTOR));
  386.  
  387.                     if (NULL == pDescriptorBuffer) {
  388.  
  389.                     }
  390.                     else {
  391.                         RtlZeroMemory(pDescriptorBuffer, descriptorBufferSize);
  392.                     }
  393.  
  394.                     ////////////////////////////////////////////////////////////////////////////
  395.                     // Retrieve Descriptors
  396.                     ////////////////////////////////////////////////////////////////////////////
  397.  
  398.                     USHORT numDescriptors;
  399.                     hr = BluetoothGATTGetDescriptors(
  400.                         hLEDevice,
  401.                         currGattChar,
  402.                         descriptorBufferSize,
  403.                         pDescriptorBuffer,
  404.                         &numDescriptors,
  405.                         BLUETOOTH_GATT_FLAG_NONE);
  406.  
  407.                     if (S_OK != hr) {
  408.  
  409.                     }
  410.  
  411.                     if (numDescriptors != descriptorBufferSize) {
  412.  
  413.                     }
  414.  
  415.                     for (int kk = 0; kk < numDescriptors; kk++) {
  416.                         PBTH_LE_GATT_DESCRIPTOR  currGattDescriptor = &pDescriptorBuffer[kk];
  417.                         ////////////////////////////////////////////////////////////////////////////
  418.                         // Determine Descriptor Value Buffer Size
  419.                         ////////////////////////////////////////////////////////////////////////////
  420.                         USHORT descValueDataSize;
  421.                         hr = BluetoothGATTGetDescriptorValue(
  422.                             hLEDevice,
  423.                             currGattDescriptor,
  424.                             0,
  425.                             NULL,
  426.                             &descValueDataSize,
  427.                             BLUETOOTH_GATT_FLAG_NONE);
  428.  
  429.                         if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
  430.  
  431.                         }
  432.  
  433.                         PBTH_LE_GATT_DESCRIPTOR_VALUE pDescValueBuffer = (PBTH_LE_GATT_DESCRIPTOR_VALUE)malloc(descValueDataSize);
  434.  
  435.                         if (NULL == pDescValueBuffer) {
  436.  
  437.                         }
  438.                         else {
  439.                             RtlZeroMemory(pDescValueBuffer, descValueDataSize);
  440.                         }
  441.  
  442.                         ////////////////////////////////////////////////////////////////////////////
  443.                         // Retrieve the Descriptor Value
  444.                         ////////////////////////////////////////////////////////////////////////////
  445.  
  446.                         hr = BluetoothGATTGetDescriptorValue(
  447.                             hLEDevice,
  448.                             currGattDescriptor,
  449.                             (ULONG)descValueDataSize,
  450.                             pDescValueBuffer,
  451.                             NULL,
  452.                             BLUETOOTH_GATT_FLAG_NONE);
  453.                         if (S_OK != hr) {
  454.  
  455.                         }
  456.                         //you may also get a descriptor that is read (and not notify) andi am guessing the attribute handle is out of limits
  457.                         // we set all descriptors that are notifiable to notify us via IsSubstcibeToNotification
  458.                         if (currGattDescriptor->AttributeHandle < 255) {
  459.                             BTH_LE_GATT_DESCRIPTOR_VALUE newValue;
  460.  
  461.                             RtlZeroMemory(&newValue, sizeof(newValue));
  462.  
  463.                             newValue.DescriptorType = ClientCharacteristicConfiguration;
  464.                             newValue.ClientCharacteristicConfiguration.IsSubscribeToNotification = 1;
  465.  
  466.                             hr = BluetoothGATTSetDescriptorValue(
  467.                                 hLEDevice,
  468.                                 currGattDescriptor,
  469.                                 &newValue,
  470.                                 BLUETOOTH_GATT_FLAG_NONE);
  471.                             if (S_OK != hr) {
  472.  
  473.                             }
  474.                             else {
  475.  
  476.                             }
  477.  
  478.                         }
  479.  
  480.                     }
  481.  
  482.                 }
  483.  
  484.  
  485.                 //set the appropriate callback function when the descriptor change value
  486.                 BLUETOOTH_GATT_EVENT_HANDLE EventHandle;
  487.  
  488.                 if (currGattChar->IsNotifiable) {
  489.  
  490.                     BTH_LE_GATT_EVENT_TYPE EventType = CharacteristicValueChangedEvent;
  491.  
  492.                     BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION EventParameterIn;
  493.                     EventParameterIn.Characteristics[0] = *currGattChar;
  494.                     EventParameterIn.NumCharacteristics = 1;
  495.                     hr = BluetoothGATTRegisterEvent(
  496.                         hLEDevice,
  497.                         EventType,
  498.                         &EventParameterIn,
  499.                         SomethingElseHappened,
  500.                         this,
  501.                         &EventHandle,
  502.                         BLUETOOTH_GATT_FLAG_NONE);
  503.  
  504.                     if (S_OK != hr) {
  505.  
  506.                     }
  507.                 }
  508.  
  509.  
  510.                 if (currGattChar->IsReadable) {//currGattChar->IsReadable
  511.                                                ////////////////////////////////////////////////////////////////////////////
  512.                                                // Determine Characteristic Value Buffer Size
  513.                                                ////////////////////////////////////////////////////////////////////////////
  514.                     hr = BluetoothGATTGetCharacteristicValue(
  515.                         hLEDevice,
  516.                         currGattChar,
  517.                         0,
  518.                         NULL,
  519.                         &charValueDataSize,
  520.                         BLUETOOTH_GATT_FLAG_NONE);
  521.  
  522.                     if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
  523.  
  524.                     }
  525.  
  526.                     pCharValueBuffer = (PBTH_LE_GATT_CHARACTERISTIC_VALUE)malloc(charValueDataSize);
  527.  
  528.                     if (NULL == pCharValueBuffer) {
  529.  
  530.                     }
  531.                     else {
  532.                         RtlZeroMemory(pCharValueBuffer, charValueDataSize);
  533.                     }
  534.  
  535.                     ////////////////////////////////////////////////////////////////////////////
  536.                     // Retrieve the Characteristic Value
  537.                     ////////////////////////////////////////////////////////////////////////////
  538.  
  539.                     hr = BluetoothGATTGetCharacteristicValue(
  540.                         hLEDevice,
  541.                         currGattChar,
  542.                         (ULONG)charValueDataSize,
  543.                         pCharValueBuffer,
  544.                         NULL,
  545.                         BLUETOOTH_GATT_FLAG_NONE);
  546.  
  547.                     if (S_OK != hr) {
  548.  
  549.                     }
  550.  
  551.  
  552.                     // Free before going to next iteration, or memory leak.
  553.                     free(pCharValueBuffer);
  554.                     pCharValueBuffer = NULL;
  555.                 }
  556.  
  557.             }
  558.  
  559.             // Go into an inf loop that sleeps. you will ideally see notifications from the HR device
  560.             while (!m_pause && !m_kill)
  561.             {
  562.  
  563.                 prevHeartRate = heartRate;
  564.  
  565.                 Sleep(1000);
  566.  
  567.             }
  568.  
  569.             CloseHandle(hLEDevice);
  570.  
  571.             if (GetLastError() != NO_ERROR &&
  572.                 GetLastError() != ERROR_NO_MORE_ITEMS)
  573.             {
  574.                 // Insert error handling here.
  575.                 return 1;
  576.             }
  577.  
  578.         }
  579.  
  580.     }
  581.  
  582.     return 0;
  583.  
  584. }
  585.  
  586. void HeartRateRetriever::PauseThread()
  587. {
  588.  
  589.     m_pause = true;
  590.  
  591. }
  592.  
  593. void HeartRateRetriever::ContinueThread()
  594. {
  595.  
  596.     m_pause = false;
  597.  
  598.     if (m_semaphore)
  599.     {
  600.  
  601.         // FEvent->Trigger() will wake up the thread
  602.         m_semaphore->Trigger();
  603.  
  604.     }
  605.  
  606. }
  607.  
  608. void HeartRateRetriever::Stop()
  609. {
  610.  
  611.     m_kill = true;
  612.     m_pause = false;
  613.  
  614.     if (m_semaphore)
  615.     {
  616.  
  617.         // Trigger the FEvent in case the thread is sleeping
  618.         m_semaphore->Trigger();
  619.  
  620.     }
  621.  
  622. }
  623.  
  624. void HeartRateRetriever::EnsureCompletion()
  625. {
  626.  
  627.     Stop();
  628.  
  629.     if (Thread)
  630.     {
  631.  
  632.         Thread->WaitForCompletion();
  633.  
  634.     }
  635.  
  636. }
  637.  
  638. bool HeartRateRetriever::IsThreadPaused()
  639. {
  640.  
  641.     return (bool)m_pause;
  642.  
  643. }
  644.  
  645. int HeartRateRetriever::GetHeartRate()
  646. {
  647.  
  648.     m_mutex.Lock();
  649.  
  650.     int HR = heartRate;
  651.  
  652.     m_mutex.Unlock();
  653.  
  654.     return HR;
  655.  
  656. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement