编写T4模板进行代码生成无法避免的两个话题:"Assembly Locking"&"Debug"

在这之前,我写了一系列关于代码生成和T4相关的文章,而我现在也试图将T4引入我们自己的开发框架。在实践中遇到了一些问题,也解决了不少问题。如果你也在进行T4相关的开发,相信你也一定会遇到这些问题。为此,特意将这些问题和解决方案与朋友们分享,希望在遇到这些问题的时候少走弯路。本篇文章介绍的是两个重要的话题:程序集锁定和调试。

目录 一、程序集引用导致的编译问题 二、T4引擎对引用程序集的锁定 三、Debugger.Break导致VS 2010的Crash 四、在Debugger.Break之前加上Debugger.Launch

一、程序集引用导致的编译问题

为了让读者对“程序集锁定”,以及由它造成的开发上的不便有一个深刻的认识,我特意写了一个小例子。如右图所示的解决方案包含两个项目:Lib和T4。其中我们的T4项目中定义了一个叫作HelloWorld.tt的模板文件,该文件需要使用到定义在Lib项目中的某个类型。所以,HelloWorld.tt模板文件中需要通过<#@Assembly…#>指令引用Lib项目编译生成的程序集(Artech.T4Template.Lib.dll)。

如果你看过我上一篇文章,你应该知道我们至少具有解决T4模板的程序集引用的五种方案,在这里我们采用的是VS宏的解决方案,即将引用程序集文件的路径设置成通过$(SolutionDir)表示的解决方案目录的相对路径。HelloWorld.tt定义如下,引用的程序集路径为Lib项目在Debug模式下编译生成的目录($(SolutionDir)Lib\Bin\Debug\)。

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly name="$(SolutionDir)Lib\Bin\Debug\Artech.T4Template.Lib.dll" #>
using System;
public class HelloWord
{
    static void Main()
    {    
        <# foreach( var person in Artech.T4Template.HelloWorldHelper.GetPersons())
        {#>
        Console.WriteLine("Hello, {0}!", "<#=person#>");    
        <# } #>
    }
}

当你保存该T4模板,T4引擎将触发并进行代码生成工作,但是此时如果你试图编译被引用(实际上是生成的程序集被引用)的Lib项目,将会出现如下所示的编译错误。错误信息为:“Unable to copy file "obj\Debug\Artech.T4Template.Lib.dll" to "bin\Debug\Artech.T4Template.Lib.dll". The process cannot access the file 'bin\Debug\Artech.T4Template.Lib.dll' because it is being used by another process.”,即之前生成的程序集正在被使用,所以不能将生成的程序集拷贝到编译目标目录下。

二、T4引擎对引用程序集的锁定

实际上这个程序集的使用者正是T4引擎。出于提高性能考虑,T4引擎在进行基于代码生成的模板转换(Template Transformation)的时候,会始终重用同一个AppDomain。由于该AppDomain不会自动卸载,这就会导致该AppDomain始终锁定所有被它加载的程序集。如果我们需要释放程序集,我们不得不重启VS。但是,对于T4模板的开发调试阶段,这种通过重新启动VS的方式去释放程序集以确保我们的项目能够成功编译是不能接受的。

那么,是否有一种解决方案既能够确保T4引擎能够进行正常的模板转换,又能避免它强行锁定引用程序集呢?如果你采用T4 ToolBox,你可以通过<#@ VolatileAssembly…#>这个指令轻松地解决这个问题。下面的T4模板中,我们将通过<#@Assembly…#>指令的程序集引用方式替换成了<#@ VolatileAssembly…#>(<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor"  name="$(SolutionDir)Lib\Bin\Debug\Artech.T4Template.Lib.dll" #>),我们的Lib项目在任何时候都可以自由地编译。

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor"  name="$(SolutionDir)Lib\Bin\Debug\Artech.T4Template.Lib.dll" #>
using System;
public class HelloWord
{
    static void Main()
    {    
        <# foreach( var person in Artech.T4Template.HelloWorldHelper.GetPersons())
        {#>
        Console.WriteLine("Hello, {0}!", "<#=person#>");    
        <# } #>
    }
}

<#@ VolatileAssembly…#>的实现原理其实挺简单的,就是在加载的时候并不是直接加载指定的源程序集,而是创建一个新的程序集拷贝。

三、Debugger.Break导致VS 2010的Crash

VS和一些T4编辑器虽然给了基本的智能感知支持,但是在绝大部分我们相当于在编写纯文本的脚本,所以对于一些比较复杂的模板转换逻辑,我们需要通过Debug的方式去发现一些无法避免的问题。关于T4模板的Debug,你Google一下会搜出一大堆。在这些“大众化”的Debug解决方案中都包含两点:

