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.