T4 Debugging – Under The Hood
Hi - my name is Tim Malone and I'm a developer in Visual Studio, working on T4. I began my time at Microsoft in the summer of 2010 as in intern working on what would eventually become the new "Debug T4 Template" feature in Visual Studio 2012 (described here). I'd like to expand a bit more on how we engineered this feature, specifically in regard to one of the improvements we've made to it for Visual Studio 2012 Update 1 and 2.
While previously a process for debugging a misbehaving template existed, it was not intuitive and somewhat of a hassle (requiring multiple instances of Visual Studio). Our intent was to remedy this situation and we approached it with two primary goals in mind
- make the ability to debug a template easily accessible and
- reproduce template behavior identical to that of the template running as usual in Visual Studio.
Unfortunately, there are cases where those goals are at odds with one another. T4 templates currently run within the Visual Studio process confined to their own AppDomain. However, the Visual Studio debugger needs a process to attach to, and asking it to debug itself clearly doesn’t make much sense! So we needed an external process for hosting template execution when debugging a template which, short of spinning up an entire second Visual Studio, brings with it some challenges in ensuring identical behavior in this new environment.
For simple templates - ones that aren't host-specific - there are unlikely to be any differences in behavior. Host-specific templates, though, particularly those using the Visual Studio automation object (or DTE) can find themselves very aware of these differences. Two specific issues we ran into were Visual Studio being busy with user interaction during calls made from our hosting process to the DTE and casts of COM objects not getting translated into the proper QueryInterface call after going through the .NET Remoting boundary across processes. Neither of these could occur on a template running inside Visual Studio, yet they would reproduce when attempting to debug the following template:
<#@ template hostspecific="true" language="C#" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ output extension=".txt" #>
EnvDTE.DTE dte = ((IServiceProvider)Host).GetService(typeof(SDTE)) as EnvDTE.DTE;
EnvDTE.ProjectItem templateItem = dte.Solution.FindProjectItem(Host.TemplateFile);
foreach (EnvDTE.ProjectItem projectItem in templateItem.ContainingProject.ProjectItems)
#><#= projectItem.Name #><#
This was an important scenario to us as many useful templates make heavy use of these features such as the templates packaged with Entity Framework and the T4Toolbox. We were able to solve the first by ensuring the template generation was initiated on its own STA thread. The second issue required us to intercept the call to obtain the DTE using a custom proxy in the host process and switch to COM remoting instead of marshaling via .NET remoting.
OK, now you’ve probably learned more than you ever thought you wanted to know about how T4 debugging works! We hope you’ll find these improvements to T4 let you have a great experience debugging a wide range of templates with Visual Studio 2012 Update 1 and 2.