Tuesday 28 July 2009

GAC, Assembly Resolution , and all that Jazz

Microsoft seem to be big fans of the GAC - which is fine for them as the platform provider, but personally I prefer to keep my code away from the GAC as far as possible. A few pet reasons, including some still-healing wounds from COM/COM+ (dll hell), and the fact that most of what I write needs to get deployed to a server-farm by tools like "robocopy", which is not the friend of GAC.

This difference raised its head recently when I was trying to get the VS2008 tooling working for protobuf-net. The way it works is that as part of the VS registration (which is done in a VS-specific registry hive) you tell it how to get your package - and a valid choice here is to specify the codebase. This is the option I took, since it introduces no extra configuration details, other than the mandatory VS hooks.

So that is fine and dandy, dandy and fine. But problems soon happened. In particular; since I already had dlls for protobuf-net and protogen, I wanted to ship those in the install directory, and have my VS package simply reference them. Which turned out to be not-so-simple...

Assembly resolution

The first thing to break down was assembly resolution; my package would simply refuse to find the dlls. Because of the different paths etc, your package's isntall directory is pretty unimportant to VS. If your required dlls are in the GAC, then it will find it - but I really didn't want to do that. But there are other tricks...

A little-known feature in .NET is that there is an event (AppDomain.AssemblyResolve) that fires when it can't find an assembly. By handling this event we can load it manually (for example: from the file system, download-on-demand, or from an embedded resource):

image

This works fine in most applications, but not VS; this event simply never fires within a VS package. Oh well. Another feasible option is to use reflection to load the dll (probably acceptable if the API is very shallow):

 

image

Not pretty, but it'll work - except now you just move the problem downstream; any dlls that our library needs will be missing too, with all the same problems plus some more issues associated with the "load context" (depending also on the whole LoadFile / LoadFrom choice)... oh dear.

Processes vs AppDomains

Our original problem was that files that existed locally weren't being resolved due to the assembly resolution paths of VS; but what if the second assembly was actually an exe? In the case of protogen.exe this was fine. So we could launch a new Process (setting the working path, etc); not elegant, but very effective - in particular it bypasses all the issues of the "load context" inherited from VS; we have our own process, thanks very much.

But there is an alternative... a second AppDomain; this lives within the same process, but is isolated (in terms of assemblies, configuration, etc), and can be unloaded. And neatly, you can execute an assemblies entry-point very easily (conveniently passing arguments as a string[] rather than having to pack them down as a single string for Process):

image

This is actually quite a cute way of launching a sub-process (for .NET code).

Processes within AppDomains within Packages

As it happens, in my case my child dll itself has to launch a Process during execution ("protoc.exe"); normally this is fine, but VS was soon causing problems again; for some curious reason (presumably linked to security evidence), when using ExecuteAssembly this inner Process.Start would always flash a console frame (even when told not to). Oddly, if I instead launch a Process from my Package, and that Process launches a Process, then this doesn't happen. A bit of an undesirable quirk, but as a consequence this multi-level Process tree is currently how the VS package does its work.

But in general you can use ExecuteAssembly as a neat alternative to a Process.

Summary

VS packages have "fun" written all over them... maybe my borked installer was right all along ;-p

Saturday 25 July 2009

Busy busy busy

Just a quickie...

Apologies if you've sent me a patch, bug, or other piece of work over the last week or so; I will get to it, but things are a bit hectic at the moment; "crunch" time on a project for work, plus lots of non-work things keeping me very busy.

So I'm not ignoring you; I'm just completely maxed. Hopefully normal service will resume shortly ;-p

Friday 17 July 2009

Things not to do with an installer...

First - important point: this bork never left my laptop; this is not a problem in the package I mentioned yesterday!

Can you spot the face-palm in the following (that installs the protobuf-net Custom Tool into Visual Studio 2008):

image

That will install just fine, and the Custom Tool will work. And then you uninstall it.... oh dear. It helps if you know that {FAE04EC1-301F-11D3-BF4B-00C04F79EFBC} is Visual Studio's nickname for "C#". Did you notice that innocent little "DeleteAtUninstall" flag? Somehow it get set to true (it certainly wasn't deliberate).

Oh well, perhaps I can get by without all the inbuilt tools! Or maybe I need to rebuild my crippled registry... Fortunately this only impacted my laptop before I spotted it (I do test things, honestly!).

Still - it could have been worse; a few levels higher up and I could have completely broken Visual Studio (or potentially windows, but I would hope that the operating system wouldn't let me do something truly stupid, such as deleting HKLM/Software).

The moral of this: when you are writing installers, double check both the install and the uninstall do exactly what you expect. No less, and (perhaps more importantly) no more.

Thursday 16 July 2009

protobuf-net; now with added Orcas

A long running gripe with protobuf-net is that while the code generation (from .proto) works, it isn't easy to integrate into your development process. Well, wait no longer! I'm very happy to announce that protobuf-net now has Visual Studio 2008 support

(important note: this does not include the "Express" editions of Visual Studio).

A big vote of thanks goes to Shaun Cooley, who kindly created me the initial "cut" which I've extended. I did try this before, but I drowned in the complexity that is Visual Studio extensibility.

So what do I get?

Initially, the installer includes a "Custom Tool" (registered against the .proto extension), and an item template (not currently registered by the tooling):

The Custom Tool

This means you can add new text file called "mymessage.proto", and it will automatically start running C# (see below) code-generation on that .proto - writing a .cs file to match your .proto. This includes things like error-processing, so if you have errors in your .proto you can click on them and it will take you to the right place in the file. It will also add the protobuf-net reference if you don't already have it.

proto-vs

For existing files, you can set the Custom Tool manually; the string you need is "ProtoBufTool" (original, huh?).

At the moment, protobuf-net lets you pass a number of options on the command line. This same functionality is available in the IDE too - just (ab)use the "Custom Tool Namespace" - it accepts a semi-colon delimited set of arguments (the first being reserved for the actual namespace to use if your file doesn't specify a package) - i.e. if you use the string ";fixCase", it will apply C#-style case fixups (so "name" becomes "Name", etc). The arguments are exactly the same as on the command line tool (protogen.exe).

And if you think this is a bit ugly; I have "plans" (see below).

The template

The installer also includes (in the loosest sense) a custom item template, so you can use Add -> New Item to get a new .proto file with a suitable skeleton (including things like the project's namespace, etc). It doesn't currently install this template - and I'll be honest; this is simply because I haven't yet had time to crack the installer specifics of this (it is.... fiddly).

To install this manually:

  • copy the BrotoBuf.zip file from (protobuf-net install dir)\ItemTemplates\csharp\data\1033 into (VS install location - Microsoft Visual Studio 9.0)\Common7\IDE\ItemTemplates\CSharp\Data\1033
  • from the VS2008 command prompt, execute "devenv /installvstemplates" (it may take a minute or two)

Enough; where is it?

The installer is available from the project home page.

What next

The support at the moment is limited; I have a number of things I want to do:

  • Add VB support! (the main problem here is detecting the project's language; the code generation itself works)
  • Register the ItemTemplate during installation
  • Move a lot of the command-line switches into custom .proto attributes
  • Fix the annoying UTF8 BOM issue once and for all...
  • etc

I hope you find it useful; working with VS extensibility has certainly been a different set of challenges - I'll blog about some of those separately.