Introducing The Windows Driver Foundation


Introduzione

In questo tutorial farò un'introduzione al WDF (Windows Driver Foundation), dato che di tutorial in giro se ne trovano pochissimi (e in italiano nessuno) eppoi ormai avevo promesso un tutorial alla giulia. Introduzione perché il WDF al momento è ancora in beta testing e uscirà con Longhorn (anche se come dopo spiegherò si può già codare driver WDF se si entra nel programma beta di microsoft). Nonostante ciò sarà comunque disponibile anche per Win2k, WinXP e Win2k3. Ora, siccome questo non è un tutorial sull'architettura di windows o su come codare driver in generale, mi aspetto che chi legga 'sto tutorial sappia cosa significhino le parole IRP, IRQL, PnP, Power Managment, WDM, ecc. Insomma se non sapete nulla del coding in Kernel Mode su windows è inutile (o quasi) che leggiate. La maggior parte delle informazioni le ho prese da microsoft.com (ve n'è qualcuna anche su osronline.com, ma poche), personalmente mi sono anche visto le sessioni WinHec riprese a Seattle ce ne sono circa una 10 di un'ora ciascuna (sono veramente interessantissime).

Ora come tutti sappiamo il corrente modello per i device driver è il WDM, ci consente di fare Function Drivers, Minidrivers, Filter Drivers ecc. per ogni utilizzo. Nonostante sia un modello molto potente il WDM è soggetto anche a delle "limitazioni", ve le elenco in breve perché chi coda WDM le conosce benissimo, diciamo che le metto solo per fare il punto della situazione per chi invece non è così ferrato:

- Non sono proprio facili da codare i WDM, chi ha avuto a che fare con PnP, Power Management ecc lo sa.

- Le attuali DDI (Device Driver Interfaces) sono state progettate molto tempo fa e il fatto che rendano disponibili ai drivers strutture interne del sistema operativo, ha fatto in modo che il loro aggiornamento sia molto difficile e spesso impossibile per motivi di retrocompatibilità. Questo è forse uno degli argomenti di cui si sente più parlare nei newsgroup o nei forum sul driver-coding, spesso programmatori incitano altri programmatori a non basare il propri driver su strutture che potrebbero cambiare da versione a versione di Windows. La verità è che spesso le strutture proibitive servono per fare cose banalissime, il primo esempio che mi viene in mente è il modo in cui si ottiene l'image name di un processo in kernel mode, cosa che in user mode come tutti sappiamo è facilissima grazie alle api nel kernel32 e alle psapi. Be' a ring 0 le cose sono un po' diverse, solo per fare ciò è necessario accedere alla PEB nella struttura EPROCESS e poi basarsi su grandezze fisse per ottenere la stringa. Eccovi un esempio di codice che scrissi diverso tempo fa:

PWCHAR GetCurrentProcessName(PWCHAR Name)   
{
   PEPROCESS CurProc;      
   BYTE *Ptr;
   ULONG_PTR Offset, Offset2 = 0;
   
   RtlFillMemory(Name, ((MAX_PATH + 1) * sizeof (WCHAR)), 0);

   CurProc = PsGetCurrentProcess();     
       
   // 0x1B0 : position of _PEB in _EPROCESS structure
         
   Ptr = (PBYTE) CurProc + 0x1B0;                  
           
   RtlCopyMemory(&Offset, Ptr, sizeof (ULONG_PTR));
       
   Ptr = (PBYTE)(Offset + 0x10);                 
    
   // 0x10 : position of ProcessParameters in _PEB structure
    
   RtlCopyMemory(&Offset, Ptr, sizeof (ULONG_PTR));
    
   // 0x3C : position of ImagePath in ProcessParameters

   Ptr = (PBYTE)(Offset + 0x3C);                 
     
   RtlCopyMemory(&Offset2, Ptr, sizeof (WORD));

   RtlCopyMemory(Name, (PBYTE)(Offset + Offset2), MAX_PATH * sizeof(WCHAR));
   
   // DbgPrint("ImagePath:[%ws]\n", Name);
   
   return Name;
}

Il problema di questo codice è che, come ben capite, potrebbe non poter più fungere su una nuova versione di windows, anzi codare in questa maniera è pericoloso perché può facilmente portare a belle BSOD. Insomma è chiaro perché soprattutto questa caratteristica delle presenti DDI ne ha spesso limitato l'estensione o comunque la modifica.

