Nel post di oggi scendiamo un po' più a basso
livello nella combinata software + hardware gestito autonomamente
dal sistema operativo. Vedremo infatti come ottenere l'elenco dei processi
attivi (classe Process del FX) in questo
momento e come leggerne le proprietà. Ogni processo inoltre è composto da
uno o più moduli (class ProcessModule del FX),
anch'essi dotati di proprietà specifiche.
Annuncio che per preparare questo post ho fatto anche un banale progetto con
VS2005 che è disponibile per il download da questo indirizzo. E' molto semplice:
contiene una sola Windows Forms (FormProcess) le cui caratteristiche essenziali
sono le seguenti:
- un oggetto TreeView e un oggetto ListView
- i Process sono mostrati sulla TreeView come nodi
a livello 0. I moduli invece come nodi a livello 1 (indentati rispetto al
nodo corrispondente al processo padre)
- la ListView serve invece a mostrare le proprietà del
Process o del ProcessModule selezionato
- c'è un membro privato Process[] currentProcess
- c'è un metodo privato GetCurrentProcesses()
- c'è un metodo privato writeProcessDetails(Process
pc)
- c'è un metodo privato writeProcessModuleDetails(ProcessModule
md)
Come passo passo come ho implementato i metodi di cui sopra. Innanzitutto, ho
gestito l'evento Load del form per lanciare il metodo GetCurrentProcesses che,
come dice il nome stesso, si occupa di rilevare con il metodo statico GetProcesses() della classe
Process, tutti i processi attivi in questo momento. Quindi:
private void FormProcess_Load(object sender, EventArgs e)
{
GetCurrentProcesses();
}
private void getCurrentProcesses()
{
// Ottengo i Process correnti
currentProcess = Process.GetProcesses();
// Non posso fare databinding diretto tra il TreeView e
// currentProcess, quindi ottengo un IEnumerator
// e popolo manualmente tutti i Nodes
IEnumerator cycle = currentProcess.GetEnumerator();
while(cycle.MoveNext())
{
Process tmp = (Process) cycle.Current;
TreeNode nd = new TreeNode(tmp.ProcessName);
trvProcesses.Nodes.Add(nd);
}
}
A questo punto, non ho potuto bindare la TreeView
direttamente al mio oggetto currentProcess, per cui l'ho fatto a manina
facendomi ritornare un IEnumerator. Per mostrare il processo sulla TreeView
utilizzo la proprietà ProcessName. A questo punto,
abbiamo popolato tutti i Nodes della TreeView a livello 0 con i processi. Ho
gestito l'evento MouseDoubleClick, facendo in modo di
aggiungere altri Nodes rispetto a quello selezionato, visualizzando i Modules associati a quel processo. Riporto qui sotto la
parte di codice più interessante, per il resto vi rimando al download del mio
progetto:
ProcessModuleCollection coll = currentProcess[node.Index].Modules;
IEnumerator cycle = coll.GetEnumerator();
node.Text += " [" + coll.Count + "]";
view.BeginUpdate();
while (cycle.MoveNext())
{
ProcessModule tmp = (ProcessModule)cycle.Current;
node.Nodes.Add(tmp.ModuleName);
}
view.EndUpdate();
In questo caso, uso la proprietà ModuleName di ogni singolo
modulo.
Dove ho usato Reflection? Beh, qui!
Nel titolo di questo
post ho accennato a Reflection. Se vi state chiedendo dove e perchè l'ho usata
(perchè mi viene da dire Reflection al femminile?), continuate a leggere. Restano infatti da vedere gli altri due
metodi privati del form, ovvero writeProcessDetails e
writeProcessModuleDetails. L'idea che volevo fare era quella di
mostrare le proprietà di ogni Process e ProcessModule sulla ListView. Cioè,
per spiegarmi meglio: lancio il progetto, vedo l'elenco dei processi. Quando
seleziono un nodo:
- se node.Level = 0, ho selezionato un Process e chiamo
writeProcessDetails
- se node.Level = 1, ho selezionato un ProcessModule e chiamo
writeProcessModuleDetails
La ListView quindi potrebbe contenere le proprietà di entrambi gli oggetti,
quindi ho scritto quanto segue.
private void writeProcessDetails(Process pc)
{
Type type = typeof(Process);
// Ottengo l'elenco delle properties della classe Process
PropertyInfo[] props = type.GetProperties();
IEnumerator cycle = props.GetEnumerator();
// Popolo la listview con i nomi delle property
// ed il loro valore
this.lvwDetails.BeginUpdate();
this.lvwDetails.Items.Clear();
while (cycle.MoveNext())
{
PropertyInfo info = (PropertyInfo) cycle.Current;
ListViewItem itm = new ListViewItem(info.Name);
try
{
itm.SubItems.Add(info.GetValue(pc, null).ToString());
}
catch { }
this.lvwDetails.Items.Add(itm);
}
this.lvwDetails.EndUpdate();
}
Usando Reflection, appunto, ottengo un array di
PropertyInfo[] ritornati dal metodo GetProperties() della classe
Type. Dopo, nulla di particolare: ottengo
un IEnumerator e popolo le due colonne della ListView: una per il Name ed
una per il Value di ogni proprietà. Notare l'utilizzo del metodo
GetValue() per ottenere il valore di ogni proprietà: ritorna un
object. Quando si tratta di proprietà semplici, tutto bene, lo
castiamo e siamo a posto. Se fosse una proprietà più
complessa, dobbiamo usare anche il secondo parametro: non so approfondire la questione
perchè non ne ho avuto bisogno. E' stato bello usare Reflection,
ma ci sono ovviamente alcuni svantaggi:
- performance? sbaglio, o usare Reflection non è il massimo della
vita?
Diciamo che per stavolta può anche andar bene...
- se volessi formattare alcune delle proprietà in modo diverso, come
faccio?
Ad esempio, gli address in esadecimale...o usando font
diversi...bla bla bla...
- se volessi mostrare alcune delle proprietà e non tutte, come faccio?
- a scopo didattico, non imparo i nomi delle proprietà
Altri utilizzi della classe Process
La classe Process
dispone di un altro metodo statico, Start, che ci permette di
avviare un nuovo processo secondo diverse modalità: il percorso del file da avviare (che può essere un eseguibile o un
documento da aprire - addio, ShellExecuteEx con VB6, è stato bello, ma prima o
poi doveva finire!), una classe ProcessStartInfo (davvero
carina ed utile), etc. etc. Inoltre, da sapere che i metodi statici della
classe Process possono lavorare anche su macchine remote - presumo avendo i
permessi per farlo.
Ho aggiunto questo pezzo direttamente nell'HTML di .TEXT da FireFox: speriamo che si legga.
Il codice è disponibile qui (ProcessViewer.zip, circa 40Kb).