Wednesday, January 30, 2008

Building C# projects with Windows Workflow

wfbuild After reading a post about using Windows Workflow to build an SMTP server, I started thinking of something I could use WF for outside the world of business processes. For a long while I've also been planning on automating my build processes for various projects I've got on the go. I then realised I could combine the two and develop an automated build system with WF. At this point, if you have any experience of automated builds, you're probably thinking "Why the hell don't you use NAnt or MSBuild like any normal person?", which is a fair point.

But using WF has a few advantages. First, as far as I'm aware NAnt and MSBuild don't provide a graphical designer for their build projects (I'm quite happy to be proved wrong on this point) whereas I was able to knock together a designer based on the Microsoft example pretty quickly. Second, I just wanted to use WF for something not related to business processes. It is a very flexible technology and I'm not sure people have realised its potential. I blame the name, which makes everyone assume WF has something to do with workflow, which for many people has a very particular meaning. Third, a WF solution shares the main advantage of NAnt and MSBuild, the build scripts can be simple XML files. To be fair it does currently depend on Visual Studio being installed, although I'm sure I could build projects using the C# compiler directly (probably taking advantage of the MSBuild API in fact!).

So after a couple of hours work I had a few activities, a designer and a command-line application to run the workflow XOML (see my previous post) and a working build system. OK, I still have some tidying up to do but I was amazed at what I'd managed to achieve. After some more work I'll dump my code somewhere for people to play with.

Another take on this may be to use the WF designer to generate NAnt and MSBuild XML files directly. My understanding is that WF provides support for using your own custom DSLs. Something else to look at...

Update – You can now download the source code here. The only warranty that comes with it is that “it works on my machine”. In fact it’s quite possible it won’t work on your machine since some of the locations are hardcoded. There are four projects, one is the workflow designer control, then there is a GUI application that uses that control, there’s an assembly full of activities (some of which could be useful in other workflows) and finally there is a console application that can be used to run your build from the command line.

Monday, January 28, 2008

Running a XOML workflow from the command line

I needed to be able to execute a XOML workflow from the command line so I wrote this little app that takes a XOML file as an argument and executes it. This is part of something that should become a nice example of using WF to build applications for non-programmers, i.e. using activities as building blocks like Lego. More on that to follow, perhaps.

using System;
using System.IO;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

namespace WFBuild
{
  class Program
  {
    private static AutoResetEvent waitHandle;
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("WFBuild");
        if (args.Length != 1)
        {
          Console.WriteLine("Usage");
          Console.WriteLine("WFBuild <XOML file to execute>");
        }
        else
        {
          WorkflowRuntime workflowRuntime = new WorkflowRuntime();
          workflowRuntime.StartRuntime();
          workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
          workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

          FileStream stream = new FileStream(args[0], FileMode.Open, FileAccess.Read);
          XmlTextReader reader = new XmlTextReader(stream);
          WorkflowInstance instance = workflowRuntime.CreateWorkflow(reader);
          waitHandle = new AutoResetEvent(false);
          instance.Start();
          Console.WriteLine("Executing...");
          waitHandle.WaitOne();
        }
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
      catch (WorkflowValidationFailedException exp)
      {
        // catch workflow failed validation exception and show validation errors
        Console.WriteLine("Validation failed:");
        foreach (ValidationError error in exp.Errors)
        {
          Console.WriteLine(error.ToString());
        }
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
    }

    static void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
    {
      Console.WriteLine("Workflow terminated");
      Console.WriteLine(e.Exception.Message);
      waitHandle.Set();
    }

    static void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
      Console.WriteLine("Workflow completed");
      waitHandle.Set();
    }
  }
}

Update - I've updated the code to show validation errors

Thursday, January 24, 2008

Google wants me

OK, not quite but I did receive an email the other day saying

Google Sydney Engineering division are visiting London to talk about R&D in Australia. You have either been referred by a friend or current Google employee as someone who may be interested in joining us for a special G'Day Google event at Google UK in London.  The evening will include plenty of food and beverages, Aussie style of course...blah...blah... If you are also interested in what is happening in Google London, this will also be a chance to meet local Googlers who can give you an insight into our local engineering divisions.

This intrigued me. First, who had referred me? I don't know anyone at Google but I do know a few people in Australia, so I guess it must have been one of them (perhaps my boss is trying to give me a gentle hint!).