- Vi sono troppi Miniport Models. I miniport sono drivers WDM che a causa del relativamente poco lavoro che devono fare si interfacciano con un altro driver che svolge gran parte del lavoro al posto loro. Ci sono troppi modelli sui cui un miniport si può basare per fare una determinata cosa. Ogni modello può essere del tutto diverso dall'altro e quindi anche il miniport driver cambierà totalmente a seconda del modello su cui si basa (sarebbe anche solo da considerarsi la documentazione necessaria per ogni modello). Inoltre i modelli devono esser aggiornati ogni qualvolta sia richiesto e dubito che ciò venga fatto. Come se non bastasse la microsoft ci ricorda che sviluppare per device multifunzione usando un solo modello è quasi impossibile, sicuramente sarà necessario sviluppare un bus driver (WDM) e due miniports.

- Gli attuali driver girano praticamente tutti in kernel mode. Non sarebbe bello se il driver della nostra webcam usb nel caso crashasse non portasse giù insieme a sé tutto l'os? Voglio dire come ben sapete un semplice errore in kernel mode ci assicura una bella Blue Screen Of Death... Questo secondo me è uno dei punti più essenziali del discorso WDF, infatti non sono così tanti i programmatori di driver e alcuni tra questi non sanno veramente programmare driver, ma cosa vi aspettate, anche le normali applicazioni crashano, non ditemi che non vi è mai crashato nulla, certo i driver in genere contengono meno codice delle applicazioni normali e vengono "in genere" anche testati parecchio prima di rilasciarli proprio perché errori di coding possono costare una sessione importante ad un utente che quasi sicuramente si lamenterà, ma non tutte le aziende son così serie da assumere professionisti di razza e state pur certi che alcuni dei driver, che avete sul pc, sono codati alla cazzo di cane. Il WDF fa sì che alcuni driver che non devono fare cose particolarmente critiche, possano girare in user mode. Ma di questo parleremo dopo.

Bene adesso che abbiamo fatto il punto della situazione resta la domanda? Cosa è il WDF? È un modello di driver molto diverso da quelli attuali, che permette forse meno controllo/potere, ma è più facile da codare e può (non necessariamente) girare in user mode. Vabbe' questa è una spiegazione alla buona, in verità c'è molto di più da dire. Vediamo le principali caratteristiche di questo modello:

- Gli IRP non vengono più passati direttamente al driver. Al loro posto vengono usate routine di callback quali: Create / Close / Ioctl / Read / Write / Start / Stop.

- Un driver nello stack non può essere posto al di sopra di un altro driver (cosa molto comune come molti sapranno negli attuali driver). Il driver deve compiere le operazioni I/O quando la callback adibita viene richiamata. E questo si può dire sarà un bel problema se qualche programmatore aveva pensato di fare rootkit col WDF. Vabbe' pace tanto il WDF non è comunque indicato per altri motivi che dopo vedremo.

- Le allocazioni in memoria saranno semplificate, tutte le allocazioni saranno non-paged pool. Così da non doversi preoccupare dell'IRQL nel caso stessimo usando un paged-pool. Questo è un genere di errore molto comune fra i newbies, che si meravigliano di ricevere una BSOD quando non sono a passive level.

- Le richieste I/O saranno bufferate (o meglio buffered) e non ci sarà più bisogno di gestire le memory descriptor lists (MDL per gli amici).

- Si potranno generare interfacce semplici per comunicare col driver da applicazioni user mode.

- Tutte le chiamate del driver sono serializzate (ehm serialized), ovvero vengono passate una alla volta, cosicché non vi sia il rischio che più thread si sovrappongano chiamando qualcosa (evviva niente lavori con locks vari). Resta comunque anche la possibilità di sincronizzare a mano.

- Ci saranno funzioni che aiuteranno a modificare il PCI Configuration Space. Nei WDM è necessario mandare l'irp IRP_MN_QUERY_INTERFACE per far ritornare dei pointer per modificare il config space oppure usare HalGetBusData()/HalGetBusDataByOffset. Inoltre funzioni di questo genere verrano messe a disposizione anche per bus USB e IEEE 1394.

- Sono supportati solo i Power State: D0 e D3. Nei WDM gestire il Power State è abbastanza un casino. Non pochi sono i driver che non sanno riprendersi da uno standby, infatti un errore in quel settore è abbastanza frequente. Un comune Power Management per una periferica usb può costare dai 40 kb in su di codice. Anzi è possibile trovare in giro driver che non fanno andare il sistema in standby perché il coder non sapeva come scrivere la ripresa dallo sleeping.

