MSDN Tips & Tricks

I consigli degli esperti italiani per sfruttare al meglio gli strumenti di sviluppo e semplificare l’attività quotidiana.

In questa pagina

Scrivere un dump della memoria delle proprie applicazione managed Scrivere un dump della memoria delle proprie applicazione managed
Trovare i buchi in una sequenza di numeri Trovare i buchi in una sequenza di numeri
Windows Vista CommandLinks Windows Vista CommandLinks
Caricare un’immagine in maniera asincrona con Windows Presentation Foundation Caricare un’immagine in maniera asincrona con Windows Presentation Foundation

Scrivere un dump della memoria delle proprie applicazione managed

Di Mauro Servienti - Microsoft MVP

Ci sono casi in cui siamo costretti ad effettuare un “debug post mortem” delle nostre applicazioni, per debug post mortem intendiamo quei casi in cui l’applicazione genera un’eccezione non gestita quando è già in produzione presso il cliente e l’infrastruttura o le policy aziendali ci impediscono di agganciare un debugger remoto.

Siamo quindi di fronte ad una casistica abbastanza rara ma decisamente importante perchè apre scenari abbastanza complessi.

La prima operazione da fare, per poter eseguire un debug post mortem, è quella di creare un dump della memoria che la nostra applicazione stava utilizzando al momento del blocco. Per fare questo ci dobbiamo necessariamente appoggiare alle API di sistema via P/Invoke perchè il .NET Framework non offre nessuna funzionalità per raggiungere lo scopo.

L’API che ci interessa è MiniDumpWriteDump esportata dalla libreria di sistema DbgHelp.dll, scrivere un dump a questo punto è un’operazione abbastanza semplice.

Riporto di seguito il codice che utilizzo nelle mie applicazioni in produzione.

/*
 * Il tipo di dump che vogliamo effettuare:
 * http://msdn2.microsoft.com/library/ms680519.aspx
 */
public enum MiniDumpType
{
None = 0x00010000,
Normal = 0x00000000,
WithDataSegs = 0x00000001,
WithFullMemory = 0x00000002,
WithHandleData = 0x00000004,
FilterMemory = 0x00000008,
ScanMemory = 0x00000010,
WithUnloadedModules = 0x00000020,
WithIndirectlyReferencedMemory = 0x00000040,
FilterModulePaths = 0x00000080,
WithProcessThreadData = 0x00000100,
WithPrivateReadWriteMemory = 0x00000200,
WithoutOptionalData = 0x00000400,
WithFullMemoryInfo = 0x00000800,
WithThreadInfo = 0x00001000,
WithCodeSegs = 0x00002000
}

L’enumerazione MiniDumpType determina il tipo di dump che vogliamo venga eseguito, a seconda del tipo scelto variano le informazioni che troveremo nel dump finale e naturalmente la sua dimensione che può spaziare da pochi kilobyte a svariati megabyte.

public static class MiniDump
{
/*
 * Importiamo dalla dll DbgHelp.dll la funzione che ci interessa
 */
[DllImport( "DbgHelp.dll" )]
private static extern Boolean MiniDumpWriteDump(
IntPtr hProcess, Int32 processId, IntPtr fileHandle,
MiniDumpType dumpType, ref MinidumpExceptionInfo excepInfo,
IntPtr userInfo, IntPtr extInfo );

/*
 * Una struttura che contiene le informazioni
 * per generare il dump
 */
struct MinidumpExceptionInfo
{
public Int32 ThreadId;
public IntPtr ExceptionPointers;
public Boolean ClientPointers;
}

/*
 * Il wrapper alla chiamata alla API di DbgHelp.dll
 */
public static Boolean TryDump( String dmpPath, MiniDumpType dmpType )
{
/*
 * In ingresso ci arrivano il percorso dove il 
 * dump deve essere salvato e il tipo di dump che 
 * dobbiamo eseguire
 * 
 * Creiamo un FileStream per scrivere su disco le informazioni
 */
using( FileStream stream = new FileStream( dmpPath, FileMode.Create ) )
{
/*
 * Recuperiamo un riferimento al processo corrente
 */
Process process = Process.GetCurrentProcess();

/*
 * Creiamo e valorizziamo la struttura che contiene
 * le informazioni relative al dump
 */
MinidumpExceptionInfo mei = new MinidumpExceptionInfo();
mei.ThreadId = Thread.CurrentThread.ManagedThreadId;
mei.ExceptionPointers = Marshal.GetExceptionPointers();
mei.ClientPointers = true;

/*
 * Chiamiamo l'API
 */
Boolean res = MiniDumpWriteDump(
process.Handle,
process.Id,
stream.SafeFileHandle.DangerousGetHandle(),
dmpType,
ref mei,
IntPtr.Zero,
IntPtr.Zero );

/*
 * Completiamo la scrittura su disco
 * e chiudiamo lo stream
 */
stream.Flush();
stream.Close();

return res;
}
}
}

