29 Janvier 2014 : J-10 avant l’édition des techdays 2014.
Dans mon précédant billet, nous avons vu comment préparer l’environnement de développement afin d’être dans les meilleurs conditions pour développer un driver, ainsi que la création d’une coquille vide pour notre driver.
Dans ce billet, nous allons nous atteler à intégrer notre driver aux Sensor APIS et faire en sorte qu’il soit reconnu comme un driver de type OrientationSensor.
Rappelez-vous, dans le squelette de notre driver, nous avions une classe CMyDriver qui dans sa méthode OnDeviceAdd, instanciait la classe CMyDevice, par l’intermédiaire d’une méthode statique CreateInstanceAndInitialize. Cette méthode appel elle même la méthode Initialize() qui a pour but essentiel, d’instancier la classe CMyDevice et de retourner un pointeur IUnknown sur la classe CMyDevice au Framework WDF.
CMyDevice::Initialize(
__inIWDFDriver * FxDriver,
__inIWDFDeviceInitialize * FxDeviceInit
)
{
IWDFDevice *fxDevice = NULL;
HRESULT hr = S_OK;
IUnknown *unknown = NULL;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
//
// Configure things like the locking model before we go to create our
// partner device.
//
//
// Set the locking model.
//
FxDeviceInit->SetLockingConstraint(None);
//
// Set power policy ownership.
//
FxDeviceInit->SetPowerPolicyOwnership(TRUE);
hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to get IUnknown %!hresult!",
hr);
goto Exit;
}
hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
DriverSafeRelease(unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to create a framework device %!hresult!",
hr);
goto Exit;
}
m_spWdfDevice = fxDevice;
//
// Drop the reference we got from CreateDevice. Since this object
// is partnered with the framework object they have the same
// lifespan - there is no need for an additional reference.
//
DriverSafeRelease(fxDevice);
//TODO : Init the SensorDDI here but move to OnPrepareHdware when link with the Oculus Rift
Exit:
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
return hr;
}
La ligne this->QueryInterface(__uuidof(IUnknown), (void **)&unknown) récupère le pointeur IUnknown sur l’instance courante de la classe CMyDevice, que l’on fournit au Framework WDF par l’intermédiaire de la méthode CreateDevice de l’instance de la classe IWDFDriver passée en paramètre de la méthode Initialize.
FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice)
Si on passe un pointeur sur notre classe, c’est que l’on souhaite que le Framework WDF nous rappel. Pour ce faire, la classe CMyDevice qui a pour rôle de préparer le périphérique et de l’initialiser, doit implémenter entre autre, deux Interfaces IPnpCallBackHardware2 et IPnpCallback comme illustré dans le code suivant :
Module Name:
Device.h
Abstract:
This module contains the type definitions of the
device driver.
Environment:
Windows User-Mode Driver Framework (WUDF)
--*/
#pragmaonce
#include"Internal.h"
#include"SensorsManager.h"
//
// Class for the device.
//
classCMyDevice :
publicCComObjectRootEx<CComMultiThreadModel>,
publicIPnpCallback,
publicIPnpCallbackHardware2,
publicIFileCallbackCleanup
{
public:
DECLARE_NOT_AGGREGATABLE(CMyDevice)
BEGIN_COM_MAP(CMyDevice)
COM_INTERFACE_ENTRY(IPnpCallback)
COM_INTERFACE_ENTRY(IPnpCallbackHardware2)
COM_INTERFACE_ENTRY(IFileCallbackCleanup)
END_COM_MAP()
CMyDevice();
~CMyDevice();
//
// Private data members.
//
private:
//
// Weak reference to framework device object.
//
IWDFDevice * m_FxDevice;
CComPtr<IWDFDevice> m_spWdfDevice;
//
// Weak reference to I/O queue
//
CMyIoQueue * m_IoQueue;
//CComObject<SensorManager>* m_pSensorManager;
//
// Private methods.
//
private:
HRESULT
Initialize(
__inIWDFDriver *FxDriver,
__inIWDFDeviceInitialize *FxDeviceInit
);
HRESULT InitializeSensorManager(_In_IWDFDevice3 * pWdfDevice, _In_IWDFCmResourceList * pWdfResourcesRaw, _In_IWDFCmResourceList * pWdfResourcesTranslated);
volatileDWORD64 m_dwShutdownControlFlags;
inlinevoid EnterShutdown();
inlinevoid ExitShutdown();
//
// Public methods
//
public:
inlineHRESULT EnterProcessing(DWORD64 dwControlFlag);
inlinevoid ExitProcessing(DWORD64 dwControlFlag);
HRESULT ProcessIoControl(
_In_IWDFIoQueue* pQueue,
_In_IWDFIoRequest* pRequest,
_In_ULONG ControlCode,
SIZE_T InputBufferSizeInBytes,
SIZE_T OutputBufferSizeInBytes,
DWORD* pcbWritten);
//
// The factory method used to create an instance of this driver.
//
static
HRESULT
CreateInstanceAndInitialize(
__inIWDFDriver *FxDriver,
__inIWDFDeviceInitialize *FxDeviceInit,
__outCMyDevice **Device
);
HRESULT
Configure(
VOID
);
//
// COM methods
//
public:
//
// IPnpCallback
//
STDMETHOD(OnD0Entry)(_In_IWDFDevice* pWdfDevice, _In_WDF_POWER_DEVICE_STATE previousState);
STDMETHOD(OnD0Exit)(_In_IWDFDevice* pWdfDevice, _In_WDF_POWER_DEVICE_STATE newState);
STDMETHOD_(VOID, OnSurpriseRemoval)(_In_IWDFDevice* pWdfDevice);
STDMETHOD_(HRESULT, OnQueryRemove)(_In_IWDFDevice* pWdfDevice);
STDMETHOD_(HRESULT, OnQueryStop)(_In_IWDFDevice* pWdfDevice);
// IPnpCallbackHardware2
STDMETHOD_(HRESULT, OnPrepareHardware)(
_In_IWDFDevice3 * pWdfDevice,
_In_IWDFCmResourceList * pWdfResourcesRaw,
_In_IWDFCmResourceList * pWdfResourcesTranslated);
STDMETHOD_(HRESULT, OnReleaseHardware)(
_In_IWDFDevice3 * pWdfDevice,
_In_IWDFCmResourceList * pWdfResourcesTranslated);
// IFileCallbackCleanup
STDMETHOD_(VOID, OnCleanupFile)(_In_IWDFFile *pWdfFile);
private :
CComObject<SensorsManager>* m_pSensorManager;
};
IPnpCallBackHardware2expose deux méthodes à implémenter, OnPrepareHardware et OnReleaseHardware, qui seront respectivement appelées par le Framework WDF lors de l’initialisation du driver ou lorsqu’il sera releasé.
// IPnpCallbackHardware2
//
HRESULTCMyDevice::OnPrepareHardware(_In_IWDFDevice3 * pWdfDevice,
_In_IWDFCmResourceList * pWdfResourcesRaw,
_In_IWDFCmResourceList * pWdfResourcesTranslated)
{
HRESULT hr = (NULL != pWdfDevice) ? S_OK : E_UNEXPECTED;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
if (SUCCEEDED(hr))
{
hr = EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
if (nullptr != pWdfDevice)
{
m_spWdfDevice = pWdfDevice;
}
hr = this->InitializeSensorManager(pWdfDevice,pWdfResourcesRaw,pWdfResourcesTranslated);
}
ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
return hr;
}
HRESULTCMyDevice::OnReleaseHardware(_In_IWDFDevice3 * pWdfDevice, _In_IWDFCmResourceList * pWdfResourcesTranslated)
{
UNREFERENCED_PARAMETER(pWdfDevice);
UNREFERENCED_PARAMETER(pWdfResourcesTranslated);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
HRESULT hr = S_OK;
EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
if (m_pSensorManager != nullptr)
{
hr = m_pSensorManager->Stop();
m_pSensorManager->Unitialize();
DriverSafeRelease<CComObject<SensorsManager>>(m_pSensorManager);
}
DriverSafeRelease<IWDFDevice>(m_FxDevice);
ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Ici nous ne faisons qu’instancier et initialiser la classe SensorManager, que nous détaillerons un peu plus loin.
IPnpCallBack quand à elle, expose entre autre la méthode OnD0Entry qui nous sert essentiellement ici à démarrer le SensorManager.
__inIWDFDevice* pWdfDevice,
__inWDF_POWER_DEVICE_STATEpreviousState
)
{
UNREFERENCED_PARAMETER(previousState);
UNREFERENCED_PARAMETER(pWdfDevice);
HRESULT hr = S_OK;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
hr = EnterProcessing(PROCESSING_IPNPCALLBACK);
if (SUCCEEDED(hr))
{
if (m_pSensorManager != nullptr)
{
hr = m_pSensorManager->Start();
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_CRITICAL, TRACE_DEVICE, "Failed to initialize sensors hr=%!HRESULT!", hr);
}
}
}
ExitProcessing(PROCESSING_IPNPCALLBACK);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
return hr;
}
Le Framework WDF appelle la méthode OnD0Entry afin de notifier qu’il entre dans un état dit Full Power et qu’il peut fournir toute les fonctionnalités à l’utilisateur.
Dans notre exemple, la méthode Start() de notre classe SensorManager a pour but :
- d’instancier la classe Sensor (que nous verrons dans un prochain billet), qui aura pour rôle :
- de faire le lien avec le capteur physique,
- de récupérer les données du capteur,
- de les convertir dans le bon format pour qu’elles soient compréhensibles par les APIs Sensor de Windows.
- d’informer le Framework WDF des capacités, des propriétés et du type de driver (OrientationSensor),
- d’identifier lorsqu’un client se connecte/déconnecte, ou s’abonne/Désabonne à un évènement,
- de retourner des données au client.
- de répondre au demande entrante (ProcessIoControl),
- de poster un évènement (PostEvent) lorsque des données sont disponibles,
- et de poster l’état du périphérique à un instant T (PosteStateChange)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
HRESULT hr = S_OK;
if (FALSE == IsInitialized())
{
if (SUCCEEDED(hr))
{
m_pSensor = std::unique_ptr<Sensor>(newSensor());
hr = m_pSensor->InitializeSensor(m_spWdfDevice,m_pWdfResourcesRaw,m_pWdfResourcesTranslated);
if (SUCCEEDED(hr))
{
m_pSensorList.AddTail(m_pSensor.get());
}
}
if (SUCCEEDED(hr))
{
hr = this->InitializeDDIAndClassExtension();
}
if (SUCCEEDED(hr))
{
m_fSensorManagerInitialized = TRUE;
m_fInitializationComplete = TRUE;
}
}
if (SUCCEEDED(hr))
{
// Step 1: Create the Data Changed Event Handle
m_hSensorEvent = ::CreateEvent(NULL, // No security attributes
FALSE, // Automatic-reset event object
FALSE, // Initial state is non-signaled
NULL); // Unnamed object
if (m_pSensor != nullptr)
{
m_pSensor->SetDataEventHandle(m_hSensorEvent);
m_pSensor->m_fInitialDataReceived = FALSE;
}
else
{
hr = E_UNEXPECTED;
}
}
// Step 2: Activate & Create and start the eventing thread
if (SUCCEEDED(hr))
{
Activate();
m_hSensorManagerEventingThread = ::CreateThread(NULL, // Cannot be inherited by child process
0, // Default stack size
&SensorsManager::_SensorEventThreadProc, // Thread proc
(LPVOID)this, // Thread proc argument
0, // Starting state = running
NULL);
if (nullptr == m_hSensorManagerEventingThread)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! sensor event thread failed to create");
hr = HRESULT_FROM_WIN32(::GetLastError());
}
else
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! sensor event thread successfully created");
}
if (SUCCEEDED(hr))
{
m_fSensorManagerInitialized = TRUE;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! sensor initialization completed ");
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! sensor event initialization failed");
}
}
if (SUCCEEDED(hr))
{
m_fDeviceStopped = FALSE;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
return hr;
}
{
HRESULT hr = (NULL == m_spClassExtension) ? S_OK : E_UNEXPECTED;
/*hr = EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);*/
if (FAILED(hr)) return hr;
hr = CComObject<SensorDDI>::CreateInstance(&m_pSensorDDI);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Failed to create the device");
return hr;
}
m_pSensorDDI->AddRef();
m_pSensorDDI->m_pSensorManager = this;
//TODO: Juste for debug an comprehension;
m_pSensorDDI->SetDevice(m_pDevice);
hr = m_pSensorDDI->Initialize(m_spWdfDevice, nullptr, nullptr);
if (FAILED(hr)) return hr;
CComPtr<IUnknown> spUnknown;
hr = m_pSensorDDI->QueryInterface(IID_IUnknown, (void**) &spUnknown);
if (FAILED(hr)) return hr;
hr = CoCreateInstance(CLSID_SensorClassExtension, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_spClassExtension));
if (REGDB_E_CLASSNOTREG == hr)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Class is not registered, hr = %!HRESULT!", hr);
hr = E_UNEXPECTED;
m_spClassExtension = nullptr;
}
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Could not create the sensor class extension");
return hr;
}
if (m_spClassExtension != nullptr)
{
if (m_spWdfDevice != nullptr)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "Initializing the SensorClassExtension");
hr = m_spClassExtension->Initialize(m_spWdfDevice, spUnknown);
}
}
if (FAILED(hr)) return hr;
hr = m_pSensorDDI->SetSensorClassExtension((m_spClassExtension));
//ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_CRITICAL, TRACE_DEVICE, "Abnormal results during hardware initialization, hr = %!HRESULT!", hr);
}
return hr;
}
La classe SensorDDI comme je l’ai dit plus haut, doit informer des capacités du driver, pour ce faire elle doit implémenter l’interface ISensorDriver et ses méthodes associées afin que le Framework WDF puisse les rappeler.
{
public:
SensorDDI();
virtual ~SensorDDI();
DECLARE_NOT_AGGREGATABLE(SensorDDI)
BEGIN_COM_MAP(SensorDDI)
COM_INTERFACE_ENTRY(ISensorDriver)
END_COM_MAP()
ULONG m_clientCount; //for this first version manage only one client
ULONG m_countSubscriber;
BOOL m_clientSubscribe;
//Public methods
public :
void SetDevice(CMyDevice *device);
HRESULT Initialize(
_In_IWDFDevice* pWdfDevice,
_In_IWDFCmResourceList * pWdfResourcesRaw,
_In_IWDFCmResourceList * pWdfResourcesTranslated);
HRESULT InitializeSensorDriverInterface(_In_IWDFDevice* pWdfDevice);
HRESULT InitializeClientManager();
VOID Uninitialize();
HRESULT SetSensorClassExtension(
_In_ISensorClassExtension* pClassExtension);
// COM Interface methods
public:
// ISensorDriver methods
HRESULTSTDMETHODCALLTYPE OnGetSupportedSensorObjects(
_Out_IPortableDeviceValuesCollection** ppSensorObjectCollection
);
HRESULTSTDMETHODCALLTYPE OnGetSupportedProperties(
_In_ LPWSTR pwszObjectID,
_Out_IPortableDeviceKeyCollection** ppSupportedProperties
);
HRESULTSTDMETHODCALLTYPE OnGetSupportedDataFields(
_In_ LPWSTR pwszObjectID,
_Out_IPortableDeviceKeyCollection** ppSupportedDataFields
);
HRESULTSTDMETHODCALLTYPE OnGetSupportedEvents(
_In_ LPWSTR pwszObjectID,
_Out_GUID** ppSupportedEvents,
_Out_ULONG* pulEventCount
);
HRESULTSTDMETHODCALLTYPE OnGetProperties(
_In_ IWDFFile* pClientFile,
_In_ LPWSTR pwszObjectID,
_In_ IPortableDeviceKeyCollection* pProperties,
_Out_IPortableDeviceValues** ppPropertyValues
);
HRESULTSTDMETHODCALLTYPE OnGetDataFields(
_In_ IWDFFile* pClientFile,
_In_ LPWSTR pwszObjectID,
_In_ IPortableDeviceKeyCollection* pDataFields,
_Out_IPortableDeviceValues** ppDataValues
);
HRESULTSTDMETHODCALLTYPE OnSetProperties(
_In_ IWDFFile* pClientFile,
_In_ LPWSTR pwszObjectID,
_In_ IPortableDeviceValues* pPropertiesToSet,
_Out_IPortableDeviceValues** ppResults
);
HRESULTSTDMETHODCALLTYPE OnClientConnect(
_In_IWDFFile* pClientFile,
_In_LPWSTR pwszObjectID
);
HRESULTSTDMETHODCALLTYPE OnClientDisconnect(
_In_IWDFFile* pClientFile,
_In_LPWSTR pwszObjectID
);
HRESULTSTDMETHODCALLTYPE OnClientSubscribeToEvents(
_In_IWDFFile* pClientFile,
_In_LPWSTR pwszObjectID
);
HRESULTSTDMETHODCALLTYPE OnClientUnsubscribeFromEvents(
_In_IWDFFile* pClientFile,
_In_LPWSTR pwszObjectID
);
HRESULTSTDMETHODCALLTYPE OnProcessWpdMessage(
_In_IUnknown* pUnkPortableDeviceValuesParams,
_In_IUnknown* pUnkPortableDeviceValuesResults
);
// ISensorDeviceCallback methods
HRESULTSTDMETHODCALLTYPE OnNewData(
_In_IPortableDeviceValues* pValues
);
public :
SensorsManager* m_pSensorManager;
private:
CComPtr<ISensorClassExtension> m_spClassExtension;
CComObject<ClientManager>* m_pClientManager;
LPWSTR GetSensorObjectID();
// Make calls to client thread safe
CComAutoCriticalSection m_ClientCriticalSection;
CComAutoCriticalSection m_CacheCriticalSection;
CMyDevice* m_device; //TODO: Juste for debug now
CComPtr<IWDFDevice2> m_spWdfDevice2;
Sensor *m_pSensor;
BOOL m_fSensorInitialized;
mutableCComAutoCriticalSection m_CriticalSection;
BOOL m_fSensorManagerInitialized;
HANDLE m_hSensorEvent;
HANDLE m_hSensorManagerEventingThread; // Thread handle for raising data events
BOOL m_fThreadActive; // Flag denoting that driver thread is active
BOOL m_fDeviceStopped; // Flag denoting that CSensorManager->Stop() was called
};
Tout d’abord un driver de type OrientationSensor d’après le document Integrating Motion and Orientation Sensors (Page 46), doit supporter un certain nombre de propriétés comme illustré sur la figure suivante :
{
WPD_OBJECT_ID,
SENSOR_PROPERTY_TYPE,
SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID,
SENSOR_PROPERTY_MANUFACTURER,
SENSOR_PROPERTY_MODEL,
SENSOR_PROPERTY_SERIAL_NUMBER,
SENSOR_PROPERTY_FRIENDLY_NAME,
SENSOR_PROPERTY_DESCRIPTION,
SENSOR_PROPERTY_CONNECTION_TYPE,
SENSOR_PROPERTY_CHANGE_SENSITIVITY,//TODO:
SENSOR_PROPERTY_ACCURACY, //TODO:
SENSOR_PROPERTY_STATE,
SENSOR_PROPERTY_MIN_REPORT_INTERVAL,
SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL,
WPD_FUNCTIONAL_OBJECT_CATEGORY,
};
Sans détailler la totalité des propriétés que le driver doit supporter, il y en a qui parle d’elle même, les plus importantes seront :
WPD_OBJECT_ID, qui sera initialisée avec un identifiant unique (exemple “OculusRift42”)
WPD_FUNCTIONAL_OBJECT_CATEGORY, définie à la valeur SENSOR_CATEGORY_ORIENTATION , comme son nom l’indique permet de définir la catégorie du driver comme étant un driver Orientation.
SENSOR_PROPERTY_TYPE, initialisé à la valeur SENSOR_TYPE_AGGREGATED_DEVICE_ORIENTATION permet d’indiquer que c’est un driver Orientation, qu’il va agréger des données provenant de plusieurs capteurs (intégrés en faite à l’Oculus Rift) et les retourner sous forme de Quaternion ou d’une matrice 3X3.
SENSOR_PROPERTY_CONNECTION_TYPE définie SENSOR_CONNECTION_TYPE_PC_ATTACHED indique que le périphérique est un périphérique externe au PC à l’inverse de SENSOR_CONNECTION_TYPE_PC_INTEGRATED qui définie que le périphérique est intégré au PC.
SENSOR_PROPERTY_STATE qui permet de définir l’état du périphérique tels que SENSOR_STATE_READY, SENSOR_STATE_NOT_AVAILABLE, SENSOR_STATE_NO_DATA, SENSOR_STATE_INITIALIZING , SENSOR_STATE_ACCESS_DENIED , SENSOR_STATE_ERROR
SENSOR_MIN_REPORT_INTERVAL et SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL déterminele lapse de temps entre chaque levée d’évènement.
SENSOR_PROPERTY_CHANGE_SENSITIVITY cette propriété détermine l’angle entre deux Quaternions pour lequel les données seront retournées au client. (Sujet que nous aborderons dans notre prochain Billet)
Note : Ces deux dernières propriétés peuvent être modifiées par le client. Toutes ces propriétés sont initialisées par défaut lors de l’initialisation de la classe Sensor.
Pour obtenir les propriétés supportées par le driver ainsi que les valeurs associées, le Framework WDF, appellera successivement les méthodes OnGetSupportedSensorObjects, OnGetSupportedProperties, OnGetProperties.
HRESULTSensorDDI::OnGetSupportedSensorObjects(_Out_IPortableDeviceValuesCollection** ppSensorObjectCollection)
{
UNREFERENCED_PARAMETER(ppSensorObjectCollection);
TraceEvents(TRACE_LEVEL_INFORMATION,TRACE_DDI, "%!FUNC! Entry");
HRESULT hr = S_OK;
m_device->EnterProcessing(PROCESSING_ISENSORDRIVER);
if (NULL == ppSensorObjectCollection)
{
returnE_POINTER;
}
CComPtr<IPortableDeviceValuesCollection> spObjects;
CComPtr<IPortableDeviceKeyCollection> spKeys;
CComPtr<IPortableDeviceValues> spValues;
hr = spObjects.CoCreateInstance(CLSID_PortableDeviceValuesCollection);
if (FAILED(hr))
return hr;
hr = OnGetSupportedProperties(GetSensorObjectID(), &spKeys); //the first parameters is the object ID, I don't need it at this time
if (FAILED(hr))
return hr;
CComPtr<IWDFFile> spTemp; //Not use at this time
hr = OnGetProperties(spTemp, GetSensorObjectID(), spKeys, &spValues);
if (FAILED(hr))
return hr;
hr = spObjects->Add(spValues);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get the supported sensor objects hr = %!HRESULT!", hr);
return hr;
}
*ppSensorObjectCollection = spObjects.Detach();
m_device->ExitProcessing(PROCESSING_ISENSORDRIVER);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr= %!HRESULT!", hr);
return hr;
}
HRESULTSensorDDI::OnGetSupportedProperties( _In_ LPWSTRpwszObjectID,
_Out_IPortableDeviceKeyCollection** ppSupportedProperties)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
UNREFERENCED_PARAMETER(pwszObjectID); //Only One Sensor Device is supported, so no need to use de ObjectID at this time but it seems the rift has more than one sensors
HRESULT hr = S_OK;
hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(ppSupportedProperties));
if (SUCCEEDED(hr))
{
if (m_pSensor == nullptr)
{
hr = E_HANDLE;
}
if (SUCCEEDED(hr))
{
m_pSensor->GetSupportedProperties(ppSupportedProperties);
}
}
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get supported sensor properties %!HRESULT!", hr);
return hr;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
HRESULTSensorDDI::OnGetSupportedDataFields(_In_ LPWSTRpwszObjectID,_Out_IPortableDeviceKeyCollection** ppSupportedDataFields)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
UNREFERENCED_PARAMETER(pwszObjectID);
HRESULT hr = S_OK;
// CoCreate a collection to store the supported property keys.
hr = CoCreateInstance(
CLSID_PortableDeviceKeyCollection,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(ppSupportedDataFields));
// Add supported property keys for the specified object to the collection
if (SUCCEEDED(hr) && ppSupportedDataFields != nullptr)
{
//hr = CopyKeys(m_spSupportedSensorDataFields, *ppSupportedDataFields);
hr = m_pSensor->GetSupportedDataFields(ppSupportedDataFields);
}
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor data fields hr = %!HRESULT!", hr);
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
HRESULTSensorDDI::OnGetProperties( _In_ IWDFFile* pClientFile,
_In_ LPWSTRpwszObjectID,_In_
IPortableDeviceKeyCollection* pProperties,
_Out_IPortableDeviceValues** ppPropertyValues)
{
UNREFERENCED_PARAMETER(pwszObjectID);
UNREFERENCED_PARAMETER(pClientFile);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
HRESULT hr = S_OK;
DWORD keyCount = 0;
CComPtr<IPortableDeviceValues> pValues;
if ((pProperties == nullptr) || ppPropertyValues == nullptr) returnE_INVALIDARG;
hr = pProperties->GetCount(&keyCount);
if (FAILED(hr)) return hr;
hr = pValues.CoCreateInstance(CLSID_PortableDeviceValues);
if (FAILED(hr)) return hr;
BOOL fError = FALSE;
for (DWORD i = 0; i < keyCount; i++)
{
PROPERTYKEY key;
hr = pProperties->GetAt(i, &key);
if (SUCCEEDED(hr))
{
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hrTemp = m_pSensor->GetProperty(key, &var);
if (SUCCEEDED(hrTemp))
{
pValues->SetValue(key, &var);
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get the sensor property value hr = %!HRESULT!", hrTemp);
pValues->SetErrorValue(key, hrTemp);
fError = TRUE;
}
if ((var.vt & VT_VECTOR) == 0)
{
// For a VT_VECTOR type, PropVariantClear()
// frees all underlying elements. Note pValues
// now has a pointer to the vector structure
// and is responsible for freeing it.
// If var is not a VT_VECTOR, clear it.
PropVariantClear(&var);
}
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get property key hr = %!HRESULT!", hr);
}
}
if (fError)
{
hr = S_FALSE;
}
*ppPropertyValues = pValues.Detach();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Le driver doit également préciser le type de données qu’il retourne, en l’occurrence entre autre un Quaternion et une matrice 3X3.
{
SENSOR_DATA_TYPE_QUATERNION,
SENSOR_DATA_TYPE_ROTATION_MATRIX,
SENSOR_DATA_TYPE_TIMESTAMP,
};
Le Framework invoquera alors la méthode OnGetSupportedDataFields pour obtenir cette information.
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
UNREFERENCED_PARAMETER(pwszObjectID);
HRESULT hr = S_OK;
// CoCreate a collection to store the supported property keys.
hr = CoCreateInstance(
CLSID_PortableDeviceKeyCollection,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(ppSupportedDataFields));
// Add supported property keys for the specified object to the collection
if (SUCCEEDED(hr) && ppSupportedDataFields != nullptr)
{
//hr = CopyKeys(m_spSupportedSensorDataFields, *ppSupportedDataFields);
hr = m_pSensor->GetSupportedDataFields(ppSupportedDataFields);
}
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor data fields hr = %!HRESULT!", hr);
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Le driver doit également être capable de gérer des évènements, lorsque des données sont disponibles et lorsque le driver change d’état.
{
SENSOR_EVENT_DATA_UPDATED, 0,
SENSOR_EVENT_STATE_CHANGED, 0,
};
Le Framework WDF invoquera la méthode OnGetSupportedEvents pour obtenir la liste des évènement supportés par le driver.
{
UNREFERENCED_PARAMETER(pwszObjectID);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
HRESULT hr = S_OK;
ULONG count = ARRAY_SIZE(g_SupportedRiftEvents);
GUID* pBuf = (GUID*) CoTaskMemAlloc(sizeof(GUID) *count);
if (pBuf != nullptr)
{
for (DWORD i = 0; i < count; count++)
{
*(pBuf + i) = g_SupportedRiftEvents[i].fmtid;
}
*ppSupportedEvents = pBuf;
*pulEventCount = count;
}
else
{
hr = E_OUTOFMEMORY;
*ppSupportedEvents = nullptr;
*pulEventCount = 0;
}
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor events hr = %!HRESULT!", hr);
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Continuons, rappelez vous, que notre driver est à utiliser avec des APIs de plus haut niveau et avec le Windows Runtime 8.1, ces APIs se trouvent dans l’espace de nom Windows.Devices.Sensors et pour notre driver plus précisément Windows.Devices.Sensors.OrientationSensor
Lorsque que nous recherchons un sensor, comme illustré dans le code suivant en C# :
{
OrientationSensor sensor = OrientationSensor.GetDefault();
}
La méthode GetDefault() est appelée, le Framework WDF invoque la méthode OnClientConnect qui notifie le driver qu’un client se connecte et OnClientDisconnect c’est l’inverse.
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
UNREFERENCED_PARAMETER(pwszObjectID);
if (pClientFile == nullptr) returnE_INVALIDARG;
HRESULT hr = S_OK;
// Synchronize access to the client manager so that after
// the client connects it can query the new client
// count atomically
CComCritSecLock<CComAutoCriticalSection>
scopeLock(m_ClientCriticalSection);
m_clientCount = +1; //TODO: Here queue the number of client connecter by default one.
if (m_clientCount == 1)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "First client, so stop idle detection");
// When using a power managed queue we are guaranteed
// to be in D0 during OnClientConnect, so there is no need
// to block on this call. It's safe to touch hardware at
// this point. There is potential, however, to temporarily
// transition from D0->Dx->D0 after this call returns, so be
// sure to reconfigure the hardware in D0Enty.
hr = m_spWdfDevice2->StopIdle(false);
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
L’utilisateur peut demander directement des données au Sensor, comme illustré dans le code C# suivant :
{
OrientationSensor sensor = OrientationSensor.GetDefault();
OrientationSensorReading data = sensor.GetCurrentReading();
}
Cette fois-ci, le Framework WDF invoque la méthode, OnGetDataFields et les données seront retournées par l’intermédiaire d’un pointeur sur la collection IPortableDeviceValues.
_In_ LPWSTRpwszObjectID,
_In_ IPortableDeviceKeyCollection* pDataFields,
_Out_IPortableDeviceValues** ppDataValues)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
UNREFERENCED_PARAMETER(pwszObjectID);
UNREFERENCED_PARAMETER(pClientFile);
UNREFERENCED_PARAMETER(pDataFields);
HRESULT hr = S_OK;
CComPtr<IPortableDeviceValues> pValues;
hr = pValues.CoCreateInstance(CLSID_PortableDeviceValues);
BOOL IsNewData;
hr=m_pSensor->GetOrientationFromRift(pValues,&IsNewData);
m_pSensor->SetTimeStamp(pValues);
*ppDataValues = pValues.Detach();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Plutôt que demander les données, l’utilisateur peut s’abonner à l’évènement ReadingChanged, comme illustré dans le code C# suivant :
{
OrientationSensor sensor = OrientationSensor.GetDefault();
sensor.ReadingChanged += sensor_ReadingChanged;
}
void sensor_ReadingChanged(OrientationSensor sender, OrientationSensorReadingChangedEventArgs args)
{
var data = args.Reading;
}
Le Framework WDF, invoquera alors la méthode OnClientSubscribeToEvents/OnClientUnsubscribeFromEvents.
_In_LPWSTRpwszObjectID)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
UNREFERENCED_PARAMETER(pClientFile);
UNREFERENCED_PARAMETER(pwszObjectID);
CComCritSecLock<CComAutoCriticalSection> scopeLock(m_ClientCriticalSection);
HRESULT hr = S_OK;
if (pClientFile == nullptr) returnE_INVALIDARG;
m_countSubscriber += 1;
if (m_clientSubscribe == FALSE&& m_countSubscriber==1) //Only One suscriber to the event
{
m_clientSubscribe = TRUE;
m_pSensor->SetOnlyOneSuscriber(1);
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
HRESULTSensorDDI::OnClientUnsubscribeFromEvents(_In_IWDFFile* pClientFile,_In_LPWSTRpwszObjectID)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
UNREFERENCED_PARAMETER(pClientFile);
UNREFERENCED_PARAMETER(pwszObjectID);
CComCritSecLock<CComAutoCriticalSection> scopeLock(m_ClientCriticalSection);
HRESULT hr = S_OK;
m_countSubscriber -= 1;
if (m_countSubscriber == 0) //no more client are connected to the sensor
{
m_clientSubscribe = FALSE;
m_pSensor->SetOnlyOneSuscriber(0);
}
//TODO: When a client unsuscribe clean the last quaternion in the oculus rift
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Dés que de nouvelles données seront disponibles, un évènement PostEvent sera levé afin de retourner les données au client.
Le client peut également modifier certaines propriétés comme SENSOR_PROPERTY_CHANGE_SENSITIVITY ou SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL de la manière suivante :
{
OrientationSensor sensor = OrientationSensor.GetDefault();
sensor.ReadingChanged += sensor_ReadingChanged;
sensor.ReportInterval = 10; //Valeur Exprime en millisecondes
}
Dans ce cas la , le Framework WDF invoque la méthode OnSetProperties afin que le driver utilise un nouveau lapse de temps entre chaque levé d’évènement.
HRESULTSensorDDI::OnSetProperties(_In_ IWDFFile* pClientFile,
_In_ LPWSTRpwszObjectID,
_In_ IPortableDeviceValues* pPropertiesToSet,
_Out_IPortableDeviceValues** ppResults)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
UNREFERENCED_PARAMETER(pwszObjectID);
HRESULT hr = S_OK;
if ((pClientFile == nullptr) || (pPropertiesToSet == nullptr) || (ppResults == nullptr))
{
returnE_INVALIDARG;
}
if (SUCCEEDED(hr))
{
DWORD count = 0;
hr = CoCreateInstance(
CLSID_PortableDeviceValues,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(ppResults));
if (SUCCEEDED(hr))
{
hr=pPropertiesToSet->GetCount(&count);
}
if (SUCCEEDED(hr))
{
for (DWORD i = 0; i < count; i++)
{
PROPERTYKEY key = WPD_PROPERTY_NULL;
PROPVARIANT var;
PropVariantInit(&var);
hr = pPropertiesToSet->GetAt(i, &key, &var);
if (SUCCEEDED(hr))
{
m_pSensor->SetProperty(key, &var, nullptr);
}
}
}
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
return hr;
}
Voici en quelques lignes, comment notre driver s’interface avec les APIs Sensor de Windows.
Vue d’ensemble de l’architecture du Driver :
Image may be NSFW.
Clik here to view.
Dans le prochain billet, nous aborderons :
- La manière dont les évènements sont retournés à l’appelant,
- Comment formater les données pour qu’elles soit compréhensibles par les APis Sensor.
- Enfin nous brancheront notre driver à l’Oculus Rift.
Eric
Image may be NSFW.Clik here to view.