- I driver saranno muniti di due callback, Start and Stop, per gestire il PnP. Ma del PnP (come anche del Power Management) ne parliamo più approfonditamente in avanti, dato che probabilmente la sua gestione è una delle caratteristiche più affascinanti del WDF.

- Le DDI del WDF saranno in grado di gestire eventi asincroni come interrupt e dati in arrivo su bus protocollati, cosicché anche quel lavoro non lo si deve fare a mano.

Queste alcune delle caratteristiche principali. Per non parlare del fatto che probabilmente sarà possibile codare anche in C# dei driver WDF. Come è a tutti noto, il 99% dei nostri drivers (WDM, nativi NT4 ecc) son codati in C, pochi in C++. Coll'introduzione del WDF sarà sicuramente più comune il C++, ma credo, da quanto ho potuto capire, che la microsoft non si vuol assolutamente lasciar sfuggire la fetta di programmatori C# (con tutto il rispetto per questo linguaggio nel quale anche io programmo). In effetti in diversi ambiti ho visto proliferare un uso pesante di questo nuovo linguaggio, e non mi stupirei se la microsoft vuol fare anche questo passo, che, come ben si capisce, non è da poco. Bisogna dire anche un'altra cosa, nelle comunità di driver c'è spesso un forte senso di rifiuto nei confronti del WDF, vi sono molti puristi che vedono il WDF come un'immane tragedia (un saluto a Xoanon): fondamentalmente la paura sta nell'astrattezza che il WDF porta nel coding di driver (senza nemmeno accennare al coding in C#). Ma d'altronde la tecnologia si sta spostando in una direzione ben precisa, è inevitabile. Chiusa la parentesi. Ad ogni modo il WDF al momento in cui scrivo è ancora un modello sotto sviluppo, anzi facendo parte della programma beta (facilmente reperibile sul sito microsoft) è possibile scrivere qualche driver e far sapere alla MS cosa si vorrebbe fosse introdotto/cambiato nel modello. A quanto pare la MS è sinceramente interessata al feedback dei programmatori di driver, vista anche la sua comunicazione con le community e le richieste fatte durante le sessioni dei winhec. Comunque quel che avete letto finora è stato solo un assaggio giusto per prender gusto a quello che gli americani chiamano double-di-ef. Partiamo dai fondamenti, ovvero il framework.
 

WDF Framework

In pratica il WDF è composto da due framework, uno user mode e uno kernel mode. La sessione winhec 2004 di introduzione ha illustrato brevemente le caratteristiche dei due framework. Entrambi supportano PnP, Power Management, I/O asincrono basato sui pacchetti e filtri. Anche il processo di installazione dei driver resta uguale a quello dei WDM e si applica sia ai kernel mode driver sia a quelli user mode, non fa differenza. Per i driver kernel mode però non vi sarà il riavvio in caso di crash, cosa che invece è prevista per quelli user mode. Infine il linguaggio inizialmente previsto per codare in kernel mode sarà il C (poi anche il C++) e in user mode C++ e C# (e il managed code consente la software isolation che garantirà una grande capacità di evitare crash e di recupero).

Ecco quella che dovrebbe essere la struttura del framework user mode:



Dove il Driver Manager è il processo wdfmgr.exe e il suo compito è di caricare il driver che sarebbe l'Host Driver Process. In pratica i vari componenti dialogano tra loro tramite il Redirector che è un componente che gira in kernel mode. Vedendo un po' più nei particolari:



Come vedete il driver ha accesso alle api win32. Però come disse il pakistano durante la sessione winhec, non sarà possibile chiamare api che riguardano le gui, cioè è un servizio non ci potete fare finestrelle anche se lavorate in user mode. Le potenziali applicazioni per il framework user mode sono lettori multimediali portatili, scanner, foto e videocamere, periferiche connesse tramite rete, tutte le periferiche basate su bus protocollati (tipo USB, IEEE 1394, IP) con eccezione di quelle che si occupano di input, visualizzazione, storage o rete. Una delle parti più importanti del WDF sono i tools di debugging e di verifica (statica e dinamica), nonostante ciò ho deciso di non trattarli in questo tutorial, poiché richiederebbe troppo e poi voglio parlare della programmazione non del debug. Probabilmente scriverò un tutorial in futuro su questo argomento. Il modo più comune di interfacciarsi al driver resta l'io control che però presenta diverse limitazioni in fatto di sicurezza, intanto non è type safe e per questo motivo non sarà possibile usarlo in managed code applications. Cosa comunque che sarà facilmente raggirabile. Come abbiamo visto nello schema del framework soprastante, vi sono, all'interno dell'Host Driver Process i WDF Objecets... Ma cosa sono?


