Singleton: Implementazione di un Data Layer

by sandro 5. January 2009 23:43

Solitamente ci viene sempre ricordato come sia importante in un'applicazione dividere nettamente la parte relativa all'interfaccia grafica (Presentation Layer) dalla parte relativa all'accesso ai dati (Data Layer). Questo è sicuramente utile perchè offre la possibilità di scrivere del codice che sia indipendente dalle tecniche di accesso ai dati. Quello che solitamente si fa in questi casi è frapporre un ulteriore livello (Business Layer) il quale si preoccuperà di astrarre completamente i meccanismi di accesso ai dati, fornendo al presentation layer una interfaccia che rimarrà tale anche se dovesse cambiare il meccanismo di accesso ai dati. Vediamo un semplice esempio in figura:

La classe Form1 istanzia un oggetto di tipo BIZ che contiene la logica di recupero delle informazioni che però devono essere restituite in formato di 'ogetto' Employee. L'istanza di BIZ a sua volta crea un oggetto Data Layer che restituisce un dataReader. Con quest'ultimo l'istanza biz scorrerà il dr per poter costruire un'istanza di tipo Employee. A questo punto non rimane che restituire tale istanza al Presentation Layer. Come si vede dalla figura, nel momento in cui cambiasse modalità di accesso al DB, lo strato di business riuscirebbe a nascondere questo cambiamento, continuando a restituire sempre lo stesso oggetto di tipo Employee.

Esistono comunque dei casi in cui, per motivi di spazio e di performance, è utile utilizzare solo i due livelli di Presentation e Data Layer. E' il caso di alcune applicazioni Mobile. Supponiamo però di avere un'applicazione mobile che utilizza diverse finestre Windows Form. Se ognuna di queste Form dovesse accedere al DB dovrebbe inevitabilmente istanziarsi una classe data layer, come in figura:

A questo punto ecco che ci viene utile il Singleton. Invece di permettere ad ogni Form di istanziare il proprio oggetto business, facciamo in modo tale che ciascun oggetto FORM possa utilizzare l'unica istanza di tipo Data, presente nella nostra applcazione. Un Singleton appunto: una classe che permette la creazione di una sola singola istanza:

Qualcuno potrebbe domandarsi perchè non costruire a questo punto una classe statica. Il motivo è che in una classe statica devo necessariamente utilizzare metodi statici e proprietà statiche. Questo limiterebbe la nostra istanza in uno schema estremamaente rigido che non ci servirebbe alla fine più di tanto. Come si fa a costruire una classe singleton? Innanzitutto è necessario fornire un costruttore non pubblico che verrà chiamato all'interno della classe stessa SOLO 1 VOLTA allorchè un oggetto FORM richiederà per la prima volta un'istanza del Singleton.

Dentro la classe Singleton poi inseriremo dei metodi per poter accedere al DB. Tali metodi ovviamente offriranno meno flessibilità rispetto alla presenza di una classe Business, ma daranno una maggiore performance sia dal punto di vista di occupazione di memoria, sia dal punto di vista della veloctà di accesso al DB. Vediamo in figura come nella dichiarazione della classe ci sono varie proprietà relative al DB da utilizzare. Una in particolare però è la proprieta di tipo ContosoDB che viene dichiarata static proprio perchè sarà l'unica istanza dell'applicazione.

Scorrendo poi il codice troviamo il costruttore. Come si riesce a capire dal sorgente, il costruttore inizializza le variabili precedenti tranne la proprietà ContosoDB. Il fulcro poi è rappresentato dal metodo GetInstance nel quale ci si chiede se la precedente proprietà è null. Se si allora è la prima volta che qualcuno chiede una istanza del singleton e invochiamo il costruttore, altrimenti non è la prima volta che viene chiesta l'istanza e restituiamo quindi l'oggetto già creato.

A questo punto siamo in grado di utilizzare la nostra classe singleton all'interno di tutte le finestre Form della nostra applicazione prendendo un'istanza tramite il metodo GetInstance e poi invocare i relativi metodi quali per esempio GetAllItems() dentro il quale si accede al DB tramite i metodi delle classi SQL. Vediamo infine come è possibile sfruttare la classe Singleton dalla Windows Form:

 In conclusione, il pattern Singleton è un'ottima soluzione nelle applicazione Windpws Mobile in cui più soggetti sono interessati ad accedere al DB tramite una classe del livello Data. Ciò farà sicuramente risparmiare memoria e aumentare le performance di accesso.

sv.

Tags: ,

Integrazione di codice Unmanaged con codice Managed in applicazioni Windows Mobile - Quarta Parte

by sandro 5. January 2009 23:25

Ritorno al...managed

Dopo essere partiti dal codice managed nella prima parte, attraversato i confini di .NET nella seconda ed essersi immersi nel codice nativo nella terza parte, vediamo in questa sessione conclusiva come ripassare al codice gestito restituendo i risultati ottenuti dalla DLL scritta in C++. Ricordo che dalla funzione della DLL ci aspettiamo una chiamata ad un metotdo di callback passandogli un intero e una stringa. Il metodo di callback che raccoglie i parametri dalla parte unmanaged si chiama "ReceivedCall" che tra non molto vedremo nel dettaglio. Prima di analizzare questo metodo, vorrei sottoporre all'attenzione di tutti il meccanismo tramite il quale si va ad aggiornare l'interfaccia grafica, inserendo l'integer e la stringa in due textbox.

La classe ControlInvoker 

Durante alcuni giri su google, ho trovato degli esempi su QuickStart della Microsoft in cui veniva esposta questa classe estremamente utile per un sacco di motivi. Vi consiglio di guardarla e, nel caso utilizzaste dei meccanismi di multithreading, di utilizzarla per ottenere un miglior codice. Come viene chiaramente spiegato nel QuickStart Tutorial, il .NET Compact Framework non offre il supporto per le chiamate asincrone BeginInvokeEndInvoke, ma sopratturro non supporta il passaggio dei parametri nelle chiamate sincrone effettuate con Invoke. Questo sarebbe estremamente limitativo nel nostro caso in quanto il metodo di callback ha bisogno di invocare un metodo (relativo al thread della Form) per passargli l'intero e la stringa che ha ricevuto dalla DLL e far così aggiornare la GUI. Ci tengo a ricordare che il metodo di clallback NON può aggiornare lui stesso la GUI poichè si violerebbe un precetto fondamentale di meccanismo di "Apartment" che impone che solo il thread che possiede gli oggetti grafici (quali textbox, bottoni o qualunque cosa si trovi nella interfaccia grafica) possa aggiornarli, altrimenti dobbiamo passare tramite Invoke. C'è però l'handicap del passaggio di parametri non supportato. La classe ControlInvoker nasce proprio per aggirare questa limitazione. Vediamo com'è fatta questa classe:

La prima cosa che deve essere fatta (ed è quello che facciamo nel codice della nostra applicazione) è quello di creare un'istanza della classa ControlInvoker passandogli l'istanza della Form (quindi this) di cui vogliamo invocare i metodi. Quando all'interno del Managed Listener Thread vogliamo invocare un metodo per aggiornare la GUI effettueremo questa chiamata:

