Montag, 21. Mai 2012

Limits of COM Interop in Silverlight 5?

You can read a whole lot of posts about the P/Invoke capabilities of Silverlight 5. For some weired reasons I needet to initiatie an OLE Drag and Drop from Silverlight to Visual Basic 6.

I thought to use P/Invoke to call the Ole32.dll DoDragDrop Method, which is used under the hood by equally named WinForms function. It has the following signature:

[DllImport("ole32.dll")]
static extern int DoDragDrop(IDataObject pDataObject, IDropSource pDropSource,
   int dwOKEffect, int[] pdwEffect);

Even though there is a IDataObject Interface in Silverlight it is not the matching one for this COM call. So I decided to marshal it by hand, as well as the IDropSource interface. This is were the trouble starts.

The IDataObject interface looks like this (at least this is what I think is right):

  [ComImport]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  [Guid("0000010E-0000-0000-C000-000000000046")]
  public interface IDataObject
  {
    void GetData([In] ref FORMATETC format, out STGMEDIUM medium);
    void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium);
    [PreserveSig]
    int QueryGetData([In] ref FORMATETC format);
    [PreserveSig]
    int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut);
    void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium,
      bool release);

    IEnumFORMATETC EnumFormatEtc(DATADIR direction);
    [PreserveSig]
    int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection);
    void DUnadvise(int connection);
    [PreserveSig]
    int EnumDAdvise(out IEnumSTATDATA enumAdvise);
  }

As you can see, there are lots of other types and interfaces in there. All in all there are nine further interfaces, six structs and six enumerations needed to merely define this interface. I did all of it, but when I try to run the code I get the following error:



The error message is "Invalid managed / unmanaged type combination" - which is refered to on stack overflow with the solution that the struct layout must be set to sequential. I felt that this was not my problem but I tried it - with no luck.

What makes me wonder is the second part of the error message. It tells us that marshalling from and to COM interface pointer is not supported. As far as I can see this is a limitation of P/Invoke in Silverlight - but I can find no reference elsewehere on the net. All P/Invoke samples call rather simple functions that accept only primitive data types.

Anyway I have uploaded the whole source code so anyone who feels lucky today can give it a try...

Freitag, 27. April 2012

Silverlight DataBinding und NULL

I recently noticed that, if you have an ObservableCollection<Something> in Silverlight bound to an ItemsControl with an explicit DataTemplate attached to it, that if you insert a NULL reference in to the collection you get the following error message:


Zeile: 1587
Fehler: Unhandled Error in Silverlight Application
Code: 4004   
Category: ManagedRuntimeError      
Message: System.Collections.Generic.KeyNotFoundException: Der angegebene Schlüssel war nicht im Wörterbuch angegeben.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at System.Windows.ResourceManagerWrapper.GetResourceForUri(Uri xamlUri, Type componentType)
    



While this seems cryptic in the first place, it still makes perfectly sense: for the type of object you add there is no something in the resource dictionaries to display it - hence the error.

The ugly thing is, that the error causes Silverlight to crash completely. A registered ApplicationUnhandledException handler just setting e.Handled = true does not seem to help either. So if you add something to a bound collection always check for null.

Maybe this saves some of you some time...

Cheers,
Tobias.

Samstag, 31. März 2012

Team Build Anpassungen

Besser spät als nie - hier die Demos aus meinem Vortrag zum Thema TFS Team Build Anpassungen, für alle die selber Anpassungen planen.

Bei Fragen fragen - ansonsten happy builiding :-)

Build Workflow Demos

Montag, 30. Januar 2012

Closures in C#

Closures kommen eigentlich auch den funktionalen Programmiersprachen. Einige Konzepte solcher Sprachen haben in C# einzug gehalten, und durch Lambda-Expressions zum Beispiel auch die Closures.

Hier ein kleines Beispiel:

public void DoSomething(int i)
{
  var x = 2 * i;
}

private Action GetAction(int initial)
{
  var i = 2 * initial;
  return () => DoSomething(i);
}

public void CallAction()
{
  Action a = GetAction(5);
  a();
}

Die Variable i in GetAction ist lokal und wird im Lambda-Ausdruck () => DoSomething(i) festgehalten. Beim Aufruf der Action in CallAction ist i eigentlich schon gar nicht mehr im Scope. Wegen des Closures kann der Delegat trotzdem darauf zugreifen.

Soweit so gut. Jetzt ein kleines Beispiel wo mich dieses Feature ein kleine Debugging-Session gekostet hat. Dazu ein vereinfachtes Beispiel:

private List<Action> actions = new List<Action>();

private void CreateActions()
{
  for (int i = 0; i < 10; i++)
  {
    var param = i * 2;
    AddAction(() => DoSomething(param));
  }
}

private void AddAction(Action a)
{
  if (!actions.Contains(a))
    actions.Add(a);
}

public void DoSomething(object i)
{
  var x = 2 * (int)i;
}