And why are Google Australia coming to the UK to try and recruit people? Do they think all our current bar staff want to return home and start working for Google? Are a lot of the Ozzies over here IT people? Or are they after getting UK residents to move over there?

Anyway, I was interested in going along, not because I think I'd have any chance of working for Google since my predominantly Windows background wouldn't fit in with their open source Linux and Python development. I also doubt I'd manage to refrain from laughing when asked one of their typical interview questions, presuming I got that far. But I was interested in knowing what they do and the kind of people who work there and they did mention beer... OK, they didn't mention beer but they did mention Australian beverages which I can only assume means beer.

They provided a link to their site to get more details and to RSVP. Except it doesn't work and hasn't done for two days. I'm wondering if this is some kind of test in itself, to weed out those people not clever enough to figure out how to find their web page.

Update - After a little research it would appear the URL provided on their email is a link to one of Google's internal subdomains. Clearly not all of Google's employees are geniuses.

Sunday, January 20, 2008

What are spammers up to?

The Random Pub Finder has a form for suggesting pubs to be added to the database. For a long time, we've had spammers posting lots of dodgy URLs via the form, which is not unexpected, since they think they will get added directly to the database. In fact, the suggestions just get emailed to me, in part to reduce the risk of dodgy URLs ending up on the site and also because I've never got round to automating the process.

Lately the spam has been increasing but not in any sensible way. I'm getting things like "Bill, Man Hatten", "Halo, Moscow" and "Diesel, Washington" coming through. It's left me scratching my head as to what these guys are up to. Why would they be submitting this stuff? It isn't linking through to anything and doesn't seem to contain anything of interest. Are we just the victims of an exceedingly stupid spammer?

Friday, January 18, 2008

Using var in C# 3.0

I hadn't really read much about the new var keyword in C# 3.0 but it had troubled me. I had assumed it was like the var keyword in JScript and meant C# was turning into a late-bound dynamic language which I really wasn't keen on. As I mentioned previously, this can cause problems in JScript.NET when the compiler can't infer the type.

So I was pleased to see that the IL generated by this is still the same as if strongly-typed variables were used and if the compiler can't infer the type from the code, it won't compile. But there's still something of a problem. The addition of this new keyword means that coders can now just declare every local variable using it (fortunately not member variables) which will lead to less clear code. Explicit declarations are good, there's no question what is going on.

And I just know in a few months/years time I'm going to be looking at some code I have to maintain and I will be swearing under my breath and asking why some pillock declared everything using var.

Friday, January 11, 2008

Great titles don't always lead to lots of readers

Some time ago Nick Bradbury (author of the marvelous FeedDemon which is now free!) said that the best way to increase feed readership is to use great titles. Whilst this is undoubtedly true if your blog has a lot of subscribers, the fact is most of us Z-list bloggers don't actually have many people who subscribe to our blogs. According to Google Sitemaps I currently have two people signed up via Google Reader, which I'm pretty astounded by given my tendency to ramble on incoherently about random subjects.

The fact is 90% of my hits come from Google and Google doesn't care about great titles, it cares about matching up search terms to relevant content. One good way to drive traffic to your site is to write about things that interests searchers and that other people aren't writing about, given that if some A-list blogger does write about it you'll get shoved down the search result list. For me, SonicWall, HTML tick marks and Vista sound problems are the most popular subjects...

Anyway, the things to have in your titles are the words people are likely to use when they are searching for something related to your post. If it's in the title, Google will consider the text more important than if it's just in the body of the post somewhere. So forget great titles, have relevant titles.

Tuesday, January 08, 2008

JScript.NET performance

JScript.NET doesn't really seem to have caught on too much. The reasons are clear, there is no IDE support for it in Visual Studio.NET and if the company that developed the language doesn't want to provide an IDE, who else is likely to?

