㈠ 详解.NET中的动态编译技术
代码的动态编译并执行是一个 NET平台提供给我们的很强大的工具用以灵活扩展(当然是面对内部开发人员)复杂而无法估算的逻辑 并通过一些额外的代码来扩展我们已有 的应用程序 这在很大程度上给我们提供了另外一种扩展的方式(当然这并不能算是严格意义上的扩展 但至少为我们提供了一种思路) 动态代码执行可以应用在诸如模板生成 外加逻辑扩展等一些场合 一个简单的例子 为了网站那的响应速度 HTML静态页面往往是我们最好的选择 但基于数据驱动的网站往往又很难用静态页面实现 那么将动态页面生成的工作或许就是一个很好的应用场合 另外 对于一些模板的套用 我们同样可以用它来做 另外这本身也是插件编写的方式
最基本的动态编译
Net为我们提供了很强大的支持来实现这一切我们可以去做的基础 主要应用的两个命名空间是 System CodeDom Compiler和Microsoft CSharp或Microsoft VisualBasic 另外还需要用到反射来动态执行你的代码 动态编译并执行代码的原理其实在于将提供的源代码交予CSharpCodeProvider来执行编译(其实和CSC没什么两样) 如果没有任何编译错误 生成的IL代码会被编译成DLL存放于于内存并加载在某个应用程序域(默认为当前)内并通过反射的方式来调用其某个方法或者触发某个事件等 之所以说它是插件编写的一种方式也正是因为与此 我们可以通过预先定义好的借口来组织和扩展我们的程序并将其交还给主程序去触发 一个基本的动态编译并执行代码的步骤包括
· 将要被编译和执行的代码读入并以字符串方式保存
· 声明CSharpCodeProvider对象实例
· 调用CSharpCodeProvider实例的CompileAssemblyFromSource方法编译
· 用反射生成被生成对象的实例(Assembly CreateInstance)
· 调用其方法
以下代码片段包含了完整的编译和执行过程
//get the code to pile string strSourceCode = this txtSource Text; // Create a new CSharpCodePrivoder instance CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); // Sets the runtime piling parameters
by crating a new CompilerParameters instance CompilerParameters objCompilerParameters = new CompilerParameters(); objCompilerParameters ReferencedAssemblies Add( System dll ); objCompilerParameters ReferencedAssemblies Add( System Windows Forms dll ); objCompilerParameters GenerateInMemory = true; // CompilerResults: Complile the code snippet
by calling a method from the provider CompilerResults cr = objCSharpCodePrivoder
CompileAssemblyFromSource(objCompilerParameters
strSourceCode); if (cr Errors HasErrors) { string strErrorMsg = cr Errors Count ToString() + Errors: ; for (int x = ; x < cr Errors Count; x++) { strErrorMsg = strErrorMsg + Line: + cr Errors[x] Line ToString() + + cr Errors[x] ErrorText; } this txtResult Text = strErrorMsg; MessageBox Show( There were build erros please modify your code
Compiling Error ); return; } // Invoke the method by using Reflection Assembly objAssembly = cr CompiledAssembly; object objClass = objAssembly CreateInstance( Dynamicly HelloWorld ); if (objClass == null) { this txtResult Text = Error: + Couldn t load class ; return; } object[] objCodeParms = new object[ ]; objCodeParms[ ] = Allan ; string strResult = (string)objClass GetType() InvokeMember( GetTime BindingFlags InvokeMethod null objClass objCodeParms); this txtResult Text = strResult;
需要解释的是 这里我们在传递编译参数时设置了GenerateInMemory为true 这表明生成的DLL会被加载在内存中(随后被默认引用入当前应用程序域) 在调用GetTime方法时我们需要加入参数 传递object类型的数组并通过Reflection的InvokeMember来调用 在创建生成的Assembly中的对象实例时 需要注意用到的命名空间是你输入代码的真实命名空间 以下是我们输入的测试代码(为了方便 所有的代码都在外部输入 动态执行时不做调整)
using System; namespace Dynamicly { public class HelloWorld { public string GetTime(string strName) { return Wele + strName +
Check in at + System DateTime Now ToString(); } } }
运行附件中提供的程序 可以很容易得到一下结果
㈡ 如何扩展Visual Studio 的编译时功能
很多时候会想在vs编译的时候自定义一些事情或者动作,
例如:
拷贝生成的文件到特定的目录。
部署程序到测试目录或者环境,例如注册到windows服务,更新GAC等。
根据编译环境生成特定的配置文件(例如web.config) PS: 身在一个复杂环境, 这是我最想要的功能。
自动执行外部exe。
同步DLL和其他资源文件。
1.最简单的自然是用Visual Studio自带的编译事件,这东西使用方便,又是Visual Studio自带的功能,就是功能弱了一点(好吧 其实是很弱)
将项目生成的DLL文件拷贝到特定目录,(如果你想拷贝一整个文件夹 用x; 当然,熟悉命令行的人可以弄出更多的玩法)
如下图所示
2.另外一种比较推荐的方式是自定义编译扩展(可以执行C#代码...功能强大多了), 看下面这个项目文件的最后几句 (项目文件就是 项目名.csproj)
<Import Project="....BuildTasksBuild.tasks" />
<Target Name="BeforeBuild">
<Message Text="Start Automatic Generate Configuration File, Source File: $(ProjectDir)web.config" Importance="high">
</Message>
<ConfigurationEnvironmentTask TemplateFile="$(ProjectDir)web.template.config" TargetFile="$(ProjectDir)web.config" DataSource="$(EnvironmentName).config" />
</Target>
</Project>
这几句话的意思是
1. 包含一个task文件 (该文件包含了ConfigurationEnvironmentTask 的定义,这是一个自定开发的类,其主要作用是根据环境生成web.config文件)
2.输出一行提示信息 Start Automatic..... (该信息将显示在Output Window)
3. 调用ConfigurationEnvironmentTask 并传入一些参数(TemplateFile 等都是自己定义的参数)
Build.tasks的文件内容其实很简单,主要就是说明了这个task定义在哪里 (Build.dll)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="Build.dll" TaskName="Build.ConfigurationEnvironmentTask"/>
</Project>