WDF Object Model

Il WDF si basa su oggetti, vediamo le direttive che la MS ha seguito per la progettazione:

- Fornire alle DDI un fondamento concettuale (oggetti non semplici liste di funzioni C).
- Definire un set di comportamenti comune a tutti gli oggetti.
- Fornire un modello col quale sia facile scalare da implementazioni semplici a implementazioni complesse (tipo passare da una implementazione miniport per fare tutt'altra cosa).
- Isolare il driver da dettagli di implementazione interna (come si era già detto prima).
- Fare in modo che sia possibile far girare due versioni diverse di implementazioni (ovvero il framework è un file sys che gira indipendemente dal ntoskrnl.exe e quindi anche due versioni diverse del framework possono girare contemporaneamente).
- Fare in modo che in futuro sia possibile un supporto per altri linguaggi.
- Usare concetti di oggetti familiari a programmatori WDM.

a loro volta gli oggetti:

- Rappresentano driver, device, queue ecc.
- Hanno prorietà, metodi e eventi (gli eventi sono callback (serializzate) nel nostro driver).
- Possono aver Context Memory (tipo device extension per WDM).
- Hanno reference counts.
- Sono organizzati gerarchicamente, con parentela padre/figlio.
- Vi si fa riferimento tramite handle, non puntatori (sempre a proposito di astrazione).

Come già detto più volte vi è la serializzazione automatica degli eventi, ma anche il cleanup è possibile da parte del framework. Se per esempio un oggetto viene chiuso (ovvero il suo reference count passa a 0) tutti i suoi figli (tramite la relazione gerarchica) vengon chiusi dal framework. Inoltre è possibile associare qualsiasi oggetto al device object in modo da avere il cleanup automatico durante l'unload (ma si può associare anche a qualsiasi altro oggetto, non per forza il device object). Il driver interagisce col framework e viceversa tramite le DDI in questi termini: il driver crea gli oggetti che gli servono, definisce routine di callback per gli eventi degli oggetti e può associare Context Memory a qualsiasi dei suoi oggetti, il framework, da parte sua, gestisce i reference count degli oggetti (anche se volendo è possibile aggiungere / rimuovere reference manualmente, se per esempio si vuol essere sicuri che un certo oggetto non venga distrutto), gestisce le parentele e chiama le routine di callback del driver nel caso si verifichi un evento. La Context Memory, come già detto, può essere associata (e quindi allocata) a qualsiasi tipo di oggetto e provvede la possibilità di associare all'oggetto informazioni specifiche. Si accede a tale memoria tramite un puntatore, il quale è possibile ottenere tramite l'handle dell'oggetto. Più di una Context Memory può essere associata a un oggetto se differisce nel tipo. Ovviamente se l'oggetto viene distrutto la sua (o sue) Conext Memory viene automaticamente deallocata.

Le convenzioni per il C DDI Set sono fondamentalmente:

- Tutti i riferimenti a oggetti sono effettuati tramite handle.
- L'handle è il primo parametro passato a qualsiasi funzione DDI del WDF.
- Ogni DDI è classificata come una proprietà, un metodo o un evento, a seconda della semantica e del valore di ritorno.
- Le callback all'interno del nostro diver sono eventi (e questo spero sia chiaro ormai).
- La naming convention è Wdf<ObjectType><Operation>, esempi:
   - WdfObjectDelete(WDFOBJECT Object)
   - WdfRequestComplete(WDFREQUEST Request, NTSTATUS Status)
   - WdfDevicePowerReference(WDFDEVICE Device, BOOLEAN WaitForD0)

Andiamo avanti.
 

Proprietà, Metodi ed Eventi

Le proprietà sono quel che nei WDM è il diretto accesso ai campi dell'oggetto. Tipo se abbiamo una struttura e settiamo un parametro Struct.Param = Param in questa maniera si è effettuato un accesso diretto (hardcoded), proprio quel che spesso complica la vita ai programmatori MS nel caso di patch o fix. Invece col WDF arrivano le proprietà a cui si accede con metodi Get/Set (nei quali non possono verificarsi errori). La chiamata Get ritorna con l'informazione richiesta e la Set ritorna void (dato che comunque non possono verificarsi errori). Ecco un esempio di naming convention per due metodi Get/Set:

ValueName WdfObjectGetValueName(WDFOBJECT Object)
VOID WdfObjectSetValueName(WDFOBJECT Object)

In generale la naming convention dei metodi è WDFSTATUS WdfObjectMethod(WDFOBJECT Object, …), giusto per ribadire. E sui metodi non c'è altro da aggiungere.

Gli eventi possono avvertire il nostro driver per una richiesta, un cambiamento del power state del sistema, un interrupt ecc. Le callback sono definite dal nostro driver e vengono chiamate se si verifica un evento, inoltre sono definite di default dal WDF se il nostro driver non si preoccupa di definire delle proprie. La suddetta serializzazione automatica degli eventi non è sempre obbligatoria, per alcuni oggetti è possibile gestirla manualmente (vedremo fra due secondi). La naming convention usata nei driver per gli eventi è: EvtObjectEventName. Gli eventi che hanno a che fare con la lifetime o, per dirla più chiaramente, la distruzione dell'oggetto sono EvtObjectDestroy e EvtObjectCleanup. Il primo evento corrisponde IRP_MJ_CLOSE, è chiamato quando il reference count va a 0, la Context Memory dell'oggetto viene liberata. Il secondo, invece, è simile a IRP_MJ_CLEANUP e può essere usato per prevenire reference counts circolari (Circular Reference Counts). Questi si verificano quando un oggetto figlio fa riferimento al padre e il padre a sua volta fa riferimento (anche indirettamente) al figlio. Questo può portare a memory leaks, quindi nell'evento di cleanup il driver deve eliminare questi reference count circolari, rimuovendo, per esempio, i propri reference (intendo dire se ha fatto uso di WdfObjectReference per aggiungere un reference manualmente). In ogni caso il framework non distrugge l'oggetto finché il reference count non scende a 0.


Serializzazione

La serialization, come precedentemente detto, riguarda gli eventi, ovvero le callback. È opzionale e comunque anche se utilizzata non riguarda l'oggetto la cui (o le cui) callback è serializzata, l'oggetto continua a rispondere normalmente. La serializzazione manuale si effettua tramite WdfObjectAcquireLock e  WdfObjectReleaseUnlock. Il driver può specificare a quale IRQL le callback vengono chiamate. È possibile specificare dispatch level (utile per la sincronizzazione diretta con dpc o timer) o passive level (nel caso di quest'ultimo il framework rinvia automaticamente a un workitem che ci richiama a passive se necessario, ovvero se qualcuno ci chiama a un altro IRQL). Si può volere o no la serializzazione, e se sì è possibile usare la struttura di configurazione per specificare i suddetti parametri:

typedef enum _WDF_SERIALIZATION_SCOPE {
    WdfSerializationNonSpecified = 0x00,
    WdfSerializationScopeObject,
    WdfSerializationScopeDevice
} WDF_SERIALIZATION_SCOPE;

typedef enum _WDF_EXECUTION_LEVEL {
    WdfExecutionLevelNonSpecified = 0x00,
    WdfExecutionLevelPassive,
    WdfExecutionLevelDispatch
} WDF_EXECUTION_LEVEL;

typedef struct _WDF_CALLBACK_CONSTRAINTS {
    ULONG                    Size;
    WDF_SERIALIZATION_SCOPE  SerializationScope;
    WDF_EXECUTION_LEVEL      ExecutionLevel;
    BOOLEAN                  SaveFloatingPointState;
} WDF_CALLBACK_CONSTRAINTS, *PWDF_CALLBACK_CONSTAINTS;

Gli accessi nelle callback degli eventi alla Context Memory sono serializzati automaticamente (se si accede esternamente la serializzazione è necessaria farla manualmente (almeno se si teme una sovrapposizione) tramite WdfObjectAcquireLock e WdfObjectReleaseLock). Però se associamo i nostri dati alla Context Memory di un dato oggetto e accediamo a questi solo nelle callback è chiaro che nessuna serializzazione dovremmo farla manualmente, il framework se ne occuperà al nostro posto: cosa abbastanza comoda direi.


Core WDF Objects

Vi sono oggetti che vedrete molto spesso nei sorgenti WDF perché di grande utilità, questi oggetti sono per esempio WDFDPC, WDFWORKITEM, WDFTIMER ecc. Ma ci sono oggetti che vedrete ancora più spesso, per non dire sempre, nei sorgenti WDF perché facenti parte del core del modello. La microsoft ne elenca quattro: WDFDRIVER, WDFDEVICE, WDFQUEUE e WDFREQUEST.

WDFDRIVER è l'oggetto creato da WdfDriverCreate, in pratica corrisponde all'inizializzazione del framework, dato che prima di questa chiamata (da fare nella driver entry) non possiamo usare nessun'altra DDI. Questo oggetto fornisce due eventi. Il primo, EvtDriverDeviceAdd, viene chiamato quando un device viene assegnato al nostro driver (un PnP device, s'intende). Il secondo, EvtDriverUnload, è opzionale, nel senso che, anche se si registra EvtDriverDeviceAdd, questo evento è solo richiesto se ci sono dati esterni al framework da liberare  (il framework fa il cleanup dei propri dati) o altre cose da fare. Ecco un esempio di come si crea un oggetto WDFDRIVER:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT  DriverObject,
           IN PUNICODE_STRING RegistryPath)
{
    WDF_DRIVER_CONFIG config;
    WDFDRIVER driver;
    NTSTATUS status;

    //
    // inizializza la struttura config
    //

    WDF_DRIVER_CONFIG_INIT(&config, EvtDriverDeviceAdd);

    //
    // chiama come prima cosa WdfDriverCreate
    //

    status = WdfDriverCreate(DriverObject, RegistryPath, NULL, &config, &driver);

    return status;
}