But since Metastorm BPM uses JScript.NET as its primary interface to the world of .NET I have to use it on a fairly regular basis. Many moons ago I had a look at the IL code produced from compiling JScript.NET and was horrified to see it was all late bound calls. Funnily enough the primary reason somebody had decided to use JScript.NET rather than JScript was to try to improve the performance of 8000 lines of server-side JScript code (why he had ever thought that 8000 lines of JScript code would not have performance problems I don't know). Anyway, compiling it did improve the performance somewhat but probably not so much as if the IL code produced had not been late bound.

So now several years later, whilst preparing some course notes, I thought I'd take a look at what the IL code looked like these days in .NET 2. And the results are worth noting I think.

Say we have two methods as follows

        public static function DoSomethingFast() : Object
        {
            var test = new StringBuilder();
            test.Append("a");
            test.Append("a");

            return test.ToString();
        }   

        public static function DoSomethingSlow() : Object
        {
            var test = "Hello world";

            var test = new StringBuilder();
            test.Append("a");
            test.Append("a");

            return test.ToString();
        }

After compiling that with jsc.exe, the IL produced for the first method looks like this

.method public static object DoSomethingFast() cil managed
{
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Text.StringBuilder builder,
        [1] object obj2)
    L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0005: stloc.0
    L_0006: ldloc.0
    L_0007: ldstr "a"
    L_000c: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0011: pop
    L_0012: ldloc.0
    L_0013: ldstr "a"
    L_0018: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_001d: pop
    L_001e: ldloc.0
    L_001f: callvirt instance string [mscorlib]System.Text.StringBuilder::ToString()
    L_0024: stloc.1
    L_0025: br L_002a
    L_002a: ldloc.1
    L_002b: ret
}

Looking at that lifted my spirits since it looks like the kind of code you'd get from a compiled C# method, no late-binding at all. Now on to the second method, which has only one difference from the first one.

.method public static object DoSomethingSlow() cil managed
{
    .maxstack 11
    .locals init (
        [0] object obj2,
        [1] object obj3,
        [2] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding,
        [3] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding2,
        [4] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding3,
        [5] object obj4,
        [6] object obj5,
        [7] object obj6)
    L_0000: ldstr "Append"
    L_0005: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_000a: stloc.2
    L_000b: ldstr "Append"
    L_0010: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0015: stloc.3
    L_0016: ldstr "ToString"
    L_001b: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0020: stloc.s binding3
    L_0022: ldstr "Hello world"
    L_0027: stloc.0
    L_0028: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_002d: stloc.0
    L_002e: ldloc.2
    L_002f: dup
    L_0030: ldloc.0
    L_0031: ldtoken TestSpeed.TestClass
    L_0036: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_003b: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0040: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_0045: ldc.i4.1
    L_0046: newarr object
    L_004b: dup
    L_004c: ldc.i4.0
    L_004d: ldstr "a"
    L_0052: stelem.ref
    L_0053: ldc.i4.0
    L_0054: ldc.i4.0
    L_0055: ldtoken TestSpeed.TestClass
    L_005a: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_005f: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0064: pop
    L_0065: ldloc.3
    L_0066: dup
    L_0067: ldloc.0
    L_0068: ldtoken TestSpeed.TestClass
    L_006d: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0072: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0077: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_007c: ldc.i4.1
    L_007d: newarr object
    L_0082: dup
    L_0083: ldc.i4.0
    L_0084: ldstr "a"
    L_0089: stelem.ref
    L_008a: ldc.i4.0
    L_008b: ldc.i4.0
    L_008c: ldtoken TestSpeed.TestClass
    L_0091: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0096: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_009b: pop
    L_009c: ldloc.0
    L_009d: stloc.s obj4
    L_009f: ldloc.s obj4
    L_00a1: isinst object
    L_00a6: dup
    L_00a7: stloc.s obj5
    L_00a9: brfalse L_00ba
    L_00ae: ldloc.s obj5
    L_00b0: callvirt instance string [mscorlib]System.Object::ToString()
    L_00b5: br L_00ee
    L_00ba: ldloc.s obj4
    L_00bc: stloc.s obj6
    L_00be: ldloc.s binding3
    L_00c0: dup
    L_00c1: ldloc.s obj6
    L_00c3: ldtoken TestSpeed.TestClass
    L_00c8: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00cd: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00d2: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_00d7: ldc.i4.0
    L_00d8: newarr object
    L_00dd: ldc.i4.0
    L_00de: ldc.i4.0
    L_00df: ldtoken TestSpeed.TestClass
    L_00e4: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00e9: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00ee: stloc.1
    L_00ef: br L_00f4
    L_00f4: ldloc.1
    L_00f5: ret
}

