15 May 2010

Accessing Visual Studio’s Automation API from F# Interactive

Because why anyone would want to write VBA in the Visual Studio macro editor is beyond me. All the bits and pieces below are pretty much known, but I think the overall result is new. For the bewildered (I don’t blame you), what you’ll be able to do is, from F# Interactive, for example enumerating the current projects in the open solution:

> myDTE.Solution.Projects |> Seq.cast<Project> |> Seq.map (fun p -> p.FullName);;
val it : seq<string> =
  seq
    ["C:\Users\Kurt\Projects\FsCheck\doc\FsCheck\FsCheck.fsproj";
     "C:\Users\Kurt\Projects\FsCheck\doc\FsCheck.Examples\FsCheck.Examples.fsproj";     "";
     "C:\Users\Kurt\Projects\FsCheck\doc\FsCheck.Checker\FsCheck.Checker.fsproj";     ...]

You get access to the DTE top level automation object of the Visual Studio instance the current FSI is running under. This DTE is a COM interface that basically allows you to do programmatically what you can do using the Visual Studio UI interface – it’s the thing you get access to in the Macro IDE as well. So you can do pretty cool things – like add files, generate code, listen to build events, find dependencies of projects and such.

But…how?

The Macro IDE gives access to the DTE object automagically. But fsi is actually a separate process, that just happens to input and output through Visual Studio. So basically we need to get access to a Visual Studio instance’s DTE object from outside of Visual Studio. Luckily, each instance of Visual Studio registers its DTE object in the Running Object Table (hereafter called ROT) with two keys:

  • As a string of the form “!VisualStudio.<VS version>.0:<processid>”. E.g. for VS2008 with process id 1234: !VisualStudio.9.0:1234
  • As the path to an open solution file, if any.

We’ll use the first option. That means we need to find out the process id of the Visual Studio instance the current FSI instance is running under. FSI is started as a child process of Visual Studio – each Visual Studio has its own FSI.

Let’s tackle each of these in turn – first finding the process id of the current Visual Studio instance, then its DTE object via the ROT.

Step 1: Finding the Visual Studio process id

Not as straightforward as it sounds. System.Diagnostics.Process does not give you direct access to a process’ parent. There are at least two ways to go about it – using the Windows API or, maybe surprisingly, through performance counters. I used the latter because that didn’t require me to do interop.

The code is pretty straightforward – remember the point is that this is being executed in F# interactive:

open System
open System.Diagnostics

let getParentProcess (processId:int) =
    let proc = Process.GetProcessById processId
    let myProcID = new PerformanceCounter("Process", "ID Process", proc.ProcessName)
    let myParentID = new PerformanceCounter("Process", "Creating Process ID", proc.ProcessName)
    myParentID.NextValue() |> int
    
let currentProcess = Process.GetCurrentProcess().Id

let myVS = getParentProcess currentProcess

You should just be able to paste that into an F# Interactive window and myVS will contain the process id of the Visual Studio instance it’s running under. (No doubt this will crash spectacularly when you’re running FSI standalone or something.)

Step 2: Getting the DTE object

This was a bit harder, as we’re now going to have to do some interop with OLE32. Code for this is floating around on the internet if you know where to look – I just translated it to F#. Here’s what we need:

#r "EnvDTE"
#r "EnvDTE80.dll" 
#r "EnvDTE90.dll" 

open EnvDTE
open EnvDTE80
open EnvDTE90
open System.Runtime.InteropServices
open System.Runtime.InteropServices.ComTypes

module Msdev =
    
    [<DllImport("ole32.dll")>]  
    extern int GetRunningObjectTable([<In>]int reserved, [<Out>] IRunningObjectTable& prot)
 
    [<DllImport("ole32.dll")>]  
    extern int CreateBindCtx([<In>]int reserved,  [<Out>]IBindCtx& ppbc)

These two interop functions allow us to enumerate the ROT and get the registered object with the key we want:

let tryFindInRunningObjectTable (name:string) =
    //let result = new Dictionary<_,_>()
    let mutable rot = null
    if Msdev.GetRunningObjectTable(0,&rot) <> 0 then failwith "GetRunningObjectTable failed."
    let mutable monikerEnumerator = null
    rot.EnumRunning(&monikerEnumerator)
    monikerEnumerator.Reset()
    let mutable numFetched = IntPtr.Zero
    let monikers = Array.init<ComTypes.IMoniker> 1 (fun _ -> null)
    let mutable result = None
    while result.IsNone && (monikerEnumerator.Next(1, monikers, numFetched) = 0) do
        let mutable ctx = null
        if Msdev.CreateBindCtx(0, &ctx) <> 0 then failwith "CreateBindCtx failed"
            
        let mutable runningObjectName = null
        monikers.[0].GetDisplayName(ctx, null, &runningObjectName)
        
        if runningObjectName = name then
            let mutable runningObjectVal = null
            if rot.GetObject( monikers.[0], &runningObjectVal) <> 0 then failwith "GetObject failed"
            result <- Some runningObjectVal
        
        //result.[runningObjectName] <- runningObjectVal
    result