Jetzt werden die Actions zunächst in einer Liste gespeichert, aber nur falls die gleiche Action nicht schon enthalten ist.

Die Preisfrage lautet: wie viele Einträge enthält die Liste actions nach der Ausführung von CreateActions?

Die richtige Antwort ist: 10. Denn obwohl der Ausdruck () => DoSomething(param) immer gleich aussieht, ist es durch den Closure jedesmal eine andere Action, weshalb die Bedingung !actions.Contains(a) niemals greift.

Bei folgender Varianten ist die Menge der Varianten in der Liste gleich 1:

private void CreateActions2()
{
  for (int i = 0; i < 10; i++)
  {
    AddAction(() => DoSomething(1));
  }
}

Soweit klar, hier wird ja kein Element aus dem externen Scope eingeschlossen.
Aber auch diese Variante liefert einen Eintrag in der Liste actions:

private object context = 1;
private void CreateActions3()
{
  for (int i = 0; i < 10; i++)
  {
    context = 2 * i;
    AddAction(() => DoSomething(context));
  }
}

Hier ist die Variable context ein Referenztyp und das Closure schlíeßt nur die Addresse der Variablen mit ein.
Wenn man es mal so einfach hinschreibt, eigentlich logisch :-)

Donnerstag, 5. Januar 2012

A BranchCreatedEvent and TFS Extensibility

There are many extension and integration points in TFS. A very fine thing is the event system. We can hook into this either by defining a web service that gets called by the TFS - this works fine with WCF. Or we define a server side plugin for the event we are interested in, by implementing a class that implements the ISubscriber interface and deploying this into TFS.

Most documentation found on the web is written for TFS 2010 but as of now everything is running well with my TFS vNetxt installation.

The BranchCreatedEvent
There are a lot of useful events, with TFS vNext the list still got longer compared to version 2010. I wanted to do something, everytime a branch is created, so i was looking for a BranchCreatedEvent. But surprise: there is no such event, neither in TFS 2010 nor in TFS vNext. A suggested solution was to create a service that polls for new branches. I am not happy with this approach, but there seems to be no other way. So I started thinking about where to put my polling service.
One opportunity was to create a long running WCF service. I discarded that, because I was not sure wehter the service would restart once the App Pool was recycled. Another option was to write a custom Windows Service. Regarding this solution I was concerned that I would end up with a new services for each new requirement. So I thougth to implement a plugin based service to have on spot to add new features.

A plugin  based Task Scheduler for TFS  
So I wanted to have a task scheduler that could be extented thorugh plugins. And surprise again, there is already such a thing in TFS, called the TFS Job Agent - the thing that is also responsible for initiating the event processing. So all I had to was to find out how to implement a plugin for this agent.

Implementing a custom TFS Job Agent Job
Information about how to accomplish this was a little harder to find. This one put me on track, and other nice information can be found here.

Here is how the story goes:

Create a new class library solution and add the follwoing references:
  • Microsoft.TeamFoundation.Client [GAC]
  • Microsoft.TeamFoundation.Common [GAC]
  • Microsoft.TeamFoundation.Framework.Server [C:\Program Files\Microsoft Team Foundation Server Dev11\Application Tier\TFSJobAgent\]
Add a class implementing the ITeamFoundationJobExtension interface:

    public class MyFirstJob : ITeamFoundationJobExtension 
    {
        public TeamFoundationJobExecutionResult Run(TeamFoundationRequestContext requestContext, TeamFoundationJobDefinition jobDefinition, DateTime queueTime, out string resultMessage)
        {
            resultMessage = "Successfuly created my first job";
            return TeamFoundationJobExecutionResult.Succeeded;
        }
    }

The following code is needed to register the job and get it executed every 30 seconds:

var tfsConfigServerUri = new Uri(String.Format("http://localhost:8080/tfs"));
var tfsConfigServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsConfigServerUri);
var service = tfsConfigServer.GetService<ITeamFoundationJobService>();

var definition = new TeamFoundationJobDefinition(
                    new Guid("E5B15F37-1B19-4014-B354-B6CA3DA908E7"),
                    "My First Job",
                    "Lab.TFSJob.FirstTry.MyFirstJob",
                    null,
                    TeamFoundationJobEnabledState.Enabled);

var schedule = new TeamFoundationJobSchedule(new DateTime(2012, 1, 5, 9, 0, 0), 30);
definition.Schedule.Add(schedule);
                
service.UpdateJob(definition);

To queue the job initially you can add the following line:

var Result = service.QueueJobNow(definition, false);

The dll must be deployed to the %ProgramFiles%\Microsoft Team Foundation Server Dev11\Application Tier\TFSJobAgent\plugins\ folder. After this the job agent service must be restarted once, otherwise the assembly will not be loaded. You can debug your job by attaching to the TFSJobAgent.exe process on the TFS machine.

Now only some logic to check wether there are new branches between two polls and you are done!

Enjoy!