Simplest Service

Windows service is somewhat considered relic of the past. It is just not sexy enough for users. More often than not I see business applications that need to be always on sitting in tray processing their data. And there is mandatory “do not log off” paper sitting on keyboard.

And I always hear same excuses. My version of Visual Studio does not support it (from Express clan); It is very hard to debug it; It is confusing; etc. Worst thing is that Windows services made is C# is neither hard to create nor it is (too) hard to debug. You just need to do things your way.

First thing to keep in mind is that service is just simple Windows Form application. There is no reason why you need to debug it any differently. Just go into properties and set Program.cs (or App.cs, as I like to name it) to be startup object. When code starts running, just check for command line parameter (I like to use /Interactive). If that parameter is present DO NOT use ServiceBase.Run but start background thread manually.

Other annoying thing about services is installing them via installutil. You do not need to do that either. For install just execute:

ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });

For uninstall it is:

ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });

Of course, that can be done with /Install and /Uninstall parameters on command line.

As far as threading goes there is no escape. You must have your code in different thread in order to run a service. Windows Service Manager will call your OnStart and OnStop methods but it will not wait forever for result. My preferred method is just creating static class with methods Start, Stop and Run. Start gets called from OnStart and it starts a thread that executes private Run method (new Thread(Run)). Stop method gets called from OnStop to kill the party. Yes, there is some signaling needed for properly canceling everything but there is not much more to it.

Jut check sample application and happy background grinding.

Avoiding COMException

Illustration

Imagine following scenario. You are minding your own business and calling some nice COM function. Suddenly you got COMException going on. And it is not your fault. Code is perfect, women are nice and all you have in that line is simple check (e.g. myCom.Show(handle) == NativeMethods.S_OK). To make things worse you get also “The operation was canceled by the user. (Exception from HRESULT: 0x800704C7)” (or something similar).

Ok, you admit to your self, I did press the cancel button. But this function clearly says that it will return code different than S_OK. It never says anything about any exceptions. And can COM even raise exception in my code? Aha! Real culprit must sit in COM interop layer.

Precise dose of googling (or binging) gives source of misery. Some helpful soul in .NET design team decided that COMException will be thrown whenever HRESULT returns anything that is not success. And thus, something that ought to handled by simple conditional statement (formerly known as if) is now trashing our carefully woven code. Fortunately, smart guy from same team gave us antidote in form of PreserveSig attribute. Small dose of said attribute at function call alleviates all issues.

P.S. Normal person would just say to apply PreserveSig attribute to any function whose result code is handled within your code.

P.P.S. Even smarter person would give you the code:

[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
[PreserveSig()]
uint Show([In, Optional] IntPtr hwndOwner);

DD-WRT on WL-330GE

Illustration

I recently bought Asus WL-330GE wireless router. I needed small travel router and I needed to run DD-WRT on it. It seemed like perfect match.

Upgrade to DD-WRT went without a hitch. However, as soon as it booted I noticed that I was getting DHCP address from my hotel’s server instead from router. Quick investigation revealed that there was no WAN port configured. Single port was in my LAN segment and thus it was leaking everything. Solution for that ought to be simple - I just went to Setup -> Networking and changed WAN assignment there. It seemed to work but, as soon as I rebooted router, everything went back to original state. Quite annoying.

Quick googling revealed something that looked quite close to solution but it didn’t work for me. I could get it to work sometime but every time after restart it would put my WAN port into default bridge. Not quite what I wanted.

In order to debug this I executed nvram show after clean install and then I executed it again after everything got working. That gave me delta that I had to apply. And, as far as bridges go, I decided to manually remove eth0 (WAN port) from default bridge.

Final result was this start-up script (Administration > Commands):

brctl delif br0 eth0
nvram set lan_ifnames="eth1"
nvram set wan_ifname="eth0"
nvram set wan_ifname2="eth0"
nvram set wan_ifnames="eth0"
nvram set wanup=0
nvram unset dhcpc_done
nvram commit
udhcpc -i eth0 -p /var/run/udhcpc.pid -s /tmp/udhcpc &

First line just ensures that WAN port is thrown out of bridge. All those nvram lines sort out minor differences. Last line enables DHCP renewal on WAN interface. After startup that should produce bridge state as displayed on picture. Just what I wanted. :)

Only thing that might look funny afterward is that both WLAN and LAN interface have same MAC address. To solve this we need to telnet (or ssh) to machine and execute following commands:

nvram get lan_hwaddr
nvram get wan_hwaddr
nvram get wl0_hwaddr

Each command will give you MAC address of each interface. In my case this was:

lan_hwaddr: __F4:6D:06:94:02:39__
wan_hwaddr: __F4:6D:06:94:02:39__
wl0_hwaddr: __F4:6D:06:94:02:3B__

From that we can interpolate that wan_hwaddr should be F4:6D:06:94:02:3A (just before wireless and just after LAN). Only thing to do now is to enhance our startup script (somewhere BEFORE nvram commit) with:

nvram set wan_hwaddr=F4:6D:06:94:02:3A
nvram set et0macaddr=F4:6D:06:94:02:3A

This game with MAC is not strictly necessary but I like to set it anyhow.

I tested this on build 14896 (recommended for Asus WL-330GE in router database) and on special build 15962 (recommended on forums as stable).

P.S. Next time remember not to take router advice from Windows programmer.

MagiWOL 3.10

Illustration

New version of my take on wake-up calls is up.

Most noticeable change here is having everything accessible over toolbar. In past some options were in hidden menu but that menu is gone now.

Other feature that is long overdue is upgrade functionality within application.

There was also some bug-fixing involved but that stuff is too boring.

Enjoy new version.

Running .NET 3.5 Application on .NET 4 (And Beyond)

Illustration

Few days ago I tried to get my VHD Attach to run on Windows Thin PC.

Initial diagnosis was easy - there was no .NET Framework installed. While there is no way to install .NET Framework 3.5 on Windows Thin PC, .NET Framework 4.0 installs just fine.

Next run gave us another clue with System.IO.FileNotFoundException: Could not load file or assembly 'System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or one of its dependencies. The system cannot find the file specified.

While .NET 4 is backward compatible, Microsoft made intentional decision that it will not run applications made with earlier runtimes. This is not a big issue in Windows 7. There you have 2.0, 3.5 and 4.0 installed side-by-side and each application can pick whatever it needs. However, in Windows Thin PC there is only 2.0 and 4.0. Notice that one version is missing and it was the one I needed.

To make long story short, there is solution that does not involve recompiling. Just add Application Configuration Files (App.config) and put following text into it:

<?xml version="1.0"?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" />
        <supportedRuntime version="v2.0.50727" />
    </startup>
</configuration>

This will tell application to run on .NET 4 if possible and to fallback to 3.5, 3.0 and 2.0 as a backup solution. This might not be most beautiful solution but it is a solution that works.

P.S. Yes, supportedRuntime cannot distinguish between .NET 2.0, 3.0 and 3.5.