La classe statica MiniDump espone un solo metodo TryDump() che permette di eseguire il dump della memoria e prende in input il percorso (comprensivo di nome del file) in cui vogliamo salvare il nostro dump e il tipo di dump che desideriamo, internamente non fa altro che chiamare l’API MiniDumpWriteDump passando le informazioni necessarie perchè il dump venga eseguito correttamente.

Una volta che abbiamo in mano il dump possiamo procedere alla sua analisi come meglio crediamo utilizzando gli strumenti che troviamo in Visual Studio, come S.O.S., o strumenti come WinDbg.

Risorse:

MiniDumpWriteDump:
http://msdn2.microsoft.com/library/ms680360.aspx

Trovare i buchi in una sequenza di numeri

Di Davide Mauri - Microsoft MVP

Un problema che più di una volta mi è stato chiesto di risolvere è quello relativo alla ricerca di “buchi” all’interno di una sequenza crescente di numeri.

I casi in cui questa richiesta è stata applicabile sono davvero numerosi e vanno da “problemi” all’interno delle tabelle che formano il libro giornale nei gestionali alla ricerca dei posti adiacenti disponibili in un cinema o in un teatro.

Per simulare una situazione tipica possiamo utilizzare questo codice:

use tempdb
go

create table dbo.Esempio
(
numero int not null primary key
)
go

insert into dbo.Esempio values(101)
insert into dbo.Esempio values(102)
insert into dbo.Esempio values(103)
insert into dbo.Esempio values(105)
insert into dbo.Esempio values(107)
insert into dbo.Esempio values(108)
insert into dbo.Esempio values(110)
insert into dbo.Esempio values(120)
insert into dbo.Esempio values(121)
insert into dbo.Esempio values(130)
go

set nocount on

declare @c1 int
declare @c2 int

set @c1 = 1

while @c1 < 1000
begin
set @c2 = 1

while @c2 < 10
begin
insert into dbo.Esempio select @c2 * @c1 * 1000 + numero from dbo.Esempio where numero < @c1 * 1000

set @c2 = @c2 + 1 
end
set @c1 = @c1 * 10
end

Dopo aver eseguito lo script, sarà disponibile nel tempdb la tabella dbo.Esempio contenente 10000 righe in cui valori nella colonna Numero sono crescenti e hanno dei “buchi” nella sequenza.

Con SQL Server 2005 è possibile sfruttare le funzionalità di ranking e le CTE per risolvere brillantemente il problema:

with cte as
(
select 
numero,
row_number() over(order by numero) as rn 
from 
Esempio
)
select 
cur.numero as buco_da, 
nxt.numero as buco_a
from 
cte as cur
inner join 
cte as nxt on cur.rn + 1 = nxt.rn
where 
nxt.numero - cur.numero > 1;

Con SQL Server 2000 si deve sudare un po’ di più ma è possibile comunque risolvere la cosa, anche se le performance sono inferiori a quelle offerte dalla soluzione con SQL Server 2005:

select
*
from
(
select
buco_da = cur.numero ,
buco_a = (select top 1 numero from Esempio nxt where nxt.numero > cur.numero order by nxt.numero)
from
Esempio cur
) t
where
t.buco_a - t.buco_da > 1

Questa soluzione è ideale per tabelle non troppo grosse (diciamo minori di 10000, giusto per dare un valore di paragone).

L’operazione di “select top” fatta a livello di colonna, infatti, richiede al motore di SQL Server di effettuare uno scan per ogni riga della tabella (è possibile verificare tale comportamento attivando l’opzione SET STATISTICS IO ON).

Per tabelle grosse, quindi, è meglio fare affidamento a una tabella temporanea:

select
rn = identity(int, 1, 1),
numero
into
#n
from
Esempio
order by 
numero


select 
cur.numero as buco_da, 
nxt.numero as buco_a
from 
#n as cur
inner join 
#n as nxt on cur.rn + 1 = nxt.rn
where 
nxt.numero - cur.numero > 1;

drop table #n;

Che, di fatto, simula al 100% la logica applicata da SQL Server 2005 in modo automatico, e quindi, benché richieda l’uso di una tabella di appoggio, permette al motore di SQL Server di lavorare meglio (molto meglio!) sfruttando le capacità del Query Optimizer di ottimizzare l’accesso ai dati.

Windows Vista CommandLinks

Di Corrado Cavalli - Microsoft MVP

I CommandLinks sono tra i nuovi elementi di interfaccia utente introdotti in Windows Vista, tecnicamente altro non sono che versioni “modificate” dei buttons che tutti conosciamo. Per trasformare un pulsante “classico” in un CommandLink è sufficiente impostare un nuovo bit di stile (BS_COMMANDLINK) e inviare una serie di nuovi messaggi:

BCM_SETNOTE = Imposta una nota descrittiva che appare sotto il testo del pulsante.
BCM_GETNOTE = Recupera la nota descrittiva impostata con BCM_SETNOTE
BCM_GETNOTELENGTH = Recupera la lunghezza della nota descrittiva.
BCM_SETSHIELD = Visualizza un’icona a forma di scudo all’interno del pulsante.

La classe riportata di seguito continene tutto il necessario per trasformare un pulsante classico in un CommandLink.

C#

using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;

public class CommandLink:Button
{
//Overloads di SendMessaged
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr SendMessage (IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr SendMessage (IntPtr hWnd, UInt32 Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int SendMessage (IntPtr hWnd, uint Msg, ref int wParam, StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int SendMessage (IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

//Messaggi da gestire
const int BS_COMMANDLINK = 0x0000000E;
const uint BCM_SETNOTE = 0x00001609;
const uint BCM_GETNOTE = 0x0000160A;
const uint BCM_GETNOTELENGTH = 0x0000160B;
const uint BCM_SETSHIELD = 0x0000160C;

public CommandLink ()
{
//Imposta lo stile a system
this.FlatStyle = FlatStyle.System;
}

[Category("Vista Only")]
[Description("Nota aggiuntiva")]
public string Note
{
get
{
//Invio BCM_GETNOTELENGTH per ottenere la lunghezza della nota
int ret = SendMessage(base.Handle, BCM_GETNOTELENGTH, 0, 0);
int len = ret + 1;
//Creo buffer e invio BCM_GETNOTE per recuperare la nota
StringBuilder sb = new StringBuilder(len);
ret = SendMessage(base.Handle, BCM_GETNOTE, ref len, sb);
return (sb.ToString());
}
set
{ 
//Invio messaggio che imposta la nota
SendMessage(this.Handle, BCM_SETNOTE, IntPtr.Zero, value);
}
}

private bool _ShowShield;

[Category("Vista Only")]
[Description("Visualizza icona dello scudo tipico della richiesta di privilegi amministrativi")]
public bool ShowShield
{
get { return _ShowShield; }
set 
{
_ShowShield = value;
IntPtr state=new IntPtr(value ? 1 : 0);
SendMessage(this.Handle, BCM_SETSHIELD, IntPtr.Zero,state);
}
}

/// <summary>
/// Imposta lo stile BS_COMMANDLINK, solo se il S.O è VISTA
/// </summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style |= (Environment.OSVersion.Version.Major >= 6) ? BS_COMMANDLINK : 0; 
return (cp);
}
}

}

VB

Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Text
Imports System.Windows.Forms

Public Class CommandLink : Inherits Button
'Overload di SendMessage
<DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInt32,
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
 Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInt32,
ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Unicode, EntryPoint:="SendMessage")> _
 Shared Function SendMessageByRef(ByVal hWnd As IntPtr, ByVal Msg As UInt32,
ByRef wParam As Integer, ByVal lParam As StringBuilder) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInt32, 
ByVal wParam As Integer, ByVal lParam As Integer) As Integer
End Function

'Messaggi da gestire
Const BS_COMMANDLINK As Integer = &HE
Const BCM_SETNOTE As UInteger = &H1609
Const BCM_GETNOTE As UInteger = &H160A
Const BCM_GETNOTELENGTH As UInteger = &H160B
Const BCM_SETSHIELD As UInteger = &H160C

Public Sub New()
'Imposta lo stile a system
MyBase.FlatStyle = FlatStyle.System
End Sub

<Category("Vista Only"), Description("Nota aggiuntiva")> _
Property Note() As String
Get
'Invio BCM_GETNOTELENGTH per ottenere la lunghezza della nota
Dim ret As Integer = SendMessage(MyBase.Handle, BCM_GETNOTELENGTH, 0, 0)
Dim len As Integer = ret + 1
'Creo buffer e invio BCM_GETNOTE per recuperare la nota
Dim sb As StringBuilder = New StringBuilder(len)
ret = SendMessageByRef(MyBase.Handle, BCM_GETNOTE, len, sb)
Return (sb.ToString())
End Get
Set(ByVal value As String)
'Invio messaggio che imposta la nota
SendMessage(MyBase.Handle, BCM_SETNOTE, IntPtr.Zero, value)
End Set
End Property

Private _ShowShield As Boolean
<Category("Vista Only"),
Description("Visualizza icona dello scudo tipico della richiesta di privilegi amministrativi")> _
Property ShowShield() As Boolean
Get
Return _ShowShield
End Get
Set(ByVal value As Boolean)
_ShowShield = value
Dim param As Integer = 0
If value Then param = 1
Dim state As IntPtr = New IntPtr(param)
SendMessage(MyBase.Handle, BCM_SETSHIELD, IntPtr.Zero, state)
End Set
End Property

''' <summary>
''' Imposta lo stile BS_COMMANDLINK, solo se il S.O è VISTA
''' </summary>
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
If Environment.OSVersion.Version.Major >= 6 Then cp.Style = cp.Style Or BS_COMMANDLINK
Return cp
End Get
End Property
End Class

Ecco un esempio di Form contenente due CommandLinks:

*

Caricare un’immagine in maniera asincrona con Windows Presentation Foundation

Di Corrado Cavalli - Microsoft MVP

Il frammento di XAML che segue mostra come caricare un'immagine all’interno di un controllo Image di Windows Presentation Foundation (WPF)

<Grid>
 <Image x:Name="MyImage" Source="c:\logo.jpg"/>
</Grid>

Volendo ottenere lo stesso risultato via codice possiamo scrivere:

C#

MyImage.Source =new BitmapImage(new Uri(@"c:\logo.jpg",UriKind.Absolute));

VB

MyImage.Source =New BitmapImage(New Uri("c:\logo.jpg",UriKind.Absolute))

Purtroppo questo approccio è sincrono percui il thread principale è bloccato fino al completo caricamento dell’immagine, ricorriamo perciò ad un thread all’interno del quale caricheremo l’immagine:

C#

Thread t = new Thread(delegate() {
                      byte[] buffer = File.ReadAllBytes(@"c:\logo.jpg");
  MemoryStream mem = new MemoryStream(buffer);
bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = mem;
bi.EndInit();
MyImage.Source = bi;
}
t.Start();

VB

Dim t As New Thread(AddressOf LoadAsync)
t.Start()

Private Delegate Sub ParamDelegate(ByVal params As Object)
Private Sub LoadAsync()
  Dim buffer As Byte() = File.ReadAllBytes("c:\logo.jpg")
  Dim mem As New MemoryStream(buffer)
  Dim bi As New BitmapImage()
  bi.BeginInit()
  bi.StreamSource = DirectCast(param, MemoryStream)
  bi.EndInit()
  MyImage.Source = bi
End Sub

All’interno del thread l’immagine viene caricata e associata alla proprietà Source di MyImage, purtroppo però il codice non funziona in quanto, anche in WPF, non possiamo accedere alle proprietà di un controllo da un thread diverso da quello che lo ha creato. Modifichiamo perciò l’esempio affinchè la creazione dell’oggetto avvenga nel thread principale usando il metodo Invoke esposto dalla classe Dispatcher dalla quale tutti i controlli WPF ereditano.

C#

private delegate void NoParamDelegate();

Thread t = new Thread(delegate() {
                      byte[] buffer = File.ReadAllBytes(@"c:\logo.jpg");
  MemoryStream mem = new MemoryStream(buffer);
base.Dispatcher.Invoke(
         System.Windows.Threading.DispatcherPriority.Normal,
 (NoParamDelegate)delegate()
 {
   MemoryStream mem = new MemoryStream(buffer);
   BitmapImage bi = new BitmapImage();
      bi.BeginInit();
   bi.StreamSource = mem;
   bi.EndInit();
   MyImage.Source = bi;
 });
}
t.Start();

VB

Dim t As New Thread(AddressOf LoadAsync)
t.Start()

Private Delegate Sub ParamDelegate(ByVal params As Object)
Private Sub LoadAsync()
  Dim buffer As Byte() = File.ReadAllBytes("c:\logo.jpg")
  Dim mem As New MemoryStream(buffer)
  MyBase.Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Normal, _
                          New ParamDelegate(AddressOf UpdateImage), mem)
End Sub

Private Sub UpdateImage(ByVal param As Object)
 Dim bi As New BitmapImage()
 bi.BeginInit()
 bi.StreamSource = DirectCast(param, MemoryStream)
 bi.EndInit()
 MyImage.Source = bi
End Sub

In questo caso abbiamo dovuto creare l’intero oggetto BitmapImage all’interno del delegate NoParamDelegate il quale è invocato nel thread principale. Sfruttando la caratteristica dell’oggetto Freezable (dal quale BitmapImage eredita) di essere condivisible da più threads una volta invocato il metodo Freeze() possiamo usare questa alternativa ancora più performante.

C#

Thread t = new Thread(delegate() {
                      byte[] buffer = File.ReadAllBytes(@"c:\logo.jpg");
  MemoryStream mem = new MemoryStream(buffer);
       MemoryStream mem = new MemoryStream(buffer);
       BitmapImage bi = new BitmapImage();
   bi.BeginInit();
bi.StreamSource = mem;
bi.EndInit();
bi.Freeze(); //Congelo l’oggetto, questo lo rende condivisibile tra threads.
base.Dispatcher.Invoke(
         System.Windows.Threading.DispatcherPriority.Normal,
 (NoParamDelegate)delegate()
 {
   MyImage.Source = bi;
 });
}
t.Start();

VB

Dim t As New Thread(AddressOf LoadAsync)
t.Start()

Private Delegate Sub ParamDelegate(ByVal params As Object)

Private Sub LoadAsync()
  Dim buffer As Byte() = File.ReadAllBytes("c:\logo.jpg")
  Dim mem As New MemoryStream(buffer)
  Dim bi As New BitmapImage()
  bi.BeginInit()
  bi.StreamSource = DirectCast(param, bi)
  bi.EndInit()
  bi.Freeze()
MyBase.Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Normal, _
                          New ParamDelegate(AddressOf UpdateImage), bi)

End Sub

Private Sub UpdateImage(ByVal param As Object) 
 MyImage.Source = DirectCast(param,BitmapImage)
End Sub

Page view tracker