a definizione del delegate MethodCallInvoker è questa:

public delegate void MethodCallInvoker(object[] o);

Quindi, tramite questa definizione di delegate,  stiamo passando un metodo, "UpdateCallArriving", e i suoi parametri all'istanza controlInvoker, la quale sarà lei a pensare a nascondere il metodo con i suoi parametri ed effettuare la corretta invocazione. il metodo invocato avrà quindi la seguente firma ed il seguente comportamento:

Finalmente i risultati provenienti dalla parte unmanaged vengono aggiornati sulla interfaccia grafica.

Conclusioni

In queste quattro sessioni abbiamo visto molti aspetti interessanti che riguardano i meccanismi di integrazione tra mondo managed e mondo unmanaged all'interno dei dispositivi mobile. Vi sottolineo l'importanza del marshalling tra i due mondi che viene risolto tramite Platform Invoke, ma che non sempre appare liscio come ci aspettiamo, anche con dati apparentemente banali come le stringhe. Come tradizione vi lascio tutto il codice di cui potete fare il download facendo attenzione che è stato complato per Windows Mobile 6. Se non avete l'SDK per Windows Mobile 6 o lo scaricate dal sito Microsoft oppure riportate pari pari lo stesso codice in un progetto per Windows Mobile 5 e ricompilate. Nessuna modifica è necessaria, nell'un o o nell'altro caso, alla DLL nativa. l'unica cosa a cui dovete fare attenzione è che in fase di deploy coincidano le directory in cui vengono memorizzati sul device sia il .exe sia la DLL, poichè al omento dell'invocazione della load Library si presuppone che siano nella stessa cartella. Rimango comunque a in attesa di vostri eventali commenti e/o suggerimenti migliorativi.

sv.

Tags: , , ,

Integrazione di codice Unmanaged con codice Managed in applicazioni Windows Mobile - Terza Parte

by sandro 5. January 2009 23:22

La DLL unmanaged

Dopo essere arrivati nella seconda parte a sfondare il confine del codice gestito, siamo arrivati in questa terza parte ad analizzare la DLL unmanaged che contiene tutta la logica di business che gestisce gli eventi provenienti da qualche driver. Vediamo subito il codice:

 

Come vediamo la funzione principale, richiamata dalla parte managed tramite Platform Invoke, si aspetta un puntatore a funzione con una firma ben precisa: un intero e una stringa in input e un intero in output. All'interno della funzione inseriamo il costrutto daemon a cui deleghiamo la costruzione di un thread apposito per effettuare il vero e proprio lavoro di listening degli eventi.

Il thread esegue la funzione "MyReceivingCallFunction". In maniera molto simbolica la funzione in questione dorme 6 secondi e poi termina. Al termine viene segnalato un oggetto su cui era rimasta in ascolto "ReadyToReceiveCall". La Wait in questione era una wait infinita, bloccante. Quando si sblocca, viene chiamato il metodo di callback, passandogli un intero ed una stringa. Da notare il metodo che viene utilizzato per poter passare una stringa alla parte managed.

Tags: , , ,

Integrazione di codice Unmanaged con codice Managed in applicazioni Windows Mobile - Seconda Parte

by sandro 5. January 2009 22:49

Start Listening

Riprendiamo la figura che abbiamo lasciato nella prima parte di questo tutorial:

 

1) Al click di un bottone presente sulla form, si genererà un altro thread che si occuperà di rimanere in ascolto delle chiamate provenienti dalla parte unmanaged. Queste chiamate indicheranno che un certo evento è successo nella parte unmanaged e dovremo quindi informare l'utente dell'interfaccia

2) Il "Listener thread" comunicherà al suo omologo unmanaged un delegate. In questo delegate c'è il nome di un metodo che sarà successivamente chiamato in corrispondenza dell'evento atteso. Per effettuare questa comunicazione si utilizza la tecnica del Platform Invoke. Un delegate sarà interpretato nella parte unmanaged come un puntatore a funzione.

3) Adesso siamo nella DLL non gestita. il Listener unmanaged crea un altro thread e proprio in questo thread va inserita tutta la logica relativa all'ascolto degli eventi a basso livello. Qui per esempio potremmo utilizzare un driver della porta seriale, oppure possiamo aprire una socket per rimanere in ascolto su una certa porta secondo un protocollo proprietario.

4) Appena accade una certa condizione, il thread comunica all'unmanaged thread che è accaduto quello che ci aspettavamo. E' il caso di avvertire la parte managed.

5) Si invoca il metodo che ci era stato passato nello step 2. Qui passiamo due parametri alla parte managed: un intero e una stringa. Dobbiamo fare qui sempre molta attenzione affinchè le cose tornino in maniera corretta visto che il marshalling dei parametri non è mai una cosa semplice e soprattutto immediata. Il metodo che invochiamo NON può aggiornare lui l'interfaccia grafica. Cioè non posso inserire i valori dei paramentri provenienti dalla parte unmanaged direttamente nelle textbox, per esempio, della GUI. Devo delegare la cosa ad un'altra classe (chiamata "ControlInvoke") di cui parleremo più avanti

6)A questo punto non rimane che invocare il metodi di una particolare classe che penserà lei a invocare il metodo della classe Form (passandogli i parametri provenienti dalla parte unmanaged) deputato all'aggiornamento delle textbox. Questo metodo appartenendo alla classe Form, quindi stesso thread della form, può aggiornare la GUI. E' una questione di "apartment".

Creazione del "Listener thread"

Vediamo come si presenta innanzitutto la windows form:

Al click del bottone creiamo il thread gestito:

 

Il nuovo thread eseguirà il metodo ListenCall() dentro il quale faremo una chiamata alla DLL tramite Platform Invoke. In questa chiamata come input passiamo il nome del metodo di callback (ReceivedCall) che vedremo più avanti. Vediamo come abbiamo definito la Classe NativeClass contenente i metodi corrispondenti alle funzioni esposte dalla DLL:

Buona norma è quella di racchiudere in un unica classe tutte le dichiarazioni Platform Invoke, come è stato fatto qui con la classe NativeClass. Come vediamo è stato dichiarato prima un delegate che sarà l'argomento passato alla parte unmanaged. Questo delegate contiene la firma di un metodo che sarà invocato successivamente al verificarsi dell'evento atteso. Da qui si deduce cha dalla parte unmanaged ci aspettiamo un intero e una stringa. La classe ListenState è un oggetto sul quale ci si sincronizza tra i thread managed (in questo caso ne abbiamo uno solo) per aggiornare eventuali variabili globali all'applicazione. Dalla parte unmanaged ci sarà quindi la funzione ReadyToReceiveCall esposta nella DLL che avrà questa firma:

Come risulta chiaro la funzione accetta un puntatore a funzione. Quest'ultimo sarà il metodo managed invocato successivamente.

Nella prossima sessione vedremo cosa succede nella DLL e come dobbiamo impostare il codice nativo C++