Suddenly things look terribly messy again, with lots of late-binding goo all over the place. The reason is pretty straight forward I think. In the first case, the compiler can infer the type of the test variable, whereas in the second case it can't because the variable is used to hold two different types. It does however mean that minor changes to code can lead to it being compiled completely differently without any warning, which is a bit of a worry.

Thursday, January 03, 2008

Writing software for mutant women

For me, the best part of "Dead Ringers" (the David Cronenberg film, not the mildly amusing impressions show) is the gynaecological instruments developed by one of the freaky medical twins. He's so out of his head on drugs that he thinks the women who are coming to his surgery are all mutants so he has to produce some terrifying new instruments to deal with them.

What brought this to mind was seeing the film again for the first time in about 15 years and also reading one of my Christmas books, "Why Software Sucks". Although seemingly unrelated, I think there is some common ground. As Davis Platt points out many times in his book, the reason why software sucks is because us software developers don't understand our users. We may not be out of our minds on drugs (at least not all the time) but our lack of empathy with our users mean we continually produce software that doesn't meet their needs.

I'm mostly in agreement with him here, although I'm not sure if he's having a go at developers directly or more generally companies that produce software. As a rule, the actual developers don't always have much of a say in what gets pushed out the door. Product management, sales, marketing, CTOs, QA and a host of other people have a pretty big input into what goes into software and how it should look and feel, whereas developers are often just following orders.

There is a reason I'm not too keen on the book, it does tend to re-hash a lot of what Alan Cooper wrote in "About Face". To be fair he is aiming at a different audience, normal people, who are pretty unlikely to have read Cooper's book. Me being a geek and not a normal person have read Cooper's book and Platt's book. If you're a developer, I'd recommend "About Face", if you're just a normal person forced to use software, I'd recommend "Why Software Sucks".

And as a developer I'm trying my best to write software that is not for mutant women.

Monday, December 31, 2007

Vista usage

I read this post on Mike Taulty's blog, which seemed to show widespread adoption of Vista

http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2007/12/06/10012.aspx

which led me to this post from Craig Murphy, again showing widespread adoption of Vista

http://www.craigmurphy.com/blog/?cat=27

which led me to this slightly more cynical view from Thomas Lee

http://tfl09.blogspot.com/2007/10/vista-adoption-numbers.html

which led to the original post from Alex Eckelberry

http://hackersblog.itproportal.com/?p=768

which again shows a less than rosy picture for Vista adoption. My guess is the first two are getting much higher numbers because they are MS-based development blogs, which are read by MS geeks, who are the people most likely to be using Vista. From my small sample space (from the Random Pub Finder) in December, of the 89% visitors who are using Windows, 7.6% are using Vista, meaning it's just overtaken Windows 2000. Personally I think this is a much more realistic figure. Most non-techy people I know don't know nor care what Vista is and continue to use XP quite happily. And I think they may be quite sensible, what are the compelling reasons to upgrade? I'm yet to find them. Under the hood there may be lots of changes, but on the surface there's not a lot to see.

os

Thursday, December 20, 2007

Calling a COM object on a remote machine with no type library

I'd always assumed to connect to a remote COM object required the client code to reference the type library for the COM object and for the type library to be registered on the client machine as well, but somebody showed me a way of doing this today that appears to avoid the need for either of these.

      Type type = Type.GetTypeFromCLSID(new Guid("{12345678-1234-1234-1234-123456789012}"), server, true);
      
      Object obj = Activator.CreateInstance(type);
      
      return (string)obj.GetType().InvokeMember("Method", 
        BindingFlags.InvokeMethod, null, objTest, new object[1]{param});

Admittedly it uses reflection, which will probably be slower than a direct call to the interface, but given it's a call over the wire, this probably won't be the slowest part of the call anyway. And it sure makes deployment easier.

Wednesday, December 12, 2007

Solving the Vista "Failed to play test tone" sound problem

It's almost a year since I installed Vista on my work machine. I sorted out most of the glitches a long time ago but I've not had any sound the whole time, which I kind of learned to live with. I'd pretty much given up trying to fix it, thinking I'd wait for SP1, which I was sure would sort out the problem.