I won’t go into the virtues of this particular piece of code. Suffice to say it’s an acquired taste. (Bonus points for anyone who can write a function that returns the ROT as a nice, lazy seq<string*obj>. Yes I know it seems easy – but please try and prepare to be frustrated.). So we now have a function that returns the COM object in the ROT based on the name. If you put in the code in comments and change a few bits and pieces, it should be easy to get a function that outputs the ROT completely.

Putting two and two together

Armed with these functions, here’s the DTE object that you can use to call the Automation API:

let getVS2008ROTName id = 
    sprintf "!VisualStudio.DTE.9.0:%i" id
    
let myDTE = (tryFindInRunningObjectTable (getVS2008ROTName myVS) |> Option.get) :?> DTE2

This is for Visual Studio 2008. For 2010, it should be as easy as changing the 9 to 10 in the ROT key string. It’s possible to make this more general if you can come up with a way to check whether the current process is .NET 4 or .NET 3.5 – the former would indicate an FSI running in VS2010, the latter in VS2008.

And that’s it. Have a look around using Intellisense to see what is possible. Also check out the Macro IDE – it has some code examples in VBA that involve the DTE. Some ideas:

  • myDTE.ExecuteCommand lets you execute any command – the things you can bind keyboard shortcuts to in the Options. For example, use ProjectandSolutionContextMenus.Item.MoveDown to move the currently selected item one place down.
  • myDTE.Events to register event handlers for all sort of Visual Studio events. For example, myDTE.Events.BuildEvents.add_OnBuildDone notifies you when a build is done. Nice for post-build steps that need to run after a solution is built (as opposed to after a particular project is built).
  • myDTE.Quit(). The l33t hax0r way to exit Visual Studio. You’ll be the envy of your less tech-savvy colleagues.

If you find an imaginative use for this, please let me know.

Share this post :

09 April 2010

Moving FsCheck to Mercurial

I’ve been using Mercurial as my source control system of choice for a few years now, and I’ve been very  happy with it. The only problem I had was that codeplex, where FsCheck is hosted, only supported SVN. I used Mercurial for daily development, and when a release was imminent I just copied the final snapshot to my local SVN checkout and updated codeplex from there. Needless to say, the SVN repository was out of date and didn’t represent the actual history.

Well, no more! Codeplex now supports Mercurial as well. One email to the kind folks at codeplex support, and they wiped my SVN repository from codeplex and provided me with a shiny new hg repository. Great.

That left me with a problem: since I was used to the hg repository being private, I also dumped some, well, private stuff in it. Before you start imagining things, there is nothing special there – just a few pdfs of papers and such, but nonetheless things I didn’t want to put in a public repository. If only because some authors prefer that they alone control dissemination of their work.

And obviously Mercurial isn’t keen on letting you delete history.

Luckily it has the excellent convert extension. This is usually used to convert SVN, CVS or other repositories to a Mercurial repository. But you can also convert a Mercurial repository to a new one, and rewrite and update history along the way. One of the things the convert extension lets you do is to specify files and folders that should not be migrated. It also lets you remove branches, and even splice a certain set of changesets on a parent in the new repository.

I used the first two features to remove some superfluous history and files. I used the last because I’m used to clone my main repository to a bunch of feature repositories to work on specific features in relative isolation. I have a few of those in progress, but of course since I was converting my main repository to a new one the feature repositories couldn’t be used to push changes back to the new main. This makes them pretty much useless, unless they are converted as well.

So here’s what I did:

  1. Convert the old main repository to the new main, stripping out files and branches along the way.
  2. Clone the new repository for each feature I’m currently working on. Now I have a mirror image of my old folder where the old main and the old feature repositories are – except the changesets in the old feature repositories need to be migrated to the new feature repositories.
  3. Using hg convert’s splice feature, and the excellent TortoiseHg repository explorer to find out the changeset numbers, I then additionally converted only the extra changesets from each old feature repository to its corresponding new feature repository. One thing to keep in mind is that hg convert uses the .hg/shamap file in the new repository to keep track of which changesets from the old repository it has already converted. This file is however not automatically copied when cloning – so I had to manually copy that over from the new main to the new feature repositories – otherwise convert would try to convert all of the changesets again.