Tags: , , ,

Integrazione di codice Unmanaged con codice Managed in applicazioni Windows Mobile - Prima parte

by sandro 5. January 2009 22:43

Intro 

Molti di noi si saranno trovati spesso a decidere, al momento dell'analisi di un progetto, se sviluppare l'applicazione sul nostro dispositivo con codice unmanaged (nativo, solitamente sviluppato in C++) o tramite codice managed con .NET Compact Framework. Di solito la propensione per quest'ultima scelta (.NET) avviene per velocizzare lo sviluppo di interfacce utente (UI), l'integrazione in classi già preesistenti o l'utilizzo di controlli terze parti che aumentano vertiginosamente la produttività degli sviluppatori coinvolti nel progetto. Si propende invece per il codice unmanaged nel momento in cui vogliamo garantire migliori performance dell'applicazione o magari una certa portabilità su altre piattaforme. Una delle soluzioni che possono essere prese è quella di utilizzare il CF .NET per scrivere l'interfaccia utente, mentre si può lasciare la scrittura della logica di business (chiamiamola così) con codice unmanaged nativo. In questa serie di post, proverò ad analizzare una semplice soluzione che step by step guiderà sia nella scrittura del codice .NET che del codice nativo.

L'applicazione

L'applicazione consiste di una solution (CF_Native_Sample) con due progetti: il primo di tipo Device Mobile .NET chamato CF_Native_Sample, mentre il secondo contiene un progetto DLL C++ per Smart Device chiamato NativeLib. Tanto per far seguire passo passo la costruzione dell'applicazione, vediamo lo schema in figura:

Costruiremo una semplice interfaccia Windows Form con due textbox, un bottone ed una label. Al click del bottone il codice managed interagirà con quello nativo inserito nella DLL scritta in C++. Lo schema che vogliamo seguire, ricalca uno standard che ritroviamo in moltissime applicazioni. Innanzitutto la DLL unmanaged contiene una funzione che rimane in attesa di alcuni eventi che simula per esempio una socket, oppure un controllore PLC, o una seriale da cui arrivano dei segnali che alla fine vengono trasformati in una chiamata (call) che dovrà essere gestita dal codice .NET. Il codice gestito consiste in una form che al click del bottone scatena un meccanismo per cui in maniera asincrona ci si mette in ascolto delle chiamate provenienti dalla parte unmanaged. Apparentemente le cose sono abbastanza semplici e se vogliamo avere anche uno schema visuale dell'applicazione, possiamo osservare la seguente figura:

In questa immagine risulta chiaro che, se vogliamo ascoltare gli eventi in maniera asincrona, dobbiamo creare nella parte managed un thread apposito che ascolterà la parte unmanaged. Primo problema: sappiamo benissimo che la GUI appartenendo al thread della Main form, non potrà essere aggiornata dal listener thread, per cui dovranno essere implementati dei meccanismi di comunicazione tra i due thread. Secondo problema: abbiamo voluto pensare al fatto che dalla parte managed ci potevano essere più di un thread in ascolto e quindi più di un thread che aggiorna alcune variabili globali. Quali meccanismi possono essere utilizzati per sincronizzare questi thread?. Altro problema: il listener thread managed dovrà passare un delegate contenente il riferimento ad una callback. Quest'ultima sarà invocata dalla parte unmanaged per poter passare dei valori alla funzione managed: come è possibile effettuare un marshalling...indolore? Per esempio come faccio a passare delle stringhe dalla parte unmanaged alla parte managed?

Dal prossimo post cominceremo a costruire tutti i dettagli implementativi, descrivendo passo passo le scelte architetturali intraprese, partendo dalla form unmanaged che sarà la prima parte ad essere costruita. A prestissimo quindi

Tags: , , ,

Gestione delle batterie nei dispositivi embedded

by sandro 5. January 2009 21:15

Da un mio recente Webcast (che potete andarvi a vedere qui), ho voluto estrapolare le informazioni essenziali per metterle a disposizione di tutta la community di questo blog. Anzitutto chiariamo una cosa fondamentale: la gestione della batteria in un dispositivo Pocket PC e in uno Smartphone è completamente diversa. Infatti il primo può (anzi ...deve) dormire, mentre il secondo no. In pratica il modello SmartPhone è molto semplice...o è acceso o è spento...più chiaro di così :-).

Quando lo Smartphone è On tutte le funzionalità sono attive, mentre quando è Off tutte le applicazioni sono inattive, non si possono ricevere telefonate, non si ricevono i remainders degli appuntamenti e la batteria è preservata da qualunque tipo di consumo. Infatti possiamo toglierla senza effetti collaterali (considerando l’eventuale presenza della batteria di ricarica dell’orologio per non resettare il time alla successiva riaccensione).

Il modello PPC è molto più  complesso, in quanto può essere On, Off oppure...“Asleep”. Ai fini della conservazione della batteria va sottolineato che nel caso in cui il dispositivo stia dormendo alcune funzionalità continuano ad essere attive (il pulsante on/off per es.). Una chiamata può risvegliare il dispositivo, così come un’applicazione schedulata per funzionare in un certo momento. PPC cercherà sempre di “dormire” e nelle impostazioni viene indicato “quanto” il dispositivo dovrà rimanere sveglio in mancanza di attività (tap, softkeys…). Questo tempo solitamente è di 3 minuti, scaduti i quali il sistema cade in uno stato di "dormi-veglia", a meno che non invochiamo esplicitamente una funzione void WINAPI SystemIdleTimerReset( void). E' così per esempio che riusciamo a far eseguire Windows Media Player per più di 3 minuti, o riusciamo a effettuare un lungo download da IE senza toccare il dispositivo.

Ma quanti stati esistono in un PPC oltre a ON e Off? Qui dovete avere la pazienza di andare a vedere nella chiave di registro [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power\State] con l'aiuto dei remote tools di VS2005. Per esempo nell'emulatore SDK 6 Professional trovate una situazione del genere:

 

Vediamoli nel dettaglio i più importanti

On
L’utente può interagire con il sistema e tutte le funzionalità sono attive

BacklightOff
Introdotto in WM5, sta ad indicare che c’è stato un periodo di inattività e la backlight è stata spenta, ma le funzionalità sono ancora attive

UserIdle
C’è stato un lungo periodo di inattività per cui sia la backlight che lo schermo LCD sono stati spenti. Non viene generalmente utilizzato in dispositivi PPC (se vado in stato di sleep già lo schermo viene spento), ma prevalentemente negli SmartPhone.

ScreenOff
Ci sono delle applicazioni (Media Player) dove ad un certo punto lo schermo non è necessario. Si fornisce un bottone per spengere lo schermo, ma tutte le funzionalità sono su. E’ differente da UserIdle: lo schermo viene spento dall’utente esplicitamente e non dopo un periodo di inattività. In questo stato la pressione di un bottone o il tap dello schermo, non riattivano lo schermo stesso (solo il bottone On/Off). E' usato sia da PocketPC che da Smartphone.

