1、简介
能够动态执行 C# 代码是一件很酷的功能,比如,我们可以在控制台中输入一行 C# 代码,然后程序自动编译并执行这一行代码,将结果显示给我们。这差不多就是一个最简单的 C# 代码解释器了。
动态执行 C# 代码又是一件很有用的功能,比如,我们可以将某些代码写在某个文件之中,由程序集在执行时进行加载,改变这些代码不用中止程序,当程序再次加载这些代码时,就自动执行的是新代码了。
下面,我将在写一个简单C# 代码解释器,然后将在 C# 代码解释器之中加入动态代码与解释器环境间的动态交互机制,来演示一个很好很强大的应用。
2、简单的 C# 代码解释器
关于如何动态执行 C# 代码在 Jailu.Net 的《如何用C#动态编译、执行代码》一文中讲述的很清晰。采用该文所述方式写一个 C# 代码解释器:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Reflection;
usingSystem.Globalization;
usingMicrosoft.CSharp;
usingSystem.CodeDom;
usingSystem.CodeDom.Compiler;
usingSystem.Text;
usingSystem.IO;
usingSystem.Xml;
namespaceTest
{
classProgram
{
staticvoidMain(string[]args)
{
Console.Write(">>");
Stringcmd;
Contextcxt=newContext();
while((cmd=Console.ReadLine().Trim())!="exit")
{
if(!String.IsNullOrEmpty(cmd))
{
Console.WriteLine();
cxt.Invoke(cmd);
}
Console.Write("/n>>");
}
}
}
publicclassContext
{
publicCSharpCodeProviderCodeProvider
{get;set;}
publicIDictionary<String,Assembly>Assemblys
{get;set;}
publicContext()
{
CodeProvider=newCSharpCodeProvider(newDictionary<string,string>()
{
{"CompilerVersion","v3.5"}});
Assemblys=newDictionary<String,Assembly>();
Assembly[]al=AppDomain.CurrentDomain.GetAssemblies();
foreach(Assemblyainal)
{
AddAssembly(a);
}
AppDomain.CurrentDomain.AssemblyLoad+=newAssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
}
privatevoidAddAssembly(Assemblya)
{
if(a!=null)
{
Assemblys.Add(a.FullName,a);
}
}
voidCurrentDomain_AssemblyLoad(objectsender,AssemblyLoadEventArgsargs)
{
Assemblya=args.LoadedAssembly;
if(!Assemblys.ContainsKey(a.FullName))
{
AddAssembly(a);
}
}
publicCompilerParametersCreateCompilerParameters()
{
CompilerParameterscp=newCompilerParameters();
cp.GenerateExecutable=false;
cp.GenerateInMemory=true;
if(Assemblys!=null)
{
foreach(AssemblyainAssemblys.Values)
{
cp.ReferencedAssemblies.Add(a.Location);
}
}
returncp;
}
publicvoidInvoke(Stringcmd)
{
StringinputCmdString=cmd.Trim();
if(String.IsNullOrEmpty(inputCmdString))return;
StringfullCmd=BuildFullCmd(inputCmdString);
CompilerResultscr=CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(),fullCmd);
if(cr.Errors.HasErrors)
{
BooleanrecompileSwitch=true;
foreach(CompilerErrorerrincr.Errors)
{
//CS0201:Onlyassignment,call,increment,decrement,andnewobjectexpressionscanbe
//usedasastatement
if(!err.ErrorNumber.Equals("CS0201"))
{
recompileSwitch=false;
break;
}
}
//重新编译
if(recompileSwitch)
{
StringdynaName="TempArg_Dynamic_"+DateTime.Now.Ticks.ToString();
inputCmdString=String.Format("var{0}=",dynaName)+inputCmdString;
inputCmdString+=";/nSystem.Console.WriteLine("+dynaName+");";
fullCmd=BuildFullCmd(inputCmdString);
cr=CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(),fullCmd);
}
if(cr.Errors.HasErrors)
{
Console.WriteLine("编译错误:");
foreach(CompilerErrorerrincr.Errors)
{
Console.WriteLine(err.ErrorNumber);
Console.WriteLine(err.ErrorText);
}
return;
}
}
Assemblyassem=cr.CompiledAssembly;
ObjectdynamicObject=assem.CreateInstance("Test.DynamicClass");
Typet=assem.GetType("Test.DynamicClass");
MethodInfominfo=t.GetMethod("MethodInstance");
minfo.Invoke(dynamicObject,null);
}
privateStringBuildFullCmd(StringinputCmdString)
{
StringfullCmd=String.Empty;
fullCmd+=@"
namespaceTest
{
publicclassDynamicClass
{
publicvoidMethodInstance()
{
"+inputCmdString+@";
}
}
}";
returnfullCmd;
}
}
}
编译执行后就得到一个傻傻的 C# 代码解析器,也可以当一个简单的计算器用:
3、解释器与所解释的代码之间进行变量交互
如果将所解释的代码中的某些变量储存下来,供给以后的代码用,这一解释器的功能又会强大很多。假设这类变量名称以$打头,如:
$myblogname = “http://xiaotie.cnblogs.com”
将在解释器环境中定义(如果该变量未存在)或赋值于(如果该变量已存在)一个名为 myblogname
的字符串变量,指向字符串“http://xiaotie.cnblogs.com”。
而,System.Console.WriteLine($myblogname)则取出并打印出字符串该变量所引用的。
简单说来,也就是让所解释的代码中能够初始化并引用解释器中的变量。
如何实现呢?这是本文的重点。
首先,在 Context 类中定义一个SortedDictionary储存变量,并提供索引访问:
BuildFullCmd方法改变为:
privateStringBuildFullCmd(StringinputCmdString)
{
StringfullCmd=String.Empty;
fullCmd+=@"
usingTest;
publicclassDynamicClass
{
privateContextm_context;
publicvoidMethodInstance(Contextcontext)
{
m_context=context;
"+inputCmdString+@";
}
}";
returnfullCmd;
}
这样,在动态生成的对象中,便可以引用Context对象。
对于inputCmdString 中未定义的外部变量,在第一次遇见时将$argname替换为一个随机生成的内部变量,在代码的最后,将这个内部变量储存在 Context 中。
虽然通过 (Context[argname].GetType())(Context[argname]) 便可引用外部变量 $argname,但是这样引用赋值时,编译器会报错。解决这个问题需要一个新的类:
将inputCmdString中的外部变量$argname统一替换为(new ObjectHelper
<m_context[“argname”].GetType()> (m_context, “argname”)).Obj"
即可实现在动态代码中对已定义外部变量的引用。
上述对inputCmdString的预处理代码为:
Regexre;
//处理未初始化的环境变量
re=newRegex(@"^(/$)(/w)+");
if(inputCmdString!=null)
{
Matchm=re.Match(inputCmdString);
if(m!=null&&m.Length>1)
{
StringoutArgName=inputCmdString.Substring(m.Index,m.Length).Substring(1);
if(this[outArgName]==null)
{
StringinnerArgName="TempArg_"+outArgName;
inputCmdString="var"+inputCmdString.Replace("$"+outArgName,innerArgName);
inputCmdString+=";m_context[/""+outArgName+"/"]="+innerArgName+";";
}
}
}
//处理其它环境变量
re=newRegex(@"(/$)(/w)+");
IDictionary<String,String>ArgsList=newDictionary<String,String>();
if(inputCmdString!=null)
{
MatchCollectionmc=re.Matches(inputCmdString);
if(mc!=null)
{
foreach(Matchminmc)
{
if(m.Length>1)
{
StringoutArgName=inputCmdString.Substring(m.Index,m.Length).Substring(1);
if(!ArgsList.ContainsKey(outArgName))
{
Objectobj=this[outArgName];
if(obj==null)thrownewException("不存在环境变量"+outArgName);
StringinnerArgName=String.Format(@"(newObjectHelper<{0}>(m_context,""{1}"")).Obj",obj.GetType(),outArgName);
ArgsList.Add(outArgName,innerArgName);
}
}
}
}
foreach(StringoutArginArgsList.Keys)
{
inputCmdString=inputCmdString.Replace("$"+outArg,ArgsList[outArg]);
}
}
这里做了个简化,即定义外部变量的格式必须为 $argname = value,其中 $argname 必须在行首。
这样,对于:$myblogname = "http://xiaotie.cnblogs.com". 因为 myblogname 变量不存在,被解析为:
var TempArg_myblogname = "http://xiaotie.cnblogs.com";
m_context["myblogname"]=TempArg_myblogname;;
定义后,当再出现 $myblogname,则被解析为 (new ObjectHelper<System.String>(m_context,"myblogname")).Obj;
看看实际执行情况:
完整代码于此下载。
4、一个很好很强大的应用—---打入.Net 程序内部,看看其执行情况。
采用上面的方法改进了 OrcShell(OrcShell详情见我前面的随笔: 实现简单的CSharpShell -- OrcShell)。新版 OrcShell 程序于此下载(需要.Net 3.5)。基本上是一个可用的 小型 .Net Framework Shell 了,可以动态的查看、创建、执行 .Net 的类型了。不过,自动提示与完成功能还没有做,使用起来还是较不方便的。
help 指令可以查看常用指令列表:
lsc 列出当前命名空间中的类型和下属命名空间。格式: lsc [name]
dirc 同 lsc
cdc 改变当前的命名空间,格式: cdc [.|..|name]
my 查看全部变量。格式:my。可通过$ArgName来引用变量。
alias 查看全部别名。格式:alias
use 添加命名空间。格式: use [namespace]
unuse 移除命名空间。格式:unuse [namespace]
import 导入程序集,有两种导入方式: "import -f [fullpath]","import [partname]"
分享到:
相关推荐
提供对C#代码生成器和代码编译器的实例的访问。如果要动态生成VB代码,可以使用VBCodeProvider。 CreateCompiler():获取编译器的实例。 二、ICodeCompiler 定义用于调用源代码编译的接口或使用指定编译器的CodeDOM...
可以直接在文本框里输入C#测试代码并执行,用于代码片段的运行测试,或通过简单地编码获取运行结果,需要.net frameword 4.0支持,原创小程序,比较简陋,需要Visual Studio 2010以上
提供对C#代码生成器和代码编译器的实例的访问。如果要动态生成VB代码,可以使用VBCodeProvider。CreateCompiler():获取编译器的实例。定义用于调用源代码编译的接口或使用指定编译器的CodeDOM树。每种编译方法都...
基于CodeDom 实现的字符串执行基于CodeDom 实现的字符串执行基于CodeDom 实现的字符串执行
一、开源项目 Javascript .NET ...示例代码: 代码如下:using Noesis.Javascript; using System; using System.Collections.Generic; namespace JsCSharp { class Program { static void Main(string
C#代码动态编译、动态执行、动态调试[参照].pdf
前几天看到一篇关于.net动态编译的文章 ...在此基础上我做了一些封装,为使调用更加简单,并增加了对动态代码调试的支持,相同代码只编译一次的支持,代码改动自动重新编译,代码引用文件的自动加载和手工加载等功能。 ...
动态执行字符串的c#代码; 另外还实现了动态调用c++ dll的功能,供参考
在编写C#程序的时候,有时我们需要动态生成一些代码并执行。然而C#不像JavaScript有一个Eval函数,可以动态的执行代码。所有这些功能都要我们自己去完成
C#动态执行与编译,实现了动态编译并执行指定类(代码)的函数的方法。
动态执行C#的代码,不需要使用VS。 可用于一些简单的任务执行。
javascript函数中执行C#代码中的函数
c# 动态编译代码 执行脚本代码 关键字还会变色 // debug it step by step (F10, F11) using System; // for Console.WriteLine using System.Windows.Forms; // for MessageBox.Show, ... using System....
本文实例讲述了C#定时器实现自动执行的方法。分享给大家供大家参考。具体实现方法如下: 代码如下: //下面讲一个打开窗体定时执行按钮的东西 private void Form1_Load(object sender, EventArgs e) { System.Timers...
C#动态编译执行代码,脚本执行程序案例
可以直接在文本框里输入C#测试代码并执行,用于代码片段的运行测试,或通过简单地编码获取运行结果,需要.net frameword 4.0支持,原创小程序,比较简陋
c#定时执行源代码
一个很强大的工具, 能将c#代码片段、文件甚至工程直接转换成java代码,并能彻底解决外部引用的DLL问题,最强的是支持c#工程的直接转换,生成的Java代码质量也很不错。软件已破解,去除了未注册版最多只能转换1000行的...
C# 多线程,可执行源代码,需要的可以下载
javascript函数中执行C#代码中的函数.docx