Confused yet? Well, here’s my first Prezi to explain it all…

You can check out the result in FsCheck’s source tab – although the main result is that there is now much less friction in my development process.

Share this post : Technet! del.icio.us it! del.iri.ous! digg it! dotnetkicks it! reddit! technorati!

15 February 2010

FsCheck 0.6.3 for Visual Studio 2010 RC and F# February 2010 CTP

A new release of F# means a new release of FsCheck.

  • Removed the dependency on the F# PowerPack. This makes FsCheck easier to use, especially since the PowerPack is now  separately distributed. Besides, it was only used to pretty print functions.
  • Cleaned up reflective generators a bit.
  • All projects are upgraded to build with Visual Studio 2010 now. For backwards compatibility, I’ve added an FsCheck2008.sln solution which allows you to build in Visual Studio 2008 with the February 2010 CTP. You’ll get a warning from MSBuild saying that ToolsVersion 4.0 is not supported and that it is treating the build as a 3.5 build. Good – that’s exactly what you want. FsCheck is still targeting .NET3.5 until .NET4 is out.

Enjoy.

11 February 2010

Visug presentation in Leuven, Belgium on 8 Feb 2010

Thanks to everyone who attended – hope you got something out of it (besides the free food and drinks…).

Here are the slides I used, as well as the (completed) demo project. There are some extra slides and some extra parts to the demo which I didn’t present. Apparently the whole thing will also appear on MSDN chopsticks, at which point I’ll update this post with the link.

22 December 2009

On Understanding Data Abstraction, Revisited

I’m not in the habit of posting links, but this essay by Willam R. Cook on the difference between ADTs and objects made such an excellent and humbling read... Humbling because in my arrogance I had assumed that I knew the difference – after all this is basic stuff. Excellent because despite the humbling experience, it’s a very enjoyable read.

Enjoy.

Share this post :

08 December 2009

How to get hg-git working with Mercurial without installing Python on Windows

Sorry for the horrendous search keyword-driven title, but I’ve been trying to get this to work for ages.

Here’s a step by step guide, along with some potential pitfalls.

1) Download TortoiseHg. I used 0.9.1.1. with Mercurial 1.4.1. Do not use the windows Mercurial command line only distribution – we’ll need to modify a zip file later and for some reason the zip file with the command line distribution cannot be modified with either WinRar, Winzip or 7-zip.

2) Install TortoiseHg as usual.

3) Download dulwich 0.4.0.

4) Add the directory called ‘dulwich’ inside the dulwich download (the one with the file __init__.py in it) to the library.zip file in the TortoiseHg folder (normally just C: \Program Files\TortoiseHg) using your favorite zip utility.

5) Download the tip of an up to date hg-git fork. I used http://bitbucket.org/gwik/hg-git/.

6) Put the hg-git directory in the TortoiseHg directory.

7) Add the following lines to your Mercurial.ini file:

bookmarks =

hggit = C:\Program Files\TortoiseHg\hg-git\hggit

That’s it. I finally managed to clone the ClojureClr repository with the above install:

> hg clone git://github.com/richhickey/clojure-clr.git
destination directory: clojure-clr.git
importing Hg objects into Git
Counting objects: 4001, done.
Compressing objects: 100% (1816/1816), done.
Total 4001 (delta 3153), reused 2774 (delta 2148)
importing Git objects into Hg
at:   0/307
at: 100/307
at: 200/307
at: 300/307
updating to branch default
313 files updated, 0 files merged, 0 files removed, 0 files unresolved

And with this we’re back to functional programming languages ;)

I didn’t test this any further.

Good luck!

Technorati: , , ,

Share this post :

26 October 2009

FsCheck 0.6.2: F# October 2009 CTP/2010 Beta 2

FsCheck is updated for the new F# release! Conversion was fairly straightforward, some name changes and indentation changes mostly. Furthermore, the following smaller changes: 

  • A nice set of new default generators contributed by Howard Mansell. FsCheck can now generate any Enum instance, as well as generators for DateTime, arrays, positive and negative ints and so on. Check out the Arbitrary.fs file for a complete overview.
  • Some more examples
  • registerInstances and overwriteInstances now throws an exception if it cannot find any instances in the given type.

Happy FsChecking!