Tuesday, March 28, 2006

Invoke Custom NAnt Tasks with CruiseControl.NET

Ever wondered how to hook up a custom NAnt task to continuous integration?
Turns out it’s very simple to do, below is my ‘Hello Worldified’ example.

Once the below class is compiled, place it somewhere on your build server.

using System;
using NAnt.Core;
using NAnt.Core.Attributes;

[TaskName ("MyTestTask")]
public class MyTestTask : Task
{
  private string myParameter;

  [TaskAttribute ("myParameter", Required = true)] // Mandatory
  [StringValidator (AllowEmpty = false)] // Validate
  public string MyParameter
  {
    get
    {
      return myParameter;
    }
    set
    {
      myParameter = value;
    }
  }

  protected override void ExecuteTask()
  {
    // Determine build success, show our property...
    Project.Log(Level.Error, MyParameter); // Will fail the build
    Project.Log(Level.Info, MyParameter); // Will log in NAnt Output
    Project.Log(Level.Warning, MyParameter); // Will display warning
  }
}


Make the following changes to your project’s NAnt build file.
This will force NAnt to get a local copy of the DLL, load it into memory and init its attributes…

<copy file="\\MyServer\MyLibraries\MyTestTask.dll" todir="."/>
<loadtasks assembly="MyTestTask.dll"/>
<target name="run-MyTestTask">
  <MyTestTask myParameter="Hello World" />
</target>


Finally, register the new task in your ccnet.config file.

<tasks>
  <nant>
    <targetList>
      <target>run-MyTestTask</target>
    </targetList>
  </nant>
</tasks>


All that is left now, is to change the task so it does something useful =]

Friday, March 24, 2006

C# Partial Function Application

Sriram Krishnan wrote a great article which really blew my mind.
After reading it 327 times, I was left with one question...

public class Lambda
{
  public delegate T Function<T,T1>(params T1[] args);

  public static Function<K,K1> Curry<K,K1>(Function<K,K1> func, params K1[] curriedArgs)
  {
    return delegate(K1[] funcArgs)
    {
      // Create a final array of parameters
      K1[] actualArgs = new K1[funcArgs.Length + curriedArgs.Length];
      curriedArgs.CopyTo(actualArgs, 0);
      funcArgs.CopyTo(actualArgs, curriedArgs.Length );

      // Call the function with our final array of parameters
      return func( actualArgs );
    };
  }
}

public static int Add (params int[] numbers)
{
  int result =0;
  foreach (int i in numbers)
  {
    result += i;
  }
  return result;
}

// Test call...
Lambda.Function<int,int> adder=Lambda.Curry<int,int>(Add,7);
adder(5, 6); // Returns 18


How is the scope of curriedArgs managed?

So, after a bit of research it turns out that the captured variables of anonymous methods are member variables of the class. Hence it stays in scope (in this case with a value of 7), between the anonymous delegate being created and invoked.

This may be cool, but may also lead to problems like this.

None the less, I'm impressed and given the direction of C# in 2.0 it looks like developers are soon going to have to open their eyes to
Functional Programming or perhaps leave it to the F# guys =]

Monday, March 06, 2006

Marshaling Complex Structs

So Microsoft has done a great deal to help with interop but sometimes it gets tricky. For instance, last week I was trying to call a C++ function which expected a semi-complex struct as its parameter...

typedef struct
{
  int serial_number;
  struct in_addr ip_address;
  time_t oldestfile[MAXDRDCHANNELS];
  time_t newestfile[MAXDRDCHANNELS];
  int group[MAXDRDCHANNELS];
  char groupname[MAXDRDCHANNELS][NAMELENGTH];
  char commonname[MAXDRDCHANNELS][NAMELENGTH];
} foo;


After crunching away for a while, the solution was...

private struct Foo
{
  int serialNumber;
  int ipAddress;
  int oldestFile0;
  int oldestFile1;
  int oldestFile2;
  int oldestFile3;
  int newestFile0;
  int newestFile1;
  int newestFile2;
  int newestFile3;
  int group0;
  int group1;
  int group2;
  int group3;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName0;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName1;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName2;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName3;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName0;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName1;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName2;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName3;
}


In this case, I knew the array members of the struct were only ever going to contain 4 elements, so I just had to pass them in as individual variables.

Things got tricky when I was trying to marshal a C++ char array.
You can see by setting the above attribute on the char with an explicit size and type, I got away with it.