Quantcast
Viewing all articles
Browse latest Browse all 29128

Les coulisses du Techdays 2014 : Développer un driver pour l’Oculus Rift : Part II

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.

HRESULT
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.

HRESULTCMyDevice::OnD0Entry(
    __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 :

  1. 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’instancier la classe SensorDDI (DDI =Device Driver Interface), qui a pour rôle :
    • 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.
  • d’instancier la classe ISensorClassExtension fournit par Windows  pour simplifier le développement de driver. Cette classe permet :
  • de démarrer un Thread qui sera à l’écoute, lorsque de nouvelles données seront disponible, que nous détaillerons également dans notre prochain billet.
  •  

    HRESULTSensorsManager::Start()
    {
        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;

    }
    HRESULTSensorsManager::InitializeDDIAndClassExtension()
    {

        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.

    classSensorDDI : publicCComObjectRoot, publicISensorDriver
    {
    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 :

    constPROPERTYKEY g_SupportedRiftProperties[] =
    {
        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, OnGetSupportedPropertiesOnGetProperties.


    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.

    constPROPERTYKEY g_SupportedRiftDataFields[] =
    {
        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.

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

    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.

    constPROPERTYKEY g_SupportedRiftEvents[] =
    {
        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.

    HRESULTSensorDDI::OnGetSupportedEvents(_In_  LPWSTRpwszObjectID, _Out_GUID** ppSupportedEvents, _Out_ULONG* pulEventCount)
    {
        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# :

    privatevoid ActivateOrientationSensor()
            {
                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.

    HRESULTSensorDDI::OnClientConnect(_In_IWDFFile* pClientFile,_In_LPWSTRpwszObjectID)
    {    
        
        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 :

    privatevoid ActivateOrientationSensor()
            {
                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.

    HRESULTSensorDDI::OnGetDataFields(    _In_  IWDFFile* pClientFile,
                                        _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 :

    privatevoid ActivateOrientationSensor()
            {
                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.

    HRESULTSensorDDI::OnClientSubscribeToEvents(_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;
        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 :

    privatevoid ActivateOrientationSensor()
            {
                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.
    archi

    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.

    Viewing all articles
    Browse latest Browse all 29128

    Trending Articles