Existe uma maneira mais fácil de percorrer o código do que iniciar o serviço por meio do Windows Service Control Manager e depois anexar o depurador ao encadeamento? É meio complicado e estou me perguntando se existe uma abordagem mais direta.
Existe uma maneira mais fácil de percorrer o código do que iniciar o serviço por meio do Windows Service Control Manager e depois anexar o depurador ao encadeamento? É meio complicado e estou me perguntando se existe uma abordagem mais direta.
Respostas:
Se eu quiser depurar rapidamente o serviço, simplesmente entrei Debugger.Break()
lá. Quando essa linha é alcançada, ela volta ao VS. Não se esqueça de remover essa linha quando terminar.
ATUALIZAÇÃO: Como alternativa aos #if DEBUG
pragmas, você também pode usar o Conditional("DEBUG_SERVICE")
atributo.
[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
Debugger.Break();
}
No seu caso OnStart
, basta chamar este método:
public override void OnStart()
{
DebugMode();
/* ... do the rest */
}
Lá, o código será ativado apenas durante as compilações de depuração. Enquanto você estiver trabalhando nisso, pode ser útil criar uma Configuração de Compilação separada para depuração do serviço.
Também acho que ter uma "versão" separada para execução normal e como serviço é o caminho a percorrer, mas é realmente necessário dedicar uma opção de linha de comando separada para esse fim?
Você não poderia simplesmente fazer:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
Isso teria o "benefício", que você pode simplesmente iniciar seu aplicativo via clique duplo (OK, se você realmente precisar disso) e clicar F5no Visual Studio (sem a necessidade de modificar as configurações do projeto para incluir isso)/console
opção).
Tecnicamente, Environment.UserInteractive
verifica se o WSF_VISIBLE
Sinalizador está definido para a estação atual da janela, mas existe algum outro motivo para retornar false
, além de ser executado como um serviço (não interativo)?
System.Diagnostics.Debugger.IsAttached
vez de Environment.UserInteractive
.
Quando configurei um novo projeto de serviço, há algumas semanas, encontrei este post. Embora haja muitas ótimas sugestões, ainda não encontrei a solução que queria: a possibilidade de chamar as classes de serviço OnStart
eOnStop
métodos sem nenhuma modificação nas classes de serviço.
A solução que eu criei usa o Environment.Interactive
modo de execução select, conforme sugerido por outras respostas a este post.
static void Main()
{
ServiceBase[] servicesToRun;
servicesToRun = new ServiceBase[]
{
new MyService()
};
if (Environment.UserInteractive)
{
RunInteractive(servicesToRun);
}
else
{
ServiceBase.Run(servicesToRun);
}
}
O RunInteractive
auxiliar usa reflexão para chamar os métodos protected 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);
}
Este é todo o código necessário, mas também escrevi orientações com explicações.
walk through
) é garantir que você vá para as propriedades do projeto e altere o tipo de saída para Console Application
antes de tentar compilar e executar. Encontre-o em Project Properties -> Application -> Output type -> Console Application
. Além disso, para que isso funcionasse corretamente, acabei tendo que executar o aplicativo usando o start
comando Ex: C:\"my app name.exe" -service
não funcionaria para mim. Em vez disso eu useiC:\start /wait "" "my app name.exe" -service
Às vezes, é importante analisar o que está acontecendo durante a inicialização do serviço. Anexar ao processo não ajuda aqui, porque você não é rápido o suficiente para anexar o depurador enquanto o serviço está sendo iniciado.
A resposta curta é: estou usando as 4 linhas de código a seguir para fazer isso:
#if DEBUG
base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout
Debugger.Launch(); // launch and attach debugger
#endif
Eles são inseridos no OnStart
método do serviço da seguinte maneira:
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);
}
Para aqueles que não fizeram isso antes, incluímos dicas detalhadas abaixo , porque você pode facilmente ficar preso. As dicas a seguir se referem ao Windows 7x64 e ao Visual Studio 2010 Team Edition , mas também devem ser válidas para outros ambientes.
Importante: Implemente o serviço no modo "manual" (usando o InstallUtil
utilitário no prompt de comandos do VS ou execute um projeto do instalador de serviço que você preparou). Abra o Visual Studio antes de iniciar o serviço e carregue a solução que contém o código-fonte do serviço - configure pontos de interrupção adicionais conforme necessário no Visual Studio - e inicie o serviço via Painel de Controle Serviço.
Devido ao Debugger.Launch
código, isso causará uma caixa de diálogo "Ocorreu uma exceção não tratada do Microsoft .NET Framework no Servicename.exe ". aparecer. Clique como mostrado na captura de tela: Yes, debug Servicename.exe
Posteriormente, principalmente no UAC do Windows 7, você poderá solicitar credenciais de administrador. Digite-os e continue com Yes:
Depois disso, a janela conhecida do depurador just-in-time do Visual Studio é exibida. Ele pergunta se você deseja depurar usando o depurador excluído. Antes de clicar Yes, selecione que você não deseja abrir uma nova instância (segunda opção) - uma nova instância não seria útil aqui, porque o código-fonte não seria exibido. Então você seleciona a instância do Visual Studio que você abriu anteriormente:
Depois de clicar Yes, depois de um tempo, o Visual Studio mostrará a seta amarela na linha em Debugger.Launch
que está a instrução e você poderá depurar seu código (método MyInitOnStart
, que contém sua inicialização).
Pressionar F5continua a execução imediatamente, até o próximo ponto de interrupção que você preparou.
Dica: Para manter o serviço em execução, selecione Depurar -> Desanexar tudo . Isso permite que você execute um cliente se comunicando com o serviço depois que ele foi iniciado corretamente e você terminou de depurar o código de inicialização. Se você pressionar Shift+F5 (parar a depuração), isso encerrará o serviço. Em vez de fazer isso, você deve usar o Painel de Controle do Serviço para interrompê-lo.
Note que
Se você criar uma versão, o código de depuração será automaticamente removido e o serviço será executado normalmente.
Estou usando Debugger.Launch()
, que inicia e anexa um depurador . Também testei Debugger.Break()
, o que não funcionou , porque ainda não existe um depurador conectado na inicialização do serviço (causando o "Erro 1067: O processo foi encerrado inesperadamente" ).
RequestAdditionalTime
define um longo tempo de espera para o arranque do serviço (que é não atrasar o código em si, mas vai continuar imediatamente com aDebugger.Launch
declaração). Caso contrário, o tempo limite padrão para iniciar o serviço é muito curto e o serviço falhará se você não ligar com base.Onstart(args)
rapidez suficiente no depurador. Praticamente, um tempo limite de 10 minutos evita que você veja a mensagem " o serviço não respondeu ..." imediatamente após o início do depurador.
Depois de se acostumar, esse método é muito fácil, pois exige apenas a adição de 4 linhas a um código de serviço existente, permitindo que você obtenha rapidamente controle e depuração.
base.RequestAdditionalTime(600000)
impedirá que o controle de serviço encerre o serviço por 10 minutos, se ele não ligar base.OnStart(args)
dentro desse prazo). Além disso, lembro que o UAC também será abortado se você não inserir as credenciais de administrador depois de um tempo (não sei exatamente quantos segundos, mas acho que você deve inseri-lo em um minuto, caso contrário, o UAC anula) , que encerrará a sessão de depuração.
O que eu costumo fazer é encapsular a lógica do serviço em uma classe separada e iniciá-la a partir de uma classe 'runner'. Essa classe de corredor pode ser o serviço real ou apenas um aplicativo de console. Portanto, sua solução possui (pelo menos) três projetos:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
Este vídeo do YouTube de Fabio Scopel explica como depurar um serviço do Windows bastante bem ... o método real de fazer isso começa às 4:45 no vídeo ...
Aqui está o código explicado no vídeo ... no seu arquivo Program.cs, adicione o material para a seção 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
}
}
}
No seu arquivo Service1.cs, adicione o método OnDebug () ...
public Service1()
{
InitializeComponent();
}
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
// your code to do something
}
protected override void OnStop()
{
}
Como funciona
Basicamente, você precisa criar um public void OnDebug()
que chame o OnStart(string[] args)
como está protegido e não pode ser acessado fora. O void Main()
programa é adicionado com o #if
pré-processador com #DEBUG
.
O Visual Studio define DEBUG
se o projeto é compilado no modo de Depuração. Isso permitirá que a seção de depuração (abaixo) seja executada quando a condição for verdadeira
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
E ele será executado como um aplicativo de console. Quando tudo der certo, você poderá alterar o modo Release
e a else
seção regular ativará a lógica.
ATUALIZAR
Essa abordagem é de longe a mais fácil:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
Deixo minha resposta original abaixo para a posteridade.
Meus serviços tendem a ter uma classe que encapsula um Timer, pois quero que o serviço verifique regularmente se existe algum trabalho para ele fazer.
Iniciamos a classe e chamamos StartEventLoop () durante a inicialização do serviço. (Essa classe também pode ser usada com facilidade em um aplicativo de console.)
O bom efeito colateral desse design é que os argumentos com os quais você configurou o Timer podem ter um atraso antes que o serviço realmente comece a funcionar, para que você tenha tempo para anexar um depurador manualmente.
ps Como anexar o depurador manualmente a um processo em execução ...?
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");
}
}
Também costumava fazer o seguinte (já mencionado nas respostas anteriores, mas com os sinalizadores do compilador condicional [#if] para ajudar a evitar o disparo em uma versão).
Parei de fazê-lo dessa maneira, porque às vezes esquecíamos de criar o Release e interrompíamos o depurador em um aplicativo em execução em uma demonstração do cliente (embaraçoso!).
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
// do something
leva mais de 30 minutos para ser concluído?
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 você não pode modificar o nível de acesso :(
O que eu costumava fazer era ter uma opção de linha de comando que iniciasse o programa como um serviço ou como um aplicativo regular. Então, no meu IDE, eu definiria a opção para que eu pudesse percorrer meu código.
Em alguns idiomas, é possível detectar se ele está sendo executado em um IDE e executar essa opção automaticamente.
Que linguagem você está usando?
Use a biblioteca TopShelf .
Crie um aplicativo de console e defina a configuração no seu 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()
{
}
}
Para depurar seu serviço, basta pressionar F5 no visual studio.
Para instalar o serviço, digite cmd "console.exe install"
Você pode iniciar e interromper o serviço no gerenciador de serviços do Windows.
Eu acho que depende do sistema operacional que você estiver usando, o Vista é muito mais difícil de conectar aos Serviços, devido à separação entre as sessões.
As duas opções que usei no passado são:
Espero que isto ajude.
Eu gosto de poder depurar todos os aspectos do meu serviço, incluindo qualquer inicialização no OnStart (), enquanto ainda o executo com comportamento de serviço completo dentro da estrutura do SCM ... sem modo "console" ou "aplicativo".
Eu faço isso criando um segundo serviço, no mesmo projeto, para usar na depuração. O serviço de depuração, quando iniciado como de costume (ou seja, no plug-in MMC de serviços), cria o processo de host do serviço. Isso fornece um processo ao qual anexar o depurador, mesmo que você ainda não tenha iniciado seu serviço real. Após anexar o depurador ao processo, inicie o serviço real e você poderá entrar nele em qualquer lugar do ciclo de vida do serviço, incluindo OnStart ().
Como requer uma intrusão mínima de código, o serviço de depuração pode ser facilmente incluído no seu projeto de configuração de serviço e é facilmente removido do seu release de produção, comentando uma única linha de código e excluindo um único instalador do projeto.
Detalhes:
1) Supondo que você esteja implementando MyService
, também crie MyServiceDebug
. Adicione ambos à ServiceBase
matriz da seguinte Program.cs
maneira:
/// <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) Adicione o serviço real E o serviço de depuração ao instalador do projeto:
Ambos os serviços (real e depuração) são incluídos quando você adiciona a saída do projeto de serviço ao projeto de instalação do serviço. Após a instalação, os dois serviços aparecerão no plug-in service.msc MMC.
3) Inicie o serviço de depuração no MMC.
4) No Visual Studio, conecte o depurador ao processo iniciado pelo serviço de depuração.
5) Inicie o serviço real e aproveite a depuração.
Quando escrevo um serviço, coloco toda a lógica de serviço em um projeto de DLL e crio dois "hosts" que chamam nessa DLL, um é um serviço do Windows e o outro é um aplicativo de linha de comando.
Uso o aplicativo de linha de comando para depuração e anexo o depurador ao serviço real apenas para erros que não consigo reproduzir no aplicativo de linha de comando.
Se você usar essa abordagem, lembre-se de que é necessário testar todo o código enquanto estiver executando em um serviço real, enquanto a ferramenta de linha de comando é uma boa ajuda para depuração, é um ambiente diferente e não se comporta exatamente como um serviço real.
Ao desenvolver e depurar um serviço do Windows, normalmente o executo como um aplicativo de console adicionando um parâmetro de inicialização / console e verificando isso. Torna a vida muito mais fácil.
static void Main(string[] args) {
if (Console.In != StreamReader.Null) {
if (args.Length > 0 && args[0] == "/console") {
// Start your service work.
}
}
}
Para depurar o Windows Services, eu combino o GFlags e um arquivo .reg criado pelo regedit.
Ou salve os seguintes trechos e substitua servicename.exe pelo nome do executável desejado.
debugon.reg:
Editor do Registro do Windows versão 5.00 [Opções de execução de arquivo de imagem HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image \ servicename.exe] "GlobalFlag" = "0x00000000" "Depurador" = "vsjitdebugger.exe"
debugoff.reg:
Editor do Registro do Windows versão 5.00 [Opções de execução de arquivo de imagem HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image \ servicename.exe] "GlobalFlag" = "0x00000000"
Para programação de rotina de pequenas coisas, eu fiz um truque muito simples para depurar facilmente meu serviço:
No início do serviço, verifico o parâmetro da linha de comandos "/ debug". Se o serviço for chamado com esse parâmetro, eu não faço a inicialização normal do serviço, mas inicio todos os ouvintes e apenas exibo uma caixa de mensagens "Depuração em andamento, pressione ok para finalizar".
Portanto, se meu serviço for iniciado da maneira usual, ele será iniciado como serviço; se for iniciado com o parâmetro / depuração da linha de comando, ele funcionará como um programa normal.
No VS, adicionarei / deparei como parâmetro de depuração e inicio o programa de serviço diretamente.
Dessa forma, eu posso depurar facilmente para a maioria dos pequenos problemas de tipo. Obviamente, algumas coisas ainda precisarão ser depuradas como serviço, mas para 99% isso é bom o suficiente.
Eu uso uma variação na resposta da JOP. Usando parâmetros da linha de comando, você pode definir o modo de depuração no IDE com propriedades do projeto ou através do gerenciador de serviços do Windows.
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
Para solucionar problemas no programa existente do Windows Service, use 'Debugger.Break ()', como sugeriram outros.
Para o novo programa Windows Service, sugiro usar o método de James Michael Hare http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template- redux.aspx
Basta colocar seu almoço do depurador em qualquer lugar e anexar o Visualstudio na inicialização
#if DEBUG
Debugger.Launch();
#endif
Também é necessário iniciar o VS como Administatrator e permitir que um processo possa ser depurado automaticamente por um usuário diferente (conforme explicado aqui ):
reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
Use o projeto C # do Modelo de Serviço do Windows para criar um novo aplicativo de serviço https://github.com/HarpyWar/windows-service-template
Existem modos de console / serviço detectados automaticamente, instalador / desinstalador automático do seu serviço e vários recursos mais usados estão incluídos.
Aqui está o método simples que eu usei para testar o serviço, sem métodos adicionais de "Depuração" e com testes de unidade VS integrados.
[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
}
}
Você tem duas opções para fazer a depuração.
Por favor consulte ESTA publicação do blog que eu criei para o tópico.
Basta colar
Debugger.Break();
em qualquer lugar no seu código.
Por exemplo ,
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);
}
}
Será atingido Debugger.Break();
quando você executar o seu programa.
A melhor opção é usar o ' System.Diagnostics espaço para nome ' '.
Coloque seu código no bloco if else do modo de depuração e de liberação, como mostrado abaixo, para alternar entre o modo de depuração e de liberação no 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