So I saw SP1 RC1 was available and installed it and excitedly tried my sound. Still nothing... OK I thought, now is the time to figure out what the problem is. I tried out some of the SysInternals tools to see if they could provide some kind of useful info, basically to see if the audio subsystem was trying to access a registry key or file that didn't exist or it didn't have access to (I'd already decided it probably wasn't a driver problem since a completely different sound card had exactly the same problem).

I got nowhere with that so I moved onto my next idea, trying to figure out what was going on in the audio log files. If you change the registry key HKLM\System\CurrentControlSet\Control\WMI\AutoLogger\Audio\Start value from 0 to 1, log files get generated in %WINDIR%\system32\LogFiles\Audio (called something like AudioSrv.Evm.001). But I have no idea how to view these log files, they are in some kind of proprietary binary format. So I'd thought I'd harass Larry Osterman. I don't know the guy, but he's the only member of the Windows Audio team who I'm aware of that blogs. But before I had a chance to make a fool of myself I saw a link on one of his comments by someone who'd solved their own Vista sound problem for themselves.

There were two nuggets in there. First up was the suggestion to debug the issue using the WinAudio sample from the Vista SDK. That way, you find out exactly what error message the audio APIs are returning. The second nugget was the solution to his particular problem, which was to delete the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\DefaultAccessPermission. I didn't think it would fix my machine, since I'd followed quite a few other suggestions without success, but I tried it anyway. It didn't work. Or so I thought, until I rebooted and my speakers miraculously started making noise.

So I'm a happy camper, but there's a few things I don't understand. Why is the logging file format not documented? Perhaps it is, but it should be more widely publicised if it is. For that matter, why's it not just a text file? And why doesn't the audio stack just write to the event log like most other Windows components when something goes wrong? If I'd seen something in there saying access was denied to some COM component I'd have a clue what the problem was. 

And see this below? That's what I'm listening to. Yes, music!