Adesso, come anche nel WDM c'è il device object, nel WDF abbiamo il WDFDEVICE, tramite cui si effettuano la maggior parte delle operazioni. In poche parole tutto l'IO gira lì. Corrisponde a una singola istanza di un device che il nostro driver supporta. Il device object lo si crea nella EvtDriverDeviceAdd.

NTSTATUS DeviceCreate(WDFDEVICE_INIT Device)
{
    WDFSTATUS status;
    WDFDEVICE  device;
    WDF_FDO_EVENT_CALLBACKS fdoCallbacks;

    //
    // specifica che il device obj è un fdo
    // e ne specifica le callback,
    // dei pdo e fdo parleremo in seguito
    // per ora basta dire il dev obj può
    // essere configurato come uno dei due
    //

    WDF_FDO_EVENT_CALLBACKS_INIT(&fdoCallbacks);

    fdoCallbacks.EvtDeviceStart           = EvtDeviceStart;
    fdoCallbacks.EvtDeviceRemove     = EvtDeviceRemove;

    WdfFdoInitSetEventCallbacks(DeviceInit, &fdoCallbacks);

    // crea WDFDEVICE

    status = WdfDeviceCreate(DeviceInit, &attributes, &device);

    return status;
}

Se, dopo aver creato il WDFDEVICE decidiamo di creare qualche altro oggetto che ci serve tipo queue, dpc ecc e lo associamo al device, non ritorniamo con uno status success, il framework si occupa di distruggere il device e tutti i suoi figli.

WDFREQUEST è quello che nel WDM chiamiamo irp. Sarebbero richieste di IO, le proprietà dell'oggetto corrispondono ai campi del irp.

WDFQUEUE si associa al device object e se ne possono associare di più allo stesso. La WDFQUEUE rappresenta una specifica coda di richieste IO. Si può richiedere in che modo le richieste siano mandate al driver: serialmente (ovvero una alla volta), parallelamente (le richieste vengono mandate quando arrivano) e manualmente (il driver si preoccupa manualmente di processare le richieste).


Conclusione

Be' per questa volta direi che può bastare, in questo articolo ho fornito un po' di fondamenti, nel prossimo mi vorrei occupare di PnP e Power Management (e anche altre cose), anche se temo non basterà un articolo solo per discutere gli argomenti restanti, quindi probabilmente suddividerò il lavoro in più parti (anche se ancora non ho un piano ben preciso). Oltretutto c'è da dire che questi articoli sicuramente andranno rivisti in molti punti all'uscita vera e propria del WDF e non so nemmeno se continuerò a scriverli in italiano.

Alla Prossima!

Daniel Pistelli