Unattended
Lo schermo, la backlight e l’audio sono spenti. Usato solo nei PocketPC da applicazioni che non vogliono lanciare alert all’utente. L’utente pensa che il PPC sia “asleep”. ActiveSync usa per esempio questo stato quando ogni 5 minuti sincronizza il dispositivo senza chiedere nulla all'utente.

Resuming

PocketPC si sta risvegliando dallo stato di “asleep”. Lo schermo è spento. E’ utilizzato per dare al sistema la possibilità di passare in stato di Unattended senza…disturbare l’utente.

Suspended
E' lo stato di “R.E.M” del PocketPC. E’ tutto disattivato e il sistema non si risveglia a meno che qualche periferica non faccia un pò di …confusione

----------------------------------------------------

Power Manager 

A beneficio di chi sviluppa applicazioni sui dispositivi, dalla versione Mobile 2003 viene fornita una libreria Pm.dll Power Manager che permette di interagire con gli stati di sistema e delle singole periferiche. L'architettura è la seguente:

 

Come si intuisce dalla figura, PM implementa diverse funzionalità che vanno dal comunicare con i drivers per la gestione dell’alimentazione dei devices, al comunicare con le applicazioni che vogliono modificare lo stato del sistema sino a notificate alle applicazioni i cambiamenti di stato del sistema. PM permette di gestire i devices in maniera indipendente dal modello di gestione di Windows Embedded CE dove questi ultimi ricevevano notifiche sullo stato del sistema tramite interrupt. I devices erano quindi “costretti” in uno spazio limitato per quanto riguarda quando e cosa fare.
Il Power Manager permette invece ai devices di ricevere notifiche sul cambiamento di stato tramite I/O control codes (IOCTLs). Questo permette una maggiore flessibilità (vengono gestiti dentro il contesto del thread) e disaccoppia la gestione del power state tra ogni device e il sistema nel suo intero. Alcuni device possono quindi essere spenti mentre il sistema è On…e viceversa.
Il Power Manager è implementato come una dynamic-link library DLL chiamata Pm.dll e linkata direttamente al processo Device.exe. Quest'ultimo invoca le entry points in Pm.dll quando vengono invocate le primitive (API) nelle applicazioni. Così facendo si permetterà ai devices di gestire intelligentemente il loro stato, dandogli tra l'altro la possibilità di implementare uno o più "device power states".

Device Power State

Registry Key

Full On

D0

Low On

D1

StandBy

D2

Sleep

D3

Off

D4

Questi stati (D0-D4) rappresentano differenti situazioni in cui il device ricade per gestire la batteria in maniera differenziata. Vi rimando alla documentazione o al webcast per vedere come un'applicazione si sottoscrive al Pm, come quest'ultimo notifica in broadcast a tutte le applicazioni interessate a qualche cambiamento nello stato di gestione della batteria da parte del sistema e di come un'applicazione invii le proprie richieste a PM per far sì che il sistema cambi il proprio stato. In pratica dovranno essere viste le funzioni
HANDLE RequestPowerNotifications(
  HANDLE hMsgQ,
  DWORD Flags
);
BOOL StopPowerNotifications(
  HANDLE h
);
HANDLE SetPowerRequirement(
  PVOID pvDevice,
  CEDEVICE_POWER_STATE DeviceState,
  ULONG DeviceFlags,
  PVOID pvSystemState,
  ULONG StateFlags
);
DWORD GetSystemPowerState(
  LPWSTR pBuffer,
  DWORD Length,
  PDWORD pFlags
);
DWORD GetDevicePower(
  PVOID pvDevice, 
  DWORD dwDeviceFlags,
  PCEDEVICE_POWER_STATE pDeviceState
);

Nella documentazione SDK 6 trovate tra gli altri un esempio di utilizzo delle API di PM.dll (che potete anche scaricarvi da qui). E' un esempio in cui l' applicazione comunica a PM.dll che è interessata ai cambiamenti di stato del sistema (subscription). L'applicazione poi tramite un bottone comunica a Pm.dll di modificare lo stato della BackLight. Quest'ultima azione farà saltare in uno stato diverso il sistema che verrà quindi notificato (notification) all'applicazione stessa. C'è insomma l'invocazione di quasi tutte le primitive più importanti di Pm.dll. 
 

 

 

Tags: , , ,

Garbage Collection nel Compact Framework

by sandro 5. January 2009 10:18

Scusate il ritardo....direbbe qualcuno. Un altro comunque direbbe...meglio tardi che mai. Comunque sia, vediamo finalmente di affrontare l'argomento del garbage collection per il Compact Framework (CF), che tanto interesse ha suscitato durante la Mobility Conference di Milano, tanto da indurmi a pensare che intorno a questo "topic" c'è ancora un pò di...mistero. Allora il concetto chiave è che sia il CF che il Full framework .NET utilzzano entrambi la tecnica del Garbage Collector(GC). Esistono però delle sostanziali differenze nel modo in cui entrambe operano. Non sto qui ad analizzare nel dettaglio l'operatività del GC per la versione Full, ma mi basti ricordare che opera sulla base del concetto delle "generazioni".

GC nel Full .NET Framework 

In pratica il nostro heap gestito viene suddiviso in 3 parti (e solo 3) chiamate generazioni. La prima generazione (la G0) è quella dove vengono allocati tutti i nuovi oggetti. Così facendo sarà la prima a saturarsi, ma in questo modo si permette al GC di concentrarsi su una relativamente piccola porzione di memoria dove troverà sicuramente la maggior parte degli oggetti non più referenziati ( questo sulla base del principio che più un oggetto è stato creato da poco, più è probabile che sarà abbandonato presto).  Non appena si cerca di allocare un nuovo oggetto nella G0 ormai piena, viene sollevata un eccezione, il GC va in esecuzione, trova tutti gli oggetti non più referenziati in G0, sposta gli oggetti ancora in vita nella parte dello heap definita Generazione 1 (aggiustando quindi i puntatori), svuota la G0 degli oggetti non più referenziati e torna a dormire sino alla prossima eccezione. E' ovvio che arriverà un momento in cui anche la G1 si saturerà insieme alla G0, per cui in quel momento la collezione degli oggetti avverrà sulle due porzioni di Heap (G0 e G1) portando quelli ancora in vita in G2, svuotando la G0 e portando gli eventuali oggetti ancora vivi in G1. Se voi come me, utilizzate pesantemente i tool di Sysinternals, con Process Explorer potrete vedere i dettagli delle generazioni. Infatti per ciascun processo che carichi al suo interno il Common Language Runtime (CLR), evidenziato in giallo nella lista dei processi, potrete vedere qualcosa di molto simile a quello mostrato in figura:

 

Diciamo subito che la situazione sopra riprodotta è dovuta ad un'applcazione windows form con una textbox ed un bottone, chegira su un laptop con 1 G di Ram ed in condizioni di affollamento medio alto della RAM, Qusto per dire che il Runtime di .NET decide ad ogni "collection" di modificare dinamicamente le dimensioni delle 3 generazioni per motivi di performance. 

