惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

博客园 - Macou

[转]软件标准项目文档 INTEL 汇编指令集 周未钓鱼钓到一只花罗汉,价值大概3000RMB [原创]一个数组整数环绕算法 个人整理的Linux Shell语法速查表(用C语言语法来作对比) [转]MONO之Red Hat5.5企业版 Linux系统ASP.NET环境配置 [转]在Eclipse3.3中安装VE插件 png 文件分析 PNG (Portable Network Graphics) Specification C语言 BMP图片处理 [转]UML中数据流图,用例图,类图,对象图,角色图,活动图,序列图详细讲述保存供参考 中断向量表 [转]程序人生--我已经努力了七年 [转]成功人士的20个优良习惯 [转]ViewState机制由浅入深3 [转]ViewState机制由浅入深2 [转]ViewState机制由浅入深1 [转].NET开源地理信息系统(GIS) [转]60多个精品源码站
[转]与动态执行的C# 代码进行通讯
Macou · 2008-03-02 · via 博客园 - Macou

1、简介

能够动态执行 C# 代码是一件很酷的功能,比如,我们可以在控制台中输入一行 C# 代码,然后程序自动编译并执行这一行代码,将结果显示给我们。这差不多就是一个最简单的 C# 代码解释器了。

动态执行 C# 代码又是一件很有用的功能,比如,我们可以将某些代码写在某个文件之中,由程序集在执行时进行加载,改变这些代码不用中止程序,当程序再次加载这些代码时,就自动执行的是新代码了。

下面,我将在写一个简单C# 代码解释器,然后将在 C# 代码解释器之中加入动态代码与解释器环境间的动态交互机制,来演示一个很好很强大的应用。

2、简单的 C# 代码解释器

关于如何动态执行 C# 代码在 Jailu.Net 的《如何用C#动态编译、执行代码》一文中讲述的很清晰。采用该文所述方式写一个 C# 代码解释器:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;
using Microsoft.CSharp;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Text;
using System.IO;
using System.Xml;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write(">> ");
            String cmd;
            Context cxt = new Context();
            while ((cmd = Console.ReadLine().Trim()) != "exit")
            {
                if (!String.IsNullOrEmpty(cmd))
                {
                    Console.WriteLine();
                    cxt.Invoke(cmd);
                }
                Console.Write("\n>> ");
            }
        }
    }

    public class Context
    {
        public CSharpCodeProvider CodeProvider { get; set; }
        public IDictionary<String, Assembly> Assemblys { get; set; }

        public Context()
        {
            CodeProvider = new CSharpCodeProvider(new Dictionary<string, string>() { "CompilerVersion", "v3.5" } });
            Assemblys = new Dictionary<String, Assembly>();
            Assembly[] al = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly a in al)
            {
                AddAssembly(a);
            }
            AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
        }

        private void AddAssembly(Assembly a)
        {
            if (a != null)
            {
                Assemblys.Add(a.FullName, a);
            }
        }

        void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            Assembly a = args.LoadedAssembly;
            if (!Assemblys.ContainsKey(a.FullName))
            {
                AddAssembly(a);
            }
        }

        public CompilerParameters CreateCompilerParameters()
        {
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            if (Assemblys != null)
            {
                foreach (Assembly a in Assemblys.Values)
                {
                    cp.ReferencedAssemblies.Add(a.Location);
                }
            }
            return cp;
        }

        public void Invoke(String cmd)
        {
            String inputCmdString = cmd.Trim();
            if (String.IsNullOrEmpty(inputCmdString)) return;

            String fullCmd = BuildFullCmd(inputCmdString);

            CompilerResults cr = CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(), fullCmd);

            if (cr.Errors.HasErrors)
            {
                Boolean recompileSwitch = true;

                foreach (CompilerError err in cr.Errors)
                {
                    //CS0201 : Only assignment, call, increment, decrement, and new object expressions can be
                    //used as a statement
                    if (!err.ErrorNumber.Equals("CS0201"))
                    {
                        recompileSwitch = false;
                        break;
                    }
                }

                // 重新编译
                if (recompileSwitch)
                {
                    String dynaName = "TempArg_Dynamic_" + DateTime.Now.Ticks.ToString();
                    inputCmdString = String.Format("  var {0} = ", dynaName) + inputCmdString;
                    inputCmdString += ";\n  System.Console.WriteLine(" + dynaName + ");";

                    fullCmd = BuildFullCmd(inputCmdString);
                    cr = CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(), fullCmd);
                }

                if (cr.Errors.HasErrors)
                {
                    Console.WriteLine("编译错误:");
                    foreach (CompilerError err in cr.Errors)
                    {
                        Console.WriteLine(err.ErrorNumber);
                        Console.WriteLine(err.ErrorText);
                    }

                    return;
                }
            }

            Assembly assem = cr.CompiledAssembly;
            Object dynamicObject = assem.CreateInstance("Test.DynamicClass");
            Type t = assem.GetType("Test.DynamicClass");
            MethodInfo minfo = t.GetMethod("MethodInstance");
            minfo.Invoke(dynamicObject, null);
        }

        private String BuildFullCmd(String inputCmdString)
        {
            String fullCmd = String.Empty;

            fullCmd += @"
                namespace Test 
                { 
                    public class DynamicClass
                    {
                        public void MethodInstance()
                        {
                            " + inputCmdString + @";
                        }
                    }
                }";
            return fullCmd;
        }
    }
}

 编译执行后就得到一个傻傻的 C# 代码解析器,也可以当一个简单的计算器用:

3、解释器与所解释的代码之间进行变量交互

如果将所解释的代码中的某些变量储存下来,供给以后的代码用,这一解释器的功能又会强大很多。假设这类变量名称以$打头,如:

$myblogname = “http://xiaotie.cnblogs.com”

将在解释器环境中定义(如果该变量未存在)或赋值于(如果该变量已存在)一个名为 myblogname 的字符串变量,指向字符串“http://xiaotie.cnblogs.com”。而,System.Console.WriteLine($myblogname)则取出并打印出字符串该变量所引用的。

简单说来,也就是让所解释的代码中能够初始化并引用解释器中的变量。

如何实现呢?这是本文的重点。

首先,在 Context 类中定义一个SortedDictionary储存变量,并提供索引访问:

        public SortedDictionary<String, Object> Instances { get; set; }
        public Object this[String instanceName]
        {
            get
            {
                if (Instances.ContainsKey(instanceName))
                {
                    return Instances[instanceName];
                }
                else
                {
                    return null;
                }
            }
            set
            {
                if (Instances.ContainsKey(instanceName))
                {
                    Instances.Remove(instanceName);
                }
                Instances.Add(instanceName, value);
            }
        }

BuildFullCmd方法改变为:

        private String BuildFullCmd(String inputCmdString)
        {
            String fullCmd = String.Empty;

            fullCmd += @"
                    using Test;

                    public class DynamicClass
                    {
                        private Context m_context;

                        public void MethodInstance(Context context)
                        {
                            m_context = context;
                            " + inputCmdString + @";
                        }
                    }";
            return fullCmd;
        }

这样,在动态生成的对象中,便可以引用Context对象。

对于inputCmdString 中未定义的外部变量,在第一次遇见时将$argname替换为一个随机生成的内部变量,在代码的最后,将这个内部变量储存在 Context 中。

虽然通过 (Context[argname].GetType())(Context[argname]) 便可引用外部变量 $argname,但是这样引用赋值时,编译器会报错。解决这个问题需要一个新的类:

    public class ObjectHelper<T>
    {
        private String m_objName;

        public Context Context { get; private set; }

        public T Obj
        {
            get
            {
                Object obj = Context[m_objName];
                return (T)obj;
            }
            set { Context[m_objName] = value; }
        }

        public ObjectHelper(Context cxt, String objName)
        {
            m_objName = objName;
            Context = cxt;
        }
    }

将inputCmdString中的外部变量$argname统一替换为(new bjectHelper <{ m_context[“argname”].GetType()}> (m_context, “argname”)).Obj" 即可实现在动态代码中对已定义外部变量的引用。

上述对inputCmdString的预处理代码为:

            Regex re;

            // 处理未初始化的环境变量
            re = new Regex(@"^(\$)(\w)+");
            if (inputCmdString != null)
            {
                Match m = re.Match(inputCmdString);
                if (m != null && m.Length > 1)
                {
                    String outArgName = inputCmdString.Substring(m.Index, m.Length).Substring(1);
                    if (this[outArgName] == null)
                    {
                        String innerArgName = "TempArg_" + outArgName;
                        inputCmdString = "var " + inputCmdString.Replace("$" + outArgName, innerArgName);
                        inputCmdString += ";m_context[\"" + outArgName + "\"]=" + innerArgName + ";";
                    }
                }
            }

            // 处理其它环境变量
            re = new Regex(@"(\$)(\w)+");
            IDictionary<String, String> ArgsList = new Dictionary<String, String>();
            if (inputCmdString != null)
            {
                MatchCollection mc = re.Matches(inputCmdString);
                if (mc != null)
                {
                    foreach (Match m in mc)
                    {
                        if (m.Length > 1)
                        {
                            String outArgName = inputCmdString.Substring(m.Index, m.Length).Substring(1);
                            if (!ArgsList.ContainsKey(outArgName))
                            {
                                Object obj = this[outArgName];
                                if (obj == null) throw new Exception("不存在环境变量" + outArgName);
                                String innerArgName = String.Format(@"(new ObjectHelper<{0}>(m_context,""{1}"")).Obj", obj.GetType(), outArgName);
                                ArgsList.Add(outArgName, innerArgName);
                            }
                        }
                    }
                }

                foreach (String outArg in ArgsList.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]"