  • 在<#@ template…#>指令中将debug属性设置为true;
  • 在需要设置断点的地方执行Debugger.Break方案

按照这两点,我们改写了我们的T4模板,在foreach语句之前加上<# Debugger.Break(); #>,并通过<#@import…#>指令导入System.Diagnostics命名空间。我不知道在VS 2008下这种解决方案是否可行,但是如果你使用的是VS 2010,这肯定会导致整个VS的崩溃。当你保存TT文件的时候,如右图所示的对话框弹出来,随之伴随整个VS的Crash。

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor"  name="$(SolutionDir)Lib\Bin\Debug\Artech.T4Template.Lib.dll" #>
<#@ import namespace="System.Diagnostics" #>
using System;
public class HelloWord
{
    static void Main()
    {    
        <# Debugger.Break(); #>
        <# foreach( var person in Artech.T4Template.HelloWorldHelper.GetPersons())
        {#>
        Console.WriteLine("Hello, {0}!", "<#=person#>");    
        <# } #>
    }
}

四、在Debugger.Break之前加上Debugger.Launch

为了避免Debugger.Break导致的VS崩溃,只需要在之前多加一句代码即可,既Debugger.Launch。为此我们对我们的T4模板略加修改

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor"  name="$(SolutionDir)Lib\Bin\Debug\Artech.T4Template.Lib.dll" #>
<#@ import namespace="System.Diagnostics" #>
using System;
public class HelloWord
{
    static void Main()
    {    
        <# 
        Debugger.Launch();
        Debugger.Break();
        foreach( var person in Artech.T4Template.HelloWorldHelper.GetPersons())
        {#>
            Console.WriteLine("Hello, {0}!", "<#=person#>");    
        <#} #>
    }
}

现在如果你保存该TT文件,VS会弹出如下一个对话框让你选在是否进行Debug。如果需要进行Debug,选择“Yes, debug devenv.exe”。

然后创建一个新的VS实例,或者选择已经打开的VS程序进行Debug,这个对话框我们应该很熟悉。最后程序将会执行到我们设置的断点(Debugger.Break),我们就可以像Debug普通托管程序一样对T4模板进行Debug了。实际上,你也可以直接通过Attach进程的方式进行Debug,不过这里的进程就是VS的进程devenv.exe。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

进程、线程、轻量级进程、协程和go中的Goroutine

进程、线程、轻量级进程、协程和go中的Goroutine 进程、线程、轻量级进程、协程和go中的Goroutine 那些事儿电话面试被问到go的协程,曾经的军伟...

4546
来自专栏Golang语言社区

深入Go语言网络库的基础实现

Go语言的出现,让我见到了一门语言把网络编程这件事情给做“正确”了,当然,除了Go语言以外,还有很多语言也把这件事情做”正确”了。我一直坚持着这样的理念——要做...

2787
来自专栏ytkah

finecms栏目文章页seo设置

  finecms栏目页和文章页默认的标题是页面title_二级栏目title_一级栏目title_网站名称(比如:finecms怎么设置标题_finecms二...

2836
来自专栏JetpropelledSnake

Python入门之PyCharm中目录directory与包package的区别

对于Python而言,有一点是要认识明确的,python作为一个相对而言轻量级的,易用的脚本语言(当然其功能并不仅限于此,在此只是讨论该特点),随着程序的增长,...

61912
来自专栏技术墨客

Nodejs学习笔记(1)——安装nodejs

    关于大名鼎鼎的Nodejs是什么就不用再介绍了,他的牛逼之处数都数不完——让javascript称霸全宇宙、将一个只用于前端的编程语言同时可以制霸前后端...

902
来自专栏Golang语言社区

从websocket看go的应用

Go是互联网时代的通用编程语言。这样它就和命令行时代的C语言、图示界面时代的C++、以及互联网早期的Java语言等有不同的侧重。它强调保持自身的精巧和独立,从而...

4208
来自专栏欧阳大哥的轮子

iOS应用程序的脱壳实现原理浅析

对于诸多逆向爱好者来说,给一个app脱壳是一项必做的事情。基于安全性的考虑,苹果对上架到appstore的应用都会进行加密处理,所以如果直接逆向一个从appst...

973
来自专栏大内老A

《WCF技术剖析》博文系列汇总[持续更新中]

近半年以来,一直忙于我的第一本WCF专著《WCF技术剖析(卷1)》的写作,一直无暇管理自己的Blog。在《WCF技术剖析(卷1)》写作期间,对WCF又有了新的感...

1758
来自专栏编程

vs code上配置python的运行环境

vs code上配置python的运行环境 Thanks for reading this. 上个月的时候花了不少功夫在VS code上配置Python的运行环...

4148
来自专栏ChaMd5安全团队

记一次腾讯SDK源代码审计后的CSRF攻击

0x00 前言 初进ChaMd5安全团队,应M姐姐之邀写下这篇技术文章,这篇文章主要讲述本人近期代码审计腾讯的第三方登录SDK包时发现的一个漏洞,阅读这篇文章需...

4046

扫码关注云+社区

领取腾讯云代金券