GC nel Compact Framework 

Detto questo, vediamo invece qual'è la filosofia che sta alla base del GC nella versione CF. Innanzitutto non ci sono le generazioni. Comprensibile visto che lo heap riservato agli oggetti del framework è molto più limitato che nelle macchine desktop. Quindi, prima domanda, quando entra in azione il GC? Il GC scatta al presentarsi di diverse occasioni:

  1. Si raggiunge un limite di 750KB di oggetti allocati
  2. L'applicazione perde il suo focus e viene posta in background
  3. Non c'è più spazio nello heap
  4. invocazione diretta tramite GC.Collect();

Quando scatta l'allarme a causa di una delle condizioni sopra esposte il GC esegue una raccolta, o come spesso viene riportato in letteratura, una collezione. Esistono 3 tipi di collezioni, cioè 3 modi di operare per raccogliere gli oggetti non più referenziati:

Simple Collection . E' l’algoritmo più semplice che si basa sul concetto di Marca e Getta (Mark & Sweep). La memoria che non contiene riferimenti attivi viene marcata come non attiva e deallocata. Ovviamente questo metodo è molto veloce ma ha un inconveniente molto evidente: alla lunga causerà una frammentazione della memoria stessa.

Compact Collection. Ad un certo punto la memoria avrà bisogno di essere ricompattata (non appena arrivata ad una certa soglia di framentazione). E’ il momento allora della Compact Collection. Verrà mandata in esecuzione una simple collection e di seguito i frammenti relativi agli oggetti ancora attivi, vengono spostati un un’area di memoria contigua. Ricorda molto lo spostamento degli oggetti nelle generazioni superiori nel Full .NET Framework. In questo modo saranno disponibili quindi chunk di heap di dimensioni più grandi per successive allocazioni.

Full collection. Si esegue quando c’è bisogno di una grande quantità di memoria. Viene eseguita una Compact Collection (vedi sopra) e di seguito tutti i metodi JITTed (cioè tutto quel codice che da MSIL è stato trasformato in codice nativo) vengono rilasciati o, come ritroviamo nella documentazione tecnica, “Pitched”. E’ il caso in cui un’applicazione PPC viene mandata in background e al ripristino il codice, all’occorrenza, viene nuovamente ricompilato dal JIT.

L'algoritmo è estremamente efficiente, ma non pensiamo di utilizzarlo a nostro piacimento invocando la GC.Collect() per migliorare l'occupazione della memoria, Questo comportamento infatti è altamente sconsigliato e foriero di notevoli degradi di performance. Il motivo si trova nel meccanismo del GC stesso. Infatti, prima che questo vada in esecuzione, il CLR cerca di portare tutti i thread in uno stato sicuro, o come si legge spesso, portare i threads in un "safe point". Uno stato in cui ciascun thread non potrà modificare lo Heap gestito. Per far questo si portano i thread a non eseguire nessuna riga di codice gestito o anche codice nativo del CLR sino a che il GC non abbia terminato il suo lavoro. Per questo motivo far partire una collect significa tutte le volte porre i vari threads in una sorta di stato di "sospensione" che impedirebbe all'applicazione una costante e regolare esecuzione del codice con conseguente sicuro degrado delle performance.

Vi rimando inoltre ad un mio post in cui vengono illustrate le tecniche per indagare le performance di un'applicazione basata sul CF e che riportano i tipi di collezioni intraprese dal GC

 

 

Tags:

Performance Counter

by sandro 5. January 2009 10:09

Qest'anno (2008) a giugno, durante l'ultima Windows Mobility Conference di Milano, ho presentato con Alessandro Pilotti una sessione riguardante il Compact Famework 2.0, in cui analizzavamo alcuni dettagli poco conosciuti sulla parte managed dei dispositivi embeddd. Con Alessandro abbiamo deciso di pubblicare degli approfondimenti aventi per tema gli argomenti dell'intervento. In questo post analizzerò una funzionalità che permette di monitorare le performance di un'applicazione .net. Nell'esempio di questo post, prenderemo in esame un'applicazione tra quelle presenti nella directory samples nell' SDK di Windows Mobile 6.0 e più precisamente C:\Programmi\Windows Mobile 6 SDK\Samples\Common\CS\RingtoneManager. Questa applicazione contiene del codice .net abbastanza significativo e inoltre si interfaccia con delle Dll native tramite Platform/Invoke, dovendo gestire la riproduzione di suoni da file .wav o .mp3.

Preparazione

Questa funzionalità è talmente potente e precisa per quanto semplice da implementare. E' sufficiente fare il deploy dell'applcazione .net, impostare una chiave di registro, avviare l'applicazione e dopo essere usciti dalla stessa, consultare il file .stat contenente i valori di alcuni parametri estremamente significativi. Andiamo per ordine. Facciamo il build della solution e succesivamente il deploy. Connettetevi con il remote tool per l'impostazione del registry e create la chiave di registro [HKEY_LOCAL_MACHINE\Software\Microsoft\.NETCompactFramework\PerfMonitor]. Qui va creata una voce di tipo DWORD con valore 0x01 di nome Counters. Alla fine il risultato dovrà essere questo:

 

Poi facciamo partire l'applicazione. Questa dovrà prima trovare un pò di file di tipo .wav o .mp3, dopodichè ne selezioniamo qualcuno e lo eseguiamo:

 

A questo punto usciamo dall'applicazione e vediamo che è stato creato un file con nome RingToneManager.stat. Facciamo l'upload del file sul computer desktop (tramite sempre remote tools) e apriamolo con notepad (qui ne vediamo per motivi di spazio solo una parte):

 

I Valori statistici 

Notiamo davvero un numero incredibile di voci. Proviamo ad analizzarne qualcuna.Managed Bytes Allocated, Managed Objects Allocated, Peak Bytes Allocated (native + managed)

Un alto numero di oggetti allocati non indica necessariamente un cattivo andamento dell'applicazione, comunque sia sppiamo bene che maggiore è il numero di oggetti allocati e maggiore sarà il tempo che la CPU dedicherà per l'eliminazione degli oggetti non più referenziati tramite l'attività del Garbage Collector.

Collections (Simple, Compact, Full)

Facendo riferimento ad un mio post dove spiego il funzionamento del Garbage Collector nei dispositivi embedded, con tali parametri ci possiamo rendere conto dell'attività del GC, quanto abbiamo ottimizzato la creazione di oggetti nello Heap Gestito e dove si annidano le potenziali aree di miglioramento. Per esempio in una situazione ideale no dovrebbero esserci delle full collection ed in ogni caso, qualora se ne presentassero alcune, il loro numero dovrebbe essere sempre inferiore aò numero degli altri due tipi di collection. Il rapporto tra Full Collections e Simple+Compact tende ad essere > 1 quando la memoria è sottoposta ad eccessivo stress.

GC Latency Time

E’ il totale in millisecondi del tempo di sospensione di un’applicazione dovuto alla garbage collection.
E’ direttamente collegata al Numero di Ogetti (altro parametro) e da il polso di quanto tempo impiegano le collezioni per fare il loro lavoro. E' da tenere sott’occhio quando le applicazioni sembrano inchiodarsi senza apparente motivo e riprendere successivamente.

