C# Service vs C++ Service

T

Tbone

In C++ I can create a console project and add some code to turn it into a
Windows Service. The same EXE file can be executed from Explorer as a
console to see the output and it can also be run from Service Control
Manager as a Windows Service. Is that possible in C#? As far as I can tell
I can only make a Window Service project and that EXE file will never run as
a standalone EXE file, or I can make a Console project and that EXE file
will never run as a Windows Service. Is it possible to have the best of
both worlds like I can in C++?
 
J

Jeroen Mostert

Tbone said:
In C++ I can create a console project and add some code to turn it into a
Windows Service. The same EXE file can be executed from Explorer as a
console to see the output and it can also be run from Service Control
Manager as a Windows Service. Is that possible in C#?

Yes. To do this, manually alter the code that starts the service. Where it
now calls ServiceBase.Run(), make that conditional on a check to
Environment.UserInteractive. If that's false, instead call the
Service.OnStart() and Service.OnStop() yourself (or call whatever code
that's wrapping directly).

This is not completely foolproof (it's possible for non-services to run in a
noninteractive window station) but it's good enough.
 
J

Jeroen Mostert

Jeroen said:
Yes. To do this, manually alter the code that starts the service. Where
it now calls ServiceBase.Run(), make that conditional on a check to
Environment.UserInteractive. If that's false,

Duh. If that's *true*.
 
J

Jeff Dege

In C++ I can create a console project and add some code to turn it into
a Windows Service. The same EXE file can be executed from Explorer as a
console to see the output and it can also be run from Service Control
Manager as a Windows Service. Is that possible in C#? As far as I can
tell I can only make a Window Service project and that EXE file will
never run as a standalone EXE file, or I can make a Console project and
that EXE file will never run as a Windows Service. Is it possible to
have the best of both worlds like I can in C++?

I posted an example of a program that would run either as a service or
as a console app over in microsoft.public.dotnet.framework.aspet, just
the other day, in a thread titled "Will .NET 2.0 Windows Services run
under Windows 2000?"

The sample code will not only run as a service, it will install itself or
uninstall itself, without having to bother with installutil.exe.

The only problem with that code I've found so far is that it hangs on
startup when run under Windows 2000 - but it only does that if the
CanHandleSessionChangeEvent flag is set true, and the OnSessionChange()
handler function exists.

In Visual Studio 2005, create a console app, add references to
System.Configuration.Install and System.ServiceProcess, then paste this
in program.cs, and compile.

Run from the command-line without arguments, it runs as a regular console
app. If the first argument is "install", it installs itself itself as a
service. If the first argument is anything else, it uninstalls.

It can also be installed or uninstalled using installutil.exe - that uses
the no-parameter constructor of the TestInstaller class.

When running, it runs a loop that writes a message to the Event Log every ten
seconds (and to the console, if it's running as a console app.) Pressing
any key while it's running from the console breaks the loop, clicking on
"Stop" in the Service Control Manager does the same, when it's running as
a service.

It has AutoLog turned on, so when things work correctly, the Service
Control Manager will write to the Event Log as it sends start and stop
events to the service.

The bit with WNetGetConnectionA() is to get around a problem with
mapped drive letters. If the .EXE is sitting on a mapped drive,
the install method writes the mapped drive into the service's
ImagePath registry key, and the Service Control Manager doesn't
seem to understand them.


I'll repost the code here:


==== BEGIN SOURCE ====

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Threading;


namespace TestServiceNS
{
class TestService : ServiceBase
{
static void Main(string[] args)
{
TestService testService = new TestService(args);
testService.begin();
}

public TestService(string[] args)
{
Log.log("TestService constructed");

if (Environment.UserInteractive)
runningAsService = false;
else
runningAsService = true;

Log.log((runningAsService ? "" : "not ") +
"running as service");

CanHandlePowerEvent = true;
CanHandleSessionChangeEvent = true;
CanPauseAndContinue = true;
CanShutdown = true;
CanStop = true;

AutoLog = true;

if (args.Length > 0)
serviceInstall(args[0]);
}

private bool runningAsService;
private bool runningInstall = false;

private void serviceInstall(string action)
{
Log.log("serviceInstall(" + action + ")");
runningInstall = true;

try
{
string location =
toUnc(Assembly.GetEntryAssembly().Location);

String path =
String.Format("/assemblypath={0}", location);
String[] cmdLine = { path };

InstallContext ctx =
new InstallContext("install.Log.log", cmdLine);

TransactedInstaller ti = new TransactedInstaller();
ti.Context = ctx;

TestInstaller testInstaller =
new TestInstaller("Test", "TestService",
ServiceAccount.LocalSystem,
ServiceStartMode.Automatic, null, null);

ti.Installers.Add(testInstaller);

if (action == "install")
{
Log.log("installing service");
ti.Install(new Hashtable());
}
else
{
Log.log("uninstalling service");
ti.Uninstall(null);
}
}
catch (Exception ex)
{
Log.log("Exception: " + ex);
}

Log.log("serviceInstall(" + action + ") finished");
}

public void begin()
{
Log.log("TestService started");

if (Environment.UserInteractive)
{
if (runningInstall)
Log.log("Running install");
else
work();
}
else
ServiceBase.Run(this);

if (!runningAsService)
{
Console.WriteLine("Press any key to exit");
Console.ReadKey(true);
}

Log.log("TestService finished");
}


[DllImport("mpr.dll")]
static extern uint WNetGetConnectionA(string lpLocalName,
StringBuilder lpRemoteName, ref int lpnLength);

static string toUnc(string path)
{
if (path.Contains(":"))
{
string[] tmp = path.Split(':');
string driveLetter = tmp[0] + ":";
string remainingPath = tmp[1];

int length = 256;
StringBuilder networkShare = new StringBuilder(length);

uint status =
WNetGetConnectionA(driveLetter, networkShare, ref length);
if (status == 0)
return networkShare.ToString() + remainingPath;
}

return path;
}

public bool checkExit()
{
if (runningAsService)
return shutdownRequested;

if (!Console.KeyAvailable)
return false;

Console.ReadKey(true);

return true;
}

public void work()
{
try
{
for (int i=0; ; i++)
{
if (checkExit())
break;

if (pauseRequested)
continue;

if ((i%100) == 0)
Log.log("in work loop" +
(runningAsService ? "" :
", press any key to break") + " ...");

Thread.Sleep(100);
}
}
catch (Exception ex)
{
Log.log("Exception: " + ex);
}
}

private Thread workerThread;

private bool shutdownRequested = false;
private bool pauseRequested = false;

public void requestShutDown()
{
Log.log("Shutdown requested");
shutdownRequested = true;
}

public void pause()
{
Log.log("Pause requested");
pauseRequested = true;
}

public void resume()
{
Log.log("Resume requested");
pauseRequested = false;
}

protected override void Dispose(bool disposing)
{
Log.log("Dispose()");
base.Dispose(disposing);
}

protected override void OnStart(string[] args)
{
Log.log("OnStart()");
workerThread = new Thread(new ThreadStart(work));
workerThread.Start();
base.OnStart(args);
}

protected override void OnStop()
{
Log.log("OnStop()");
requestShutDown();
workerThread.Join(2000);
base.OnStop();
}

protected override void OnPause()
{
Log.log("OnPause()");
pause();
base.OnPause();
}

protected override void OnContinue()
{
Log.log("OnContinue()");
resume();
base.OnContinue();
}

protected override void OnShutdown()
{
Log.log("OnShutdown()");
base.OnShutdown();
}

protected override void OnCustomCommand(int command)
{
Log.log("OnCustomCommand()");
base.OnCustomCommand(command);
}

protected override bool OnPowerEvent(
PowerBroadcastStatus powerStatus)
{
Log.log("OnPowerEvent()");
return base.OnPowerEvent(powerStatus);
}

protected override void OnSessionChange(
SessionChangeDescription changeDescription)
{
Log.log("OnSessionChange()");
base.OnSessionChange(changeDescription);
}
}

class Log
{
static string evSource = "TestService";
static string evLog = "TestService";

public static void log(string message)
{
if (!EventLog.SourceExists(evSource))
EventLog.CreateEventSource(evSource, evLog);

System.Console.WriteLine(
DateTime.Now.ToString(
"yyyy-MM-dd HH:mm:ss.fff ") + message);
EventLog.WriteEntry(evSource, message);
}
}

[RunInstaller(true)]
public class TestInstaller : Installer
{
public TestInstaller()
:
this("Test", "TestService", ServiceAccount.LocalSystem,
ServiceStartMode.Automatic, null, null)
{
Log.log("TestInstaller constructed by installutil.exe");
}

public TestInstaller(string serviceName, string displayName,
ServiceAccount account, ServiceStartMode startMode,
string username, string password)
{
Log.log("TestInstaller(" + serviceName + ")");

ServiceProcessInstaller serviceProcessInstaller =
new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();

serviceProcessInstaller.Account = account;
serviceProcessInstaller.Username = username;
serviceProcessInstaller.Password = password;

serviceInstaller.DisplayName = displayName;
serviceInstaller.StartType = startMode;

serviceInstaller.ServiceName = serviceName;

this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
}
}



==== END SOURCE ====




--
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS d- s+:++ a+ C+++$ UL+++ P--- L+++ E--- W-- N++ o-- K+++ w--- O@ M--
V-- PS+ PE Y+ PGP t--- a-5 X-- R- tv b++++ DI++++ D++ G+ e++> h+
r* y*
-----END GEEK CODE BLOCK-----
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top