Esiste un modo più semplice per scorrere il codice che avviare il servizio tramite Gestione controllo servizi di Windows e quindi collegare il debugger al thread? È un po 'ingombrante e mi chiedo se esiste un approccio più diretto.
Esiste un modo più semplice per scorrere il codice che avviare il servizio tramite Gestione controllo servizi di Windows e quindi collegare il debugger al thread? È un po 'ingombrante e mi chiedo se esiste un approccio più diretto.
Risposte:
Se voglio eseguire il debug rapido del servizio, inserisco semplicemente una Debugger.Break()
. Quando viene raggiunta quella linea, mi riporterà a VS. Non dimenticare di rimuovere quella linea quando hai finito.
AGGIORNAMENTO: in alternativa ai #if DEBUG
pragmi, puoi anche usare l' Conditional("DEBUG_SERVICE")
attributo.
[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
Debugger.Break();
}
Sul tuo OnStart
, basta chiamare questo metodo:
public override void OnStart()
{
DebugMode();
/* ... do the rest */
}
Lì, il codice sarà abilitato solo durante le build di debug. Mentre ci sei, potrebbe essere utile creare una Configurazione build separata per il debug del servizio.
Penso anche che avere una "versione" separata per la normale esecuzione e come servizio sia la strada da percorrere, ma è davvero necessario dedicare un interruttore della riga di comando separato a tale scopo?
Non potresti semplicemente fare:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
Ciò avrebbe il "vantaggio", che puoi semplicemente avviare la tua app tramite doppio clic (OK, se ne hai davvero bisogno) e che puoi semplicemente accedere F5a Visual Studio (senza la necessità di modificare le impostazioni del progetto per includere /console
quell'opzione).
Tecnicamente, Environment.UserInteractive
controlla se WSF_VISIBLE
Flag è impostato per la stazione corrente della finestra, ma c'è qualche altro motivo per cui dovrebbe tornare false
, oltre ad essere gestito come un servizio (non interattivo)?
System.Diagnostics.Debugger.IsAttached
invece di Environment.UserInteractive
.
Quando ho creato un nuovo progetto di assistenza qualche settimana fa, ho trovato questo post. Sebbene ci siano molti ottimi suggerimenti, non ho ancora trovato la soluzione che volevo: la possibilità di chiamare le classi di servizio OnStart
e i OnStop
metodi senza alcuna modifica alle classi di servizio.
La soluzione che ho trovato utilizza la Environment.Interactive
modalità di esecuzione select, come suggerito da altre risposte a questo post.
static void Main()
{
ServiceBase[] servicesToRun;
servicesToRun = new ServiceBase[]
{
new MyService()
};
if (Environment.UserInteractive)
{
RunInteractive(servicesToRun);
}
else
{
ServiceBase.Run(servicesToRun);
}
}
L' RunInteractive
helper utilizza la reflection per chiamare il metodo protetto OnStart
e OnStop
:
static void RunInteractive(ServiceBase[] servicesToRun)
{
Console.WriteLine("Services running in interactive mode.");
Console.WriteLine();
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] { new string[] { } });
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(
"Press any key to stop the services and end the process...");
Console.ReadKey();
Console.WriteLine();
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped");
}
Console.WriteLine("All services stopped.");
// Keep the console alive for a second to allow the user to see the message.
Thread.Sleep(1000);
}
Questo è tutto il codice richiesto, ma ho anche scritto una procedura dettagliata con spiegazioni.
walk through
) è assicurarmi di andare nelle proprietà del progetto e cambiare il tipo di output Console Application
prima di provare a compilare ed eseguire. Trovalo a Project Properties -> Application -> Output type -> Console Application
. Inoltre, affinché questo funzioni correttamente per me, ho finito per dover eseguire l'applicazione usando il start
comando. Es: C:\"my app name.exe" -service
non funzionerebbe per me. Invece ho usatoC:\start /wait "" "my app name.exe" -service
A volte è importante analizzare cosa succede durante l'avvio del servizio. Il collegamento al processo non aiuta in questo caso, poiché non si è abbastanza veloci da collegare il debugger durante l'avvio del servizio.
La risposta breve è, sto usando le seguenti 4 righe di codice per fare questo:
#if DEBUG
base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout
Debugger.Launch(); // launch and attach debugger
#endif
Questi sono inseriti nel OnStart
metodo del servizio come segue:
protected override void OnStart(string[] args)
{
#if DEBUG
base.RequestAdditionalTime(600000); // 10 minutes timeout for startup
Debugger.Launch(); // launch and attach debugger
#endif
MyInitOnstart(); // my individual initialization code for the service
// allow the base class to perform any work it needs to do
base.OnStart(args);
}
Per coloro che non l'hanno mai fatto prima, ho incluso suggerimenti dettagliati di seguito , perché puoi facilmente rimanere bloccato. I seguenti suggerimenti si riferiscono a Windows 7x64 e Visual Studio 2010 Team Edition , ma dovrebbero essere validi anche per altri ambienti.
Importante: distribuire il servizio in modalità "manuale" (utilizzando l' InstallUtil
utilità dal prompt dei comandi VS o eseguire un progetto di installazione del servizio che è stato preparato). Aprire Visual Studio prima di avviare il servizio e caricare la soluzione contenente il codice sorgente del servizio - impostare punti di interruzione aggiuntivi come richiesto in Visual Studio - quindi avviare il servizio tramite il Pannello di controllo del servizio.
A causa del Debugger.Launch
codice, ciò causerà una finestra di dialogo "Si è verificata un'eccezione di Microsoft .NET Framework non gestita in Servicename.exe ." apparire. Clicca come mostrato nello screenshot: Yes, debug Servicename.exe
Successivamente, soprattutto in UAC di Windows 7, è possibile che venga richiesto di immettere le credenziali di amministratore. Inseriscili e procedi con Yes:
Successivamente, viene visualizzata la nota finestra del debugger just-in-time di Visual Studio . Ti chiede se desideri eseguire il debug utilizzando il debugger eliminato. Prima di fare clic Yes, selezionare che non si desidera aprire una nuova istanza (seconda opzione): una nuova istanza non sarebbe utile qui, poiché il codice sorgente non verrebbe visualizzato. Quindi selezioni invece l'istanza di Visual Studio che hai aperto in precedenza:
Dopo aver fatto clic Yes, dopo un po 'Visual Studio mostrerà la freccia gialla nella riga in cui si trova l' Debugger.Launch
istruzione e sarà possibile eseguire il debug del codice (metodo MyInitOnStart
, che contiene l'inizializzazione).
La pressione F5continua l'esecuzione immediatamente, fino al raggiungimento del punto di interruzione successivo che è stato preparato.
Suggerimento: per mantenere attivo il servizio, selezionare Debug -> Scollega tutto . Ciò consente di eseguire un client che comunica con il servizio dopo che è stato avviato correttamente e al termine del debug del codice di avvio. Se si preme Shift+F5 (interrompe il debug), questo terminerà il servizio. Invece di farlo, dovresti usare il Pannello di controllo del servizio per fermarlo.
Si noti che
Se si crea una versione, il codice di debug viene automaticamente rimosso e il servizio viene eseguito normalmente.
Sto usando Debugger.Launch()
, che avvia e allega un debugger . Ho anche testato Debugger.Break()
, che non ha funzionato , perché non è ancora collegato alcun debugger all'avvio del servizio (causando "Errore 1067: il processo è terminato in modo imprevisto" ).
RequestAdditionalTime
imposta un lungo timeout per l'avvio del servizio (è non ritardare il codice stesso, ma immediatamente continuare con la Debugger.Launch
dichiarazione). In caso contrario, il timeout predefinito per l'avvio del servizio è troppo breve e l'avvio del servizio non riesce se non si chiama base.Onstart(args)
abbastanza rapidamente dal debugger. In pratica, un timeout di 10 minuti evita che venga visualizzato il messaggio " il servizio non ha risposto ..." immediatamente dopo l'avvio del debugger.
Una volta che ci si abitua, questo metodo è molto semplice perché richiede solo di aggiungere 4 righe a un codice di servizio esistente, consentendo di ottenere rapidamente controllo e debug.
base.RequestAdditionalTime(600000)
impedirà al controllo del servizio di terminare il servizio per 10 minuti se non chiama base.OnStart(args)
entro tale periodo di tempo). A parte questo, ricordo che UAC si interromperà anche se non inserisci le credenziali di amministratore dopo un po '(non so quanti secondi esattamente, ma penso che devi inserirlo entro un minuto, altrimenti UAC si interrompe) , che terminerà la sessione di debug.
Quello che faccio di solito è incapsulare la logica del servizio in una classe separata e iniziare da una classe "runner". Questa classe di corridori può essere il servizio effettivo o solo un'applicazione console. Quindi la tua soluzione ha (almeno) 3 progetti:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
Questo video di YouTube di Fabio Scopel spiega come eseguire correttamente il debug di un servizio Windows ... il metodo effettivo per farlo inizia alle 4:45 nel video ...
Ecco il codice spiegato nel video ... nel tuo file Program.cs, aggiungi gli elementi per la sezione Debug ...
namespace YourNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
#if DEBUG
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}
}
Nel tuo file Service1.cs, aggiungi il metodo OnDebug () ...
public Service1()
{
InitializeComponent();
}
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
// your code to do something
}
protected override void OnStop()
{
}
Come funziona
Fondamentalmente devi creare un public void OnDebug()
che chiama il OnStart(string[] args)
come è protetto e non accessibile all'esterno. Il void Main()
programma viene aggiunto con il #if
preprocessore con#DEBUG
.
Visual Studio definisce DEBUG
se il progetto è compilato in modalità Debug. Ciò consentirà l'esecuzione della sezione di debug (sotto) quando la condizione è vera
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
E funzionerà proprio come un'applicazione console, una volta che le cose vanno bene puoi cambiare la modalità Release
e la else
sezione normale attiverà la logica
AGGIORNARE
Questo approccio è di gran lunga il più semplice:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
Lascio la mia risposta originale qui sotto per i posteri.
I miei servizi tendono ad avere una classe che incapsula un Timer poiché voglio che il servizio controlli a intervalli regolari se c'è qualche lavoro da fare.
Rinnoviamo la classe e chiamiamo StartEventLoop () durante l'avvio del servizio. (Questa classe potrebbe essere facilmente utilizzata anche da un'app console.)
Il piacevole effetto collaterale di questo design è che gli argomenti con cui si imposta il timer possono essere utilizzati per avere un ritardo prima che il servizio inizi effettivamente a funzionare, in modo da avere il tempo di collegare manualmente un debugger.
ps Come collegare manualmente il debugger a un processo in esecuzione ...?
using System;
using System.Threading;
using System.Configuration;
public class ServiceEventHandler
{
Timer _timer;
public ServiceEventHandler()
{
// get configuration etc.
_timer = new Timer(
new TimerCallback(EventTimerCallback)
, null
, Timeout.Infinite
, Timeout.Infinite);
}
private void EventTimerCallback(object state)
{
// do something
}
public void StartEventLoop()
{
// wait a minute, then run every 30 minutes
_timer.Change(TimeSpan.Parse("00:01:00"), TimeSpan.Parse("00:30:00");
}
}
Inoltre, ero solito fare quanto segue (già menzionato nelle risposte precedenti ma con i flag del compilatore condizionale [#if] per evitare che sparasse in una build di rilascio).
Ho smesso di farlo in questo modo perché a volte ci saremmo dimenticati di compilare in Release e avere un'interruzione del debugger in un'app in esecuzione su una demo client (imbarazzante!).
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
// do something
richiede più di 30 minuti?
static void Main()
{
#if DEBUG
// Run as interactive exe in debug mode to allow easy
// debugging.
var service = new MyService();
service.OnStart(null);
// Sleep the main thread indefinitely while the service code
// runs in .OnStart
Thread.Sleep(Timeout.Infinite);
#else
// Run normally as service in release mode.
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]{ new MyService() };
ServiceBase.Run(ServicesToRun);
#endif
}
OnStart
è protected
e non è possibile modificare il livello di accesso :(
Quello che ero solito fare era avere un commutatore da riga di comando che avrebbe avviato il programma come servizio o come normale applicazione. Quindi, nel mio IDE imposterei l'interruttore in modo da poter scorrere il mio codice.
Con alcune lingue puoi effettivamente rilevare se è in esecuzione in un IDE ed eseguire questa opzione automaticamente.
Che lingua stai usando?
Usa la libreria TopShelf .
Crea un'applicazione console quindi configura la configurazione nel tuo Main
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
// setup service start and stop.
x.Service<Controller>(s =>
{
s.ConstructUsing(name => new Controller());
s.WhenStarted(controller => controller.Start());
s.WhenStopped(controller => controller.Stop());
});
// setup recovery here
x.EnableServiceRecovery(rc =>
{
rc.RestartService(delayInMinutes: 0);
rc.SetResetPeriod(days: 0);
});
x.RunAsLocalSystem();
});
}
}
public class Controller
{
public void Start()
{
}
public void Stop()
{
}
}
Per eseguire il debug del tuo servizio, premi F5 in Visual Studio.
Per installare il servizio, digitare cmd "console.exe install"
È quindi possibile avviare e interrompere il servizio nel gestore servizi di Windows.
Penso che dipenda dal sistema operativo in uso, Vista è molto più difficile da collegare ai servizi, a causa della separazione tra le sessioni.
Le due opzioni che ho usato in passato sono:
Spero che questo ti aiuti.
Mi piace essere in grado di eseguire il debug di ogni aspetto del mio servizio, compresa qualsiasi inizializzazione in OnStart (), mentre lo sto ancora eseguendo con un comportamento di servizio completo nel quadro di SCM ... nessuna modalità "console" o "app".
Lo faccio creando un secondo servizio, nello stesso progetto, da utilizzare per il debug. Il servizio di debug, quando avviato come di consueto (ovvero nel plug-in MMC dei servizi), crea il processo host del servizio. Questo ti dà un processo per collegare il debugger anche se non hai ancora iniziato il tuo vero servizio. Dopo aver collegato il debugger al processo, avvia il tuo vero servizio e puoi interromperlo ovunque nel ciclo di vita del servizio, incluso OnStart ().
Poiché richiede un'intrusione di codice molto minima, il servizio di debug può essere facilmente incluso nel progetto di installazione del servizio e può essere facilmente rimosso dalla versione di produzione commentando una singola riga di codice ed eliminando un singolo programma di installazione del progetto.
Dettagli:
1) Supponendo che stai implementando MyService
, crea anche MyServiceDebug
. Aggiungi entrambi ServiceBase
all'array in questo Program.cs
modo:
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new MyService(),
new MyServiceDebug()
};
ServiceBase.Run(ServicesToRun);
}
2) Aggiungere il servizio reale E il servizio di debug al programma di installazione del progetto per il progetto di servizio:
Entrambi i servizi (reali e di debug) vengono inclusi quando si aggiunge l'output del progetto di servizio al progetto di installazione per il servizio. Dopo l'installazione, entrambi i servizi verranno visualizzati nel plug-in MMC service.msc.
3) Avviare il servizio di debug in MMC.
4) In Visual Studio, collegare il debugger al processo avviato dal servizio di debug.
5) Avvia il servizio reale e goditi il debug.
Quando scrivo un servizio inserisco tutta la logica di servizio in un progetto DLL e creo due "host" che chiamano in questa DLL, uno è un servizio Windows e l'altro è un'applicazione da riga di comando.
Uso l'applicazione a riga di comando per il debug e allego il debugger al servizio reale solo per i bug che non riesco a riprodurre nell'applicazione a riga di comando.
Se usi questo approccio, ricorda solo che devi testare tutto il codice durante l'esecuzione in un servizio reale, mentre lo strumento da riga di comando è un valido aiuto per il debug, è un ambiente diverso e non si comporta esattamente come un vero servizio.
Durante lo sviluppo e il debug di un servizio Windows in genere lo eseguo come un'applicazione console aggiungendo un parametro di avvio / console e verificandolo. Semplifica la vita.
static void Main(string[] args) {
if (Console.In != StreamReader.Null) {
if (args.Length > 0 && args[0] == "/console") {
// Start your service work.
}
}
}
Per eseguire il debug dei servizi Windows, combino GFlags e un file .reg creato da regedit.
Oppure salva i seguenti frammenti e sostituisci servicename.exe con il nome eseguibile desiderato.
debugon.reg:
Editor del registro di Windows Versione 5.00 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Opzioni di esecuzione file immagine \ servicename.exe] "GlobalFlag" = "0x00000000" "Debugger" = "vsjitdebugger.exe"
debugoff.reg:
Editor del registro di Windows Versione 5.00 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Opzioni di esecuzione file immagine \ servicename.exe] "GlobalFlag" = "0x00000000"
Per la programmazione ordinaria di piccole cose ho fatto un trucco molto semplice per eseguire facilmente il debug del mio servizio:
All'avvio del servizio, cerco un parametro della riga di comando "/ debug". Se il servizio viene chiamato con questo parametro, non eseguo il consueto avvio del servizio, ma invece avvio tutti i listener e visualizzo solo una finestra di messaggio "Debug in progress, premi ok per terminare".
Quindi, se il mio servizio viene avviato come di consueto, verrà avviato come servizio, se viene avviato con il parametro della riga di comando / debug funzionerà come un normale programma.
In VS aggiungerò / debug come parametro di debug e avvierò direttamente il programma di servizio.
In questo modo posso facilmente eseguire il debug per la maggior parte dei piccoli problemi. Naturalmente, alcune cose dovranno ancora essere sottoposte a debug come servizio, ma per il 99% è abbastanza buono.
Uso una variazione sulla risposta di JOP. Utilizzando i parametri della riga di comando è possibile impostare la modalità di debug nell'IDE con le proprietà del progetto o tramite il service manager di Windows.
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
Per la risoluzione dei problemi sul programma di servizio Windows esistente, usa "Debugger.Break ()" come altri ragazzi hanno suggerito.
Per il nuovo programma di servizio Windows, suggerirei di utilizzare il metodo di James Michael Hare http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template- redux.aspx
Metti il tuo pranzo debugger ovunque e collega Visualstudio all'avvio
#if DEBUG
Debugger.Launch();
#endif
Inoltre, devi avviare VS come Administatrator e devi consentire che un processo possa essere automaticamente sottoposto a debug da un utente diverso (come spiegato qui ):
reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
Utilizzare il progetto C # modello di servizio di Windows per creare una nuova app di servizio https://github.com/HarpyWar/windows-service-template
Esistono modalità console / servizio rilevate automaticamente, installazione automatica / disinstallazione del servizio e sono incluse diverse funzionalità più utilizzate.
Ecco il semplice metodo che ho usato per testare il servizio, senza ulteriori metodi di "Debug" e con Test VS Unit integrati.
[TestMethod]
public void TestMyService()
{
MyService fs = new MyService();
var OnStart = fs.GetType().BaseType.GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(fs, new object[] { null });
}
// As an extension method
public static void Start(this ServiceBase service, List<string> parameters)
{
string[] par = parameters == null ? null : parameters.ToArray();
var OnStart = service.GetType().GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(service, new object[] { par });
}
static class Program
{
static void Main()
{
#if DEBUG
// TODO: Add code to start application here
// //If the mode is in debugging
// //create a new service instance
Service1 myService = new Service1();
// //call the start method - this will start the Timer.
myService.Start();
// //Set the Thread to sleep
Thread.Sleep(300000);
// //Call the Stop method-this will stop the Timer.
myService.Stop();
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}
Hai due opzioni per eseguire il debug.
Si prega di fare riferimento a QUESTO post sul blog che ho creato per l'argomento.
Basta incollare
Debugger.Break();
ovunque nel tuo codice.
Per esempio ,
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
private static void Main()
{
Debugger.Break();
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
Colpirà Debugger.Break();
quando esegui il tuo programma.
L'opzione migliore è utilizzare lo spazio dei nomi " System.Diagnostics ".
Racchiudere il codice in caso contrario bloccare per la modalità di debug e la modalità di rilascio come mostrato di seguito per passare dalla modalità di debug a quella di rilascio in Visual Studio,
#if DEBUG // for debug mode
**Debugger.Launch();** //debugger will hit here
foreach (var job in JobFactory.GetJobs())
{
//do something
}
#else // for release mode
**Debugger.Launch();** //debugger will hit here
// write code here to do something in Release mode.
#endif