Bytes Pitched, Number of Methods Pitched

Nel .NET Compact Framework c’è il concetto del “code pitching” e rappresenta l'azione di rimuovere il codice compilato (da MSIL a nativo) quando c’è bisogno di memoria altrimenti non disponibile. Questo causerà una successiva ricompilazione nel momento in cui ci sarà bisogno nuovamente del codice. Un parametro troppo elevato di questo tipo indica un rallentamento dell'applicazione in cui va ottimizzata la creazione di oggetti. Un’alta incidenza di pitching indica un grande utilizzo della memoria e forse che l’applicazione mantiene inutilmente vivi troppi oggetti. Possibili soluzioni possono consistere nel decrementare se possibile il numero di live objects, modificare eventualmente il device aggiungendo memorie oppure controllare se ci sono altre applicazioni che consumano troppa memoria.

Native Bytes Jitted, Number of Methods Jitted

E' il totale di codice complato in nativo. Se il numero è molto elevato, per certe aree della nostra applicazione, controllare se eventualmente non esiste un’altra successione di chiamate che fa lo stesso lavoro, con un numero minore di call

Mi riprometto di analizzare i meccanismi per migliorare le performance di applicazioni basate sul compact framework in successivi post

 

 

Installare certificati in Windows Mobile 6.0

by sandro 5. January 2009 08:53

Qualche giorno fa qualcuno mi ha chieso una procedura semplice per poter installare dei certificati su dispositivi Windows Mobile 6.0. Vorrei scrivere un pò di più su questo delicato argomento e di cui in giro non troviamo mai una documentazione soddisfacente....mi riprometto di fare qualcosa di più organico e completo in un prossimo post. Per adesso mi limiterò ad illustrare una procedura semplice ed efficace, riproducibile in qualunque ambiente.

Dove sono i miei certificati?

I certificati in Windows Mobile sono memorizzati in chiavi di registro ben precise in formato Blob (Binary Large object).Vi sono alcune differenze, che accenniamo solamente, tra la versione 5 e la 6 per quanto riguarda la presenza di stores e il loro accesso. In Windows Mobile 5.0 le store Root e Certificate Authentication (CA) erano completamente inaccessibili a tutti gli utenti che non avessero i permessi assegnati al ruolo di Manager. In Windows Mobile 6 sono state aumentate le store e quindi troviamo le store User Root e User CA per permettere di aggiungere dei certificati anche ad utenti con minori permessi, oltre alle store system Root e system CA che imangono comunque inaccessibili senza permessi da Manager o Enterprise. Ricordiamo come nei dispositivi venga utiizzato un meccanismo di selezione degli accessi alle risorse (chiavi di registro, applicazioni, certificate stores...) basato sul ruolo che ciascun utente al momento dell'accesso al dispositivo ricopre.

Ho trovato le stores dei certificati (ma dove sono!!?? :-)) e li ho riprodotti in figura per un Mobile Device Emulator 5.0:(ricordo che questo screenshot è stato prodotto utilizzando i remote tools di Visual Studio 2005 ed in particolare il Device Emulator Manager)

 

Come si vede sono presenti tutte le stores relative alla macchina, mentre se andate sulla chiave di registro [HKEY_CURRENT_USER\Comm\Security\SystemCertificates] troverete solamente la chiave "my" in cui memorizzare i certificati dell'utente.

Si può vedere nell'immagine successiva invece come in Windows Mobile 6 le cose siano state modificate abbastanza per quanto riguarda soprattutto le stores relative a HKCU. In particolare la chiave "root" può contetnere certificati root o self-signed installati da qualcuno con il ruolo Authenticated ( e non Enterprise o Manager). Inoltre la chiave "ca" (sempre all'interno di HKCU) contiene certificati emessi da CA intermedie installabili ancora da utenti nel ruolo Authenticated User.

Let's Install!!

Procuriamoci innanzi tutto un certificato. A proposito...che tipo di certificato supporta Windows Mobile 6? Abbiamo la possibilità di scegliere tra questi formati:

PFX/.P12 – Public-Key Cryptography Standards #12 (PKCS #12). Questo formato include certificati personali a cui si possono allegate anche le rispettive chiavi private

CER – Base64-encoded oppure DER-encoded X.509. Certificati che si installano nelle stores intermediate e root.

P7B - Public-Key Cryptography Standards #7 (PKCS #7) Si installano con questo formato, più certificati in qualsiasi store del device.

Io ho scelto un certificato personale presente sulla mia macchina desktop che tramite lo snap-in "certificates" della MMC ho esportato (insieme alla chiave privata) in un file di tipo .pfx. Solitamente poi, siccome i file .pfx/.p12 vengono protetti con una ulteriore password che viene fornita al momento dell'esportazione, ho inserito una password che al momento della istallazione sul device dovrò digitare nuovamente.

 

Adesso si pone il problema di come fare il deploy del file .pfx. Nel caso in esempio ho ovviamente utilizzato il remote tool "Windows CE file View", ma nella realtà è possibile utilizzare diversi meccanismi quali per esempio ActiveSync, card rimuovibili, attachment di e-mail oppure tramite download da Mobile Internet Explorer di file .cpf (dei wrapper di cui parleremo in un successivo post). Quando il file sarà presente sul dispositivo si presenterà così:

 

A questo punto non rimane che fare un tap sul file per far partire automaticamente la procedura di installazione del certificato. Verrà ovviamente richiesta la password:

 

Ed infine il certificato sarà installato con successo:

 

Dove lo ritroviamo? il percorso da seguire è Start>Settings>System>Certificates ed infine arriviamo alle nostre stores che vengono visualizzate sul dispositivo:

Costruire un servizio in Windows Mobile

by sandro 5. January 2009 07:32

Come promesso in un mio recente webcast, su questo blog troverete il codice sorgente per poter fornire il vostro dispositivo di un servizio, o se volete, di un'attività che parta all'avvio del dispositivo stesso. Per chi volesse comunque avere solo un riassunto delle "puntate precedenti"...allora accomodatevi :-).

Allora, si comincia parlando dei servizi in genere. Come sapranno in molti, i servizi nei dispositivi desktop sono quei programmi che vanno in esecuzione all'avvio della macchina, senza che nessuno abbia ancora effettuato il logon. Sono dei processi che vengono lanciati con specifici contesti di sicurezza e, dopo aver caricato un certo numero di DLL, eseguono dei compiti ben precisi. Alcuni di questi processi si chiamano svchost (che qualcuno guardando in task manager si sarà chiesto..ma questi svchost, a cosa servono?) ed analizzando la nostra macchina con process explorer possiamo anche scoprire a quali compiti ciascuno di essi assolve.

 

 

Tutti i processi evidenziati in rosa sono servizi. Il processo Svchost selezionato, ha caricato al suo interno rpcss.dll (dll che troviamo in Windows\System32) e di conseguenza ne deduciamo che implementa le funzionalità per la gestione delle remote procedure calls. Analizzando il resto dei processi troveremo che altri svchost ospitano anche più di una dll per implementare i servizi di "ora di Windows", Crittografia, Telefonia, Windows firewall e altri ancora.

E nel mondo Mobile che cosa accade? Fortunatamente ci viene in contro un processo che ritroviamo dalla versione PPC 2002 in poi (anche se per i dispositivi un pò più datati è possibile aggirare questo problema) che si chiama services.exe, il quale è preposto alla esecuzione di alcune DLL poste in locazioni ben precise, non apena il dispositivo effettua uno start (o restart). Il funzionamento di Services.exe è molto simile al processo Devices.exe, anche nella gestione come vedremo. Una differenza sostanziale è che Services.exe viene utilizzato se non abbiamo bisogno di accedere ad un hardware ben preciso ovvero se non dobbiamo implementare delle tecniche di IPC (inter Process Comunication). Ecco graficamente come appare il processo Services.exe

 

Attivazione, Gestione e Deattivazione

Per scrivere un servizio, è necessario scrivere una dll che sarà poi caricata nel processo che vediamo sopra in figura. Questa DLL deve necessariamente esporre delle funzioni con nomi ben precisi. due di queste funzioni le vediamo subito analizzandone il prototipo:

DWORD xxx_Init( DWORD dwData);     DWORD xxx_Deinit(DWORD dwData);

Naturalmente xxx deve essere sostituito dal nome del nostro servizio, oppure da un prefisso che noi abbiamo scelto. Questa funzione viene utilizzata da Service.exe dopo che un'altra primitiva RegisterService avrà caricato la nostra DLL e avrà verificato che quest'ultima esporti le corrette funzioni, proprio come quando una classe implementa un'interfaccia. Se tutto è ok, Services.exe si vedrà ritornare un valore da xxx_Init che sarà poi utilizzato in altre eventuali successive chiamate. RegisterService ottiene un Handle del servizio. Possiamo immaginarci come la funzione Deinit venga chiamata da Serices.exe dopo che la primitiva DeregisterService ha controllato e deattivato il nostro servizio. Per poter controllare in corsa il servizio invece, è necessario procurarci prima di tutto un handle della nostra dll tramite la primitiva

HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDispostion, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);

Esatto, proprio CreateFile. Dopo che la funzione avrà avuto esito positivo si potranno inviare alla DLL dei messaggi di tipo IOCTL (Input Output Controls) tramite la primitiva DeviceIOControl la quale provoca l'invocazone della primitiva esposta nella DLL di nome xxx_IOControl. Questa ultima si aspetta di ricevere dei messaggi IOCTL tramite i quali intraprendere le corrette azioni. Solo per dare un'idea vediamo alcuni dei messaggi IOCTL più comuni che possono essere gestiti dalla DLL del servizio:

IOCTL

Descrizione

IOCTL_SERVICE_STOP

ferma il servizo

IOCTL_SERVICE_START

Fa partire il servizio

IOCTL_SERVICE_STATUS

Ritorna lo stato del servizio

IOCTL_SERVICE_CALLBACK_STATUS

Funzione di callback da invocare ed eseguire in Services.exe

I messaggi IOCTL possono essere inviati sia dalle applicazioni che da Services.exe. Una cosa molto interessante è che si possono mandare in esecuzione all'interno di Services.exe, più istanze dello stesso servizio, senza cioè far girare più istanze dello stesso processo. Questo va tenuto in grande considerazione, visto che ancora abbiamo il problema del limite dei 32 processi (superato con la versione 6.0 di windows CE e non di Windows Mobile 6 che equipaggia ancora il Windows Mobile 5.x!!!!!). Oltre alla possibilità di avere più istanze è possibile comunque far girare il nostro servizio in un processo Services.exe assolutamente dedicato come indicato in figura:

 

Per poter far partire il servizio in un processo separato, è necessario impostare la chiave di registro HKEY_LOCAL_MACHINE\Services\<Service Name>\Context di tipo DWORD e assegnarle il valore 0x02 (SERVICE_INIT_STANDALONE), dove naturalmente <Service Name> è un placeholder per il nome che noi abbiamo assegnato al servizio. Questo valore verrà poi passato ala funzione xxx_Init nel parametro dwData, la quale ritornerà FALSE se la DLL non supporta l'avvio stand-alone

Super Service Sockets

Una caratteristica fondamentale che non dobbiamo trascurare se il servizio che stiamo costruendo deve utilizzare funzionalità di rete, è proprio Super Service Sokets. Chi è "avvezzo" all'utilizzo di sistemi Open Source non sarà per niente sorpreso da questa funzionalità che si avvicina molto per esempio al servizio Inetd di FreeBSD. In pratica Super Service rimane in ascolto solo lui delle richieste di connessione per conto dei vari servizi attivati smistando le richieste dei clients alle varie DLL. Questo migliora l'overhead di sistema ed una migliore gestione delle socket (esiste un solo thread in accept() per tutti i servizi e non un thread in accept() per ogni servizio). Il compito di Super Service sarà quindi quello di mettersi in ascolto su una certa porta in nome e per conto di una certa DLL e all'arrivo di una richiesta di connessione passargli la socket che verrà a quel punto gestita direttamente dallo specifico servizio. Il sistema di bindng può essere impostato all'avvio di Services.exe oppure gestito dinamicamente. Per l'avvio di un Super Services è necessario impostare il flag SERVICE_INIT_STOPPED nella chiave di registro Context (vedi prima). Conseguentemente Services.exe chiamerà xxx_IOControl e passerà il valore IOCTL_SERVICE_REGISTER_SOCKADDR. Se tutto ok allora xxx_IOControl ritornerà TRUE e Super Service si troverà le porte su cui attendere i client nelle seguenti chiavi di registro

[HKEY_LOCAL_MACHINE\Services\TELNETD]
"Context"=0x00000001                     //SERVICE_INIT_STOPPED, use   super server
[HKEY_LOCAL_MACHINE\Services\TELNETD\Accept\TCP-23] ; TCP port 23, default Telnet Port
"SockAddr"=hex:02,00,00,17,00,00,00,00,00,00,00,00,00,00,00,00
[HKEY_LOCAL_MACHINE\Services\TELNETD\Accept\TCP-24] ; Also listen on TCP port 24
"SockAddr"=hex:02,00,00,18,00,00,00,00,00,00,00,00,00,00,00,00

In questo caso (dopo aver impostato il flag context) si indicano due porte di ascolto TCP sulla porta 23 e 24 per il servizio Telnet. (che potrete trovare come esempio nei file del SDK).

Progetto Service

E veniamo adesso ad un progetto vero e proprio che vogliamo implementare sul nostro Device. La solution si basa su  tre progetti (in codice nativo C++), rispettivamente:

  1. La DLL che implementa il servizio vero e proprio
  2. Un file CAB per fare il deploy del servizio
  3. Una DLL di Setup (Setup.dll) che servirà proprio per avviare il servizio

In pratica la nostra DLL verrà inserita nel CAB insieme ad un file setup.dll contenete le invocazioni delle primitive per registrare e avviare il servizio, proprio come spiegavamo in precedenza. Il concetto è che non appena abbiamo fatto il deploy della DLL nella directory \Windows (poichè è li che Services.exe si aspetta di trovare tutte le DLL che implementano dei servizi), dobbiamo invocare la primitiva RegisterService per causare l'invocazione di xxx_Init(). Quale miglior luogo per inserire il RegisterService se non in una Setup.dll che verrà utilizzata dal .CAB quando lanceremo quest'ultimo. Infatti, quando il .CAB viaggia con una DLL chiamata Setup.dll, la prima cosa che fa, dopo aver messo a posto i file nelle directory giuste ed inizializzato le chiavi di registro, è quello di chiamare la funzione Install_Init. Solitamente in questa funzione viene inserita la classica domanda tipo "sei sicuro di voler installare il servizio?". Se  l'utente digita OK allora il .CAB chiama la funzione Install_Exit dentro la quale si invoca effettivamente RegisterService. Tanto per fare chiarezza vediamo in questa immagine una illustrazione sistematica delle fasi di cui abbiamo parlato:

Il progetto servizio viene realizzato da una dll MyService.dll dentro la quale vengono implementate le funzioni xxx_Init, xxx_Deinit e così via, dove il prefisso scelto è naturalmente CTS. Dobbiamo poi fornire un file .DEF in cui pubblichiamo le funzioni esportate. Il Working-thread (il thread che efettua il lavoro vero e proprio del servizio) non è altro che un thread che ogni 10 secondi lancia una messagebox.

La dll Setup.dll contiene le implementazioni delle funzioni Install_Init e Install_Exit dove rispettivamente vengono invocate le primitive RegisterService e DeregisterService. Ricordo che queste primitive vengono invocate direttamente (e per default) dal .CAB al momento dell'avvio di quest'ultimo.

Il progetto forse più interessante è quello del CAB per device. In questo progetto infatti dobbiamo indicare per prima cosa che si intende utilizzare un file setup.dll e questo lo diciamo nelle proprietà del progetto come indicato:

Dopodichè, dopo aver indicato che il file Myservice.dll dovrà essere inserito nella directory /Windows del target device e la dll Setup.dll nella directory Program Files, ecco come si da indicazione al file .CAB di come dovranno essere impostate le chiavi di registro necessarie all'installazione. Come vediamo i valori sono autoesplicativi:

A questo punto sarà necessario compilare e simulare un deploy del .CAB copiando manualmente (tramite i remote tools di VS2005) il .CAB in una directory qualsiasi del dispositivo target. Un'ultima notazione va fatta per il debug. Inserire dei break point non porta a nulla in quanto la nostra DLL viene eseguita nello spazio di indirizzamento del processo Services.exe. Questo ci obbliga all'inserimento di un'istruzione DebugBreak() nel nostro sorgente come in figura:

Questo significa che il procedimento per effettuare il debug sarà il seguente (dopo aver ricompilato il sorgente ovviamente):

  • Ricompilare la solution
  • Fare il deploy del .CAB
  • Fare un attach al processo Services.exe utilizzando il tool di VS2005
  • Lanciare il CAB
  • Attendere l'arrivo all'istruzione DebugBreak().

L'attach è illustrato in figura:

Con questo mi sembra di aver detto tutto, tranne dove potete scaricarvi i sorgenti e cioè da qui.

 

 

Il progetto servizio viene realizzato da una dll MyService.dll dentro la quale vengono implementate le funzioni xxx_Init, xxx_Deinit e così via, dove il prefisso scelto è naturalmente CTS. Dobbiamo poi fornire un file .DEF in cui pubblichiamo le funzioni esportate. Il Working-thread (il thread che efettua il lavoro vero e proprio del servizio) non è altro che un thread che ogni 10 secondi lancia una messagebox.

La dll Setup.dll contiene le implementazioni delle funzioni Install_Init e Install_Exit dove rispettivamente vengono invocate le primitive RegisterService e DeregisterService. Ricordo che queste primitive vengono invocate direttamente (e per default) dal .CAB al momento dell'avvio di quest'ultimo.

Il progetto forse più interessante è quello del CAB per device. In questo progetto infatti dobbiamo indicare per prima cosa che si intende utilizzare un file setup.dll e questo lo diciamo nelle proprietà del progetto come indicato:

 

Dopodichè, dopo aver indicato che il file Myservice.dll dovrà essere inserito nella directory /Windows del target device e la dll Setup.dll nella directory Program Files, ecco come si da indicazione al file .CAB di come dovranno essere impostate le chiavi di registro necessarie all'installazione. Come vediamo i valori sono autoesplicativi:

 

A questo punto sarà necessario compilare e simulare un deploy del .CAB copiando manualmente (tramite i remote tools di VS2005) il .CAB in una directory qualsiasi del dispositivo target. Un'ultima notazione va fatta per il debug. Inserire dei break point non porta a nulla in quanto la nostra DLL viene eseguita nello spazio di indirizzamento del processo Services.exe. Questo ci obbliga all'inserimento di un'istruzione DebugBreak() nel nostro sorgente come in figura:

 

Questo significa che il procedimento per effettuare il debug sarà il seguente (dopo aver ricompilato il sorgente ovviamente):

  • Ricompilare la solution
  • Fare il deploy del .CAB
  • Fare un attach al processo Services.exe utilizzando il tool di VS2005
  • Lanciare il CAB
  • Attendere l'arrivo all'istruzione DebugBreak().

L'attach è illustrato in figura:

Con questo mi sembra di aver detto tutto, tranne dove potete scaricarvi i sorgenti e cioè da qui.

 

Sandro Vecchiarelli

MeLaureato in Scienze dell'informazione a Pisa, si è occupato di analisi e progettazione software per istituti bancari e pubbliche amministrazioni, approfondendo inoltre gli aspetti della programmazione ad oggetti sin dai primi esordi del web in Italia. Certificato MCSE e MCT si occupa anche di elementi legati al networking e alla sicurezza. Svolge un' importante attività di docenza su argomenti riguardanti la piattaforma .NET, XML e Web services. E' autore di 2 corsi di successo: "Windows Server Security"(MIE1311), riguardante gli aspetti legati alla sicurezza della piattaforma Windows Server, e "Ingegneria del software: tecniche per la costruzione di software di qualità" (MIEAU36) dove vengono affrontate le tecniche per scrivere codice il più possibile robusto, riusabile e performante. Negli ultimi anni ha acquisito anche importanti competenze riguardanti prodotti molto complessi quali Microsoft Biztalk Server, oltre ad aver affrontato l'analisi e lo sviluppo di applicazioni in ambienti embedded quali Windows CE e Pocket PC. Nel 2008 è stato nominato MVP Microsoft Windows Mobile, riconfermato nel 2009, 2010 e 2011.

1 2 3 4 5 6 7 8

Tag cloud

Page List