Currently listening to Something , Blue Jay Way (Tran by The Beatles from the album Love

Addendum for non-technical readers

  • From the Windows Start menu, choose 'Run...'.
  • Type regedit in the 'Open' text box.
  • Navigate to 'Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole
  • Delete the registry key 'DefaultAccessPermission'. If it isn't there, chances are this isn't the problem causing your lack of sound.
  • Beware that fiddling with your registry can cause system problems if you modify important settings.

Sunday, December 02, 2007

The last CRT

Some time ago our TV died, trying to switch it on led to a light coming on for a few seconds, then it turned itself off. My guess was the power supply had died or the screen itself had died (for a few days the picture displayed hadn't been the right size).I did think for a second about getting it fixed but then thought it would be as cheap to buy a new TV. After all, the price of TVs has been dropping for a while. And my brother would no doubt be able to fix it, so the TV wouldn't go to waste. But then I actually had a look for a TV and was unable to find any CRT TVs anywhere. I did find some LCD TVs that were pretty cheap but there were a few problems.

  • Most of them were too big to fit into our living room without removing shelving. Not everybody wants a 32"+ screen.
  • Call me a Luddite but the picture quality of LCD screens doesn't seem that good. OK, when high definition TV becomes ubiquitous, the picture quality may be pretty good, but for a 28" screen I don't really need high definition TV.
  • Most LCD screens seem very fragile. They look like they can get knocked over very easily. Having a five year old wandering around with an LCD screen in the room is asking for trouble.

I know I'm not really getting into the new technology here but I think CRT TVs are great. The picture quality, even on very cheap models, is pretty damn good. The technology is proven. The prices are now great, if you can actually find one. Finally I discovered probably the last CRT TV available, from Argos.

Some of you may be thinking "it's analogue, you won't be able to receive anything on it in a couple of years", which is a fair point*. But we have a DVR with FreeView so that's not an issue for us. Also, there is a digital version available for a few quid more. Funnily enough, delivery on this TV took a very long time. Apparently the demand for it is pretty high, so perhaps I'm not the only person who thinks CRT TVs are actually pretty good.

* Digital TV itself seems something of a con. OK, there are more (mostly crap) channels available, although chances are you'll need a better aerial to receive them but why turn off the analogue signal?  Obvious really, it forces everybody to upgrade their kit so equipment manufacturers get some additional income and the government can sell of the old analogue bandwidth to get some extra revenue. Everybody's a winner, except the public. Expect a switch to HD only channels in a few years time.

Wednesday, November 28, 2007

What can I do with an LCD screen and a COM port?

There is something particularly gratifying about writing software that drives other bits of hardware. I guess it's the feeling of controlling something in the real world, rather than just making square boxes do things in the virtual world of PCs. .NET 2 made controlling COM ports easy through the SerialPort class, and there is a library available for controlling the USB ports. Then I saw this article about driving LCD screens from .NET, again via the serial port.

So now it's all very easy to control hardware from my preferred development environment, I just have to find a useful application to develop.

One possibility I've been mulling over for a while is some software to monitor electricity usage. Apparently there are electricity meters out there that will do this for you, showing pretty graphs etc, but the electricity companies aren't keen on installing them. Presumably this is due to the cost and also perhaps because it might encourage people to use less power, which isn't really in their interests. You can buy a clamp meter to non-invasively measure power usage but the ones that actually come with any kind of data interface are damn expensive so until I can find a cheap one, this particular idea is on the back burner.

Saturday, November 24, 2007

Boethius's Wheel

"It's my belief that history is a wheel. 'Inconstancy is my very essence,' says the wheel. Rise up on my spokes if you like but don't complain when you're cast back down into the depths. Good times pass away, but then so do the bad. Mutability is our tragedy, but it's also our hope. The worst of times, like the best, are always passing away."

So Boethius wrote about 1500 years ago*. And as far as I can see, it still holds true today, on a personal level and on a national/global level. Gordon Brown may have said "no more boom and bust" but the fact is he always had very little control over the economic cycle. It's human nature to over extend ourselves during the good times, thus driving the boom to unsustainable levels then doing the opposite during the downturn. And it looks to me like the 10 years of boom is just about to come to an end. Interesting times.

*I'm not particularly well read, I picked up on this quote when a tramp shouted it at Tony Wilson in "24 Hour Party People". It's then alluded to when Wilson is forced to present "Wheel of Fortune" in order to make ends meet.

Buy Nothing Day

Today was Buy Nothing Day, which I think is a top idea. I've been aware of this for a few years, but today I only heard about it after I'd popped out to buy some batteries. They are rechargeable so I don't feel too bad about it.

Friday, November 23, 2007

What is click fraud?

There are plenty of examples of obvious click fraud, people setting up dubious websites with advertising then using nefarious means to get clicks on those ads, companies clicking on their competitors ads to drive up their competitors' costs. And these kind of examples of fraud are quite possibly detectable by the companies who run the ads. But there seems to be a whole other bunch of other stuff that may also be considered fraudulent, or at the very least not valid clicks.

For instance, my daughter can now use Google to search for stuff on the internet but she has no idea of what the difference is between a sponsored ad and a normal search result and since she can't read everything, she just clicks on whatever she feels like. And, until she works out how to use my credit card, she won't be buying any Charlie and Lola gear from one of the sites she visits.

Or what about me when I see an ad for some dubious debt company or some get rich quick scheme and I click on the link purely to cost them some money?

Or what about those websites that try to disguise their ads as just another link on their site?

These may or may not be fraudulent, but I'm fairly certain Google and the other ad companies can't tell they aren't real punters who are actually interested in the website's offerings. Presumably companies will continue to pay up until they aren't getting a return on their outlay, which is definitely one of the advantages of online ads. And that being the case, it seems like one great way to stop these dodgy companies from advertising is actually to just click on their ads...

Tuesday, November 20, 2007

Posting errors to a website

The exception logger I've written is one of the most popular pages on my proper website but I've never been completely happy with it. I've always wanted to be able to get the details of an unhandled exception from the user of my software to me easily. I added support for sending an email some time ago, but that's never felt like a good solution since the user must configure their SMTP connection before sending off the email. Then I read a website posting suggesting a much better solution, post the error to a website, then email the error to the relevant address from the web server. That way, email only needs to be configured in one place, on the web server. Admittedly if the user isn't hooked up to the internet it won't work, but sending an email via SMTP will also generally fail in that scenario.

The exception logger code shows how to do the client-side code, but here it is anyway.

    private void LogToWebsite(string error)
    {
      Uri uri = new Uri("http://www.yourwebsite.com/SubmitBug.aspx");
      HttpWebRequest httpWebRequest    = (HttpWebRequest) WebRequest.Create(uri);
      httpWebRequest.Method    = "POST";
      httpWebRequest.ContentType = "application/x-www-form-urlencoded";

      Encoding encoding = Encoding.Default;

      string parameters = "msg=" HttpUtility.UrlEncode(error);

      // get length of request (may well be a better way to do this)
      MemoryStream memStream = new MemoryStream();
      StreamWriter streamWriter = new StreamWriter(memStream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Flush();
      httpWebRequest.ContentLength = memStream.Length;
      streamWriter.Close();

      Stream stream = httpWebRequest.GetRequestStream();
      streamWriter = new StreamWriter(stream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Close();

      using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
      using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
      {
        streamReader.ReadToEnd();
      }
    }

A couple of points to make here. I'm using POST rather than GET, because GETs have a maximum length of data that can be passed (8K if I remember correctly). The error is URL encoded, so that ampersands don't truncate the posted error message. The code to work out the length of the content may not be required, you may be able to get away without passing it at all and there may be a better way of calculating it. I did it that way when I was fiddling around with the encoding type, since different encoding types would naturally produce different lengths of content. Finally reading back the response data isn't really necessary since we don't do anything with it.

Now onto the server-side code, which is pretty simple.

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!string.IsNullOrEmpty(Request.Params["msg"]))
    {
      SmtpMail.SmtpServer = "localhost";
      SmtpMail.Send("mail@yourwebsite.com", "error@yourwebsite.com", 
        "Bug report", Request.Params["msg"]);
    }
  }

The only thing to note here is that because we are using POST, the data is passed in via the Params property, rather than the QueryString property.

Monday, November 19, 2007

Developing custom state activities

I've banged on before about how extensible Windows Workflow is (assuming you can work out how to extend it), but there is at least one place where it isn't quite as extensible as one would like, state activities.

I was trying to create a custom state activity that contained a state initialization activity and an event driven activity and various other activities within those. The first problem I encountered was when I inherited from StateActivity. Trying to add any activities at design-time failed. This seems to be down to the state activity designer. One solution would have been to write my own designer class, but this is complicated by the fact that the standard state designer is internal so can't be inherited from. I guess I could have decompiled the code using Reflector and made the required mods in my own code, but that seemed too much like hard work. So I decided to construct the activity in code instead.

Then I bumped into the second problem. Adding activities in code is pretty straightforward, just do it in the constructor after the call to InitializeComponent, like so.

public UserActivity()
{
  InitializeComponent();
  CanModifyActivities = true;
  try
  {
    // add activities here
  }
  finally
  {
    CanModifyActivities = false;
  }
}

Having said that, if you're doing all your activity construction in code, you can actually get rid of the call to InitializeComponent and get rid of the designer.cs file, since you're not going to need any design-time support.

Anyway that's the theory. In practice, it doesn't quite work out like this, because the StateActivity's validator doesn't like you to add child activities to a state activity. At least this class is public so I could inherit from it, but there aren't really any extensibility points to allow for modification of its behaviour. So I initially wrote my own validator class, that doesn't do anything at all. I later realised that if the validator wasn't going to do anything, I could actually just use ActivityValidator instead. This isn't perfect since it means none of the child activities I've added will be validated, but it works well enough for my scenario.

One further problem (although it isn't an issue for me) is that an end user of your custom state activity won't be able to add their own child activities. I guess the solution to this once again is to write your own designer.

Another related problem is the addition of a set state activity. Since my users can't add one themselves, I have to add one but I don't know the name of the state to transition to until the activity is in use. This was pretty easy to solve, just add a TargetStateName to my activity and pass that value through to my own set state activity.

public string TargetStateName
{
  get
  {
    return setState.TargetStateName;
  }
  set
  {
    setState.TargetStateName = value;
  }
}

My final problem was that the state activity was displayed with all of its child activities visible to the end user, although they are not editable. I still haven't solved this issue, although I suspect the solution will yet again involve writing my own designer class. If only that standard state designer wasn't internal...

Thursday, November 15, 2007

Sharepoint create task workflow error - Not supported exception

If you've tried to create tasks in workflows hosted in Sharepoint using the CreateTask activity you may have come across this error in the Sharepoint logs.

System.NotSupportedException: Specified method is not supported.     at Microsoft.SharePoint.Workflow.SPWorkflowTask.SetWorkflowData(SPListItem task, Hashtable newValues, Boolean ignoreReadOnly)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.UpdateTaskInternal(Guid taskId, SPWorkflowTaskProperties properties, Boolean fSetWorkflowFinalize, Boolean fCreating, HybridDictionary specialPermissions)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.CreateTaskWithContentTypeInternal(Guid taskId, SPWorkflowTaskProperties properties, Boolean useDefaultContentType, SPContentTypeId ctid, HybridDictionary specialPermissions)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.... 

Although saying that, my own searches on the Internet didn't bring up anything, so I may be the only one. Or there may not be many people using workflow in Sharepoint, which is odd given that it's free, which compares very favourably with most workflow products.

But I digress. I thought this would be pretty easy to solve. First up, have a look at the implementation of SPWorkflowTask.SetWorkflowData in Reflector and see what causes it to throw the exception. Which is where things got difficult. Reflector told me the method was obfuscated. I don't know if this is true or if Reflector was getting confused but I left it for a while since I had other things to do. Then I had a thought, perhaps I could still have a look at the code in Reflector if I chose IL as my language of choice. And indeed I could. And this is what the area of interest looked like.

    L_01e8: br.s L_01f0
    L_01ea: newobj instance void [mscorlib]System.NotSupportedException::.ctor()
    L_01ef: throw
    L_01f0: ldloca.s id
    L_01f2: ldsfld valuetype Microsoft.SharePoint.SPContentTypeId       Microsoft.SharePoint.SPBuiltInContentTypeId::WorkflowTask
    L_01f7: call instance bool Microsoft.SharePoint.SPContentTypeId::IsChildOf(valuetype Microsoft.SharePoint.SPContentTypeId)
    L_01fc: brtrue.s L_0200
    L_01fe: br.s L_01ea
    L_0200: ldarg.0

At lines L_01f0 to L_01f7, the method checks to see if the content type is a child of workflow task. If not, it throws the exception. Turns out my workflow.xml file had its TaskListContentTypeId attribute set to 0x0108 (task), rather than 0x010801 (workflow task). I changed that and everything worked, the task was created and I was on my way to workflow nirvana.

It's just a shame that whoever implemented this bit of code didn't use the NotSupportedException that takes an error message as a parameter, then they could have told me what the problem was...

Sunday, November 11, 2007

Pies and Prejudice

One of the sure signs of getting older is listening to Radio 2, not in some kind of ironic post-modern way but because, of all the options available, it seems like the best one. And if you listen to Radio 2 then it's pretty much required to like Stuart Maconie, since it seems like half their output involves him. Fortunately I fall into that category. The man's funny and it's always good to hear a Northern accent on the radio (even if it has been softened, presumably from too many years living in the South).

So when I saw his book 'Pies and Prejudice - In Search of the North' I was interested. When I read the back cover 'A northerner in exile, stateless and confused' I thought yep that's me and promptly bought it (OK I put it on my Amazon wish list and somebody bought it for me several months later).

And it's a great read. He writes like he talks, so if you like his shows you'll like the book. Any Northerner reading it will no doubt be disappointed by his lack of coverage of <insert name of place that is important to you>. I was surprised by the lack of mention of Lancaster, one of the more attractive towns in Lancashire and the Trough of Bowland, Lancashire's best kept secret. I wasn't so surprised by him avoiding Blackburn and Darwen, which even the most rabid Lancastrian would have to admit are dumps. And even when he does cover areas in some depth, anybody with some inside knowledge will probably think he's missed out some important parts.

But covering every little nook and cranny isn't really the point of the book. Maconie is attempting to redress the balance of the coverage of the North. It's not all terraced houses, grim weather, even grimmer food, unemployment and fighting, which is what you may think based on the typical drama based anywhere in the North. And he succeeds in doing that. Unfortunately he may have succeeded but I suspect the people who should read it, the people with those prejudices about the North, quite likely won't be the people buying it. And perhaps that's no bad thing. Do we really want the North to become yet another weekend escape from London, with property prices driven up by bankers who only ever visit every month? Perhaps it's best for the prejudices to remain intact, the people who matter know it's not true. Lets keep it our little secret.