• .NET 本身提供了强大的脚本引擎,可以直接使用.NET CLR的任何编程语言作为脚本语言,如VB.NET、C#、JScript, J#等等。使用脚本引擎,我们可以动态生成任意表达式、或动态导入任意脚本文件,并在任意时候执行。
    经实践发现,我们可以使用至少两种不同的方式在.NET中使用脚本引擎:VsaEngine和CodeDom。
    其实,CodeDom不能算是真正的脚本引擎,它实际上是编译器。但是我们完全可以利用CodeDom来模拟脚本引擎。
    使用Emit方法也能达到动态生成可执行代码的目的,而且Emit生成的代码不需要编译,因此速度更快。但是Emit插入的实际上是汇编代码,不能算是脚本语言。
    本文介绍如何以CodeDom方式来动态生成可执行代码。


    如何在.NET中实现脚本引擎 (CodeDom篇) 沐枫网志 http://ly4cn.cnblogs.com

    1. 构造一个编译器

    设置编译参数
    编译参数需要在CompilerParameters设置:
    CompilerOptions 用于设置编译器命令行参数
    IncludeDebugInformation 用于指示是否在内存在生成Assembly
    GenerateInMemory 用于指示是否在内存在生成Assembly
    GenerateExecutable 用于指示生成的Assembly类型是exe还是dll
    OutputAssembly 用于指示生成的程序文件名(仅在GenerateInMemory为false的情况)
    ReferencedAssemblies 用于添加引用Assembly


    例如:

    theParameters.ReferencedAssemblies.Add("System.dll");

    创建指定语言的编译器
    编译需要由指定语言的CodeDomProvider生成。
    这里列举一些.NET的CodeDomProvider:

    vb.net Microsoft.VisualBasic.VBCodeProvider
    C# Microsoft.CSharp.CSharpCodeProvider
    jscript Microsoft.JScript.JScriptCodeProvider
    J# Microsoft.VJSharp.VJSharpCodeProvider


    以C#为例,要创建C#编译器,代码如下:

    ICodeCompiler compiler = new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
    下面是完整的创建编译器的例子:


    /**//// <summary>
    /// 创建相应脚本语言的编译器
    /// </summary>
    private void createCompiler(string strLanguage, bool debugMode, string strAssemblyFileName)
    {
    this.theParameters = new CompilerParameters();
    this.theParameters.OutputAssembly = System.IO.Path.Combine(System.IO.Path.GetTempPath(), strAssemblyFileName + ".dll");
    this.theParameters.GenerateExecutable = false;
    this.theParameters.GenerateInMemory = true;
    if(debugMode)
    {
    this.theParameters.IncludeDebugInformation = true;
    this.theParameters.CompilerOptions += "/define:TRACE=1 /define:DEBUG=1 ";
    }
    else
    {
    this.theParameters.IncludeDebugInformation = false;
    this.theParameters.CompilerOptions += "/define:TRACE=1 ";
    }

    AddReference("System.dll");
    AddReference("System.Data.dll");
    AddReference("System.Xml.dll");

    strLanguage = strLanguage.ToLower();

    CodeDomProvider theProvider;

    if("visualbasic" == strLanguage || "vb" == strLanguage)
    {
    theProvider = new Microsoft.VisualBasic.VBCodeProvider();
    if(debugMode)
    theParameters.CompilerOptions += "/debug:full /optimize- /optionexplicit+ /optionstrict+ /optioncompare:text /imports:Microsoft.VisualBasic,System,System.Collections,System.Diagnostics ";
    else
    theParameters.CompilerOptions += "/optimize /optionexplicit+ /optionstrict+ /optioncompare:text /imports:Microsoft.VisualBasic,System,System.Collections,System.Diagnostics ";
    AddReference("Microsoft.VisualBasic.dll");
    }
    else if("jscript" == strLanguage || "js" == strLanguage)
    {
    theProvider = new Microsoft.JScript.JScriptCodeProvider();
    AddReference("Microsoft.JScript.dll");
    }
    else if("csharp" == strLanguage || "cs" == strLanguage || "c#" == strLanguage)
    {
    theProvider = new Microsoft.CSharp.CSharpCodeProvider();
    if(!debugMode)
    theParameters.CompilerOptions += "/optimize ";
  • 关于 int 与 byte[] 的相互转换,Mattias Sjogren 介绍了3种方法。请参见 《将Integer转换成Byte Array》。其实应该还有不少方法。在这里,我归纳了包括Mattias Sjogren在内的4种方法。

    1. 最普通的方法

    从byte[] 到 uint
    b = new byte[] {0xfe,0x5a,0x11,0xfa};
    u = (uint)(b[0] | b[1] << 8 | b[2] << 16 | b[3] << 24);
    从int 到 byte[]
    b[0] = (byte)(u);
    b[1] = (byte)(u >> 8);
    b[2] = (byte)(u >> 16);
    b[3] = (byte)(u >> 24);
    2. 使用 BitConverter (强力推荐)

    从int 到byte[]
    byte[] b = BitConverter.GetBytes(
    0xba5eba11 );
    //{0x11,0xba,0x5e,0xba}
    从byte[]到int
    uint u = BitConverter.ToUInt32(
    new byte[] {0xfe, 0x5a, 0x11,
    0xfa},0 ); // 0xfa115afe
    3. Unsafe代码 (虽然简单,但需要更改编译选项)

    unsafe {// 从int 到byte[] fixed ( byte* pb = b )
    // 从byte[] 到 int u = *((uint*)pb);}
    4. 使用Marshal类
    IntPtr ptr = Marshal.AllocHGlobal(4); // 要分配非托管内存
    byte[] b= new byte[4]{1,2,3,4};
    //从byte[] 到 int
    Marshal.Copy(b, 0, ptr, 4);
    int u = Marshal.ReadInt32(ptr);
    //从int 到byte[]
    Marshal.WriteInt32(ptr, u);
    Marshal.Copy(ptr,b,0,4);
    Marshal.FreeHGlobal(ptr); // 最后要记得释放内存

    使用第4种看起来比较麻烦,实际上,如果想把结构(struct)类型转换成byte[],则第4种是相当方便的。例如:

    int len = Marshal.Sizeof(typeof(MyStruct));
    MyStruct o;
    byte[] arr = new byte[len];//{...};

    IntPtr ptr = Marshal.AllocHGlobal(len);
    try
    {
    // 从byte[] 到struct MyStruct
    Marshal.Copy(arr, index, ptr, Math.Min(length, arr.Length - index));
    o = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct));


    // 从struct MyStruct 到 byte[]
    Marshal.StructureToPtr(o, ptr, true); // 使用时要注意fDeleteOld参数
    Marshal.Copy(ptr, arr, 0, len);
    }
    finally
    {
    Marshal.FreeHGlobal(ptr);
    }
    return o;
  • [解除Managed C++ DLL对kernel32.dll的引用]

    用c++.net2002生成的managed dll,在默认情况下它会自动引用kernel32.dll,并且生成的dll也比较大。使用ildasm查看,可以看到dll多了__DllMainStartUp@12函数。而使用C#或VB.NET生成的DLL是不会这样的。
    为什么会这样?原因很简单,为了兼容非managed代码。假如在程序在使用到非Managed C++的new/delete以及析构函数,全局或静态变量等,就得需要C/C++运行库和启动代码的支持。
    最新的MSDN认为,这种方法有可能使映象在某种情况下无法正常运行。

    因此,当没有使用到C/C++运行库,就可以去掉这些东西,以使生成的DLL更小,更纯。方法就是在链接器选项加入“/NOENTRY /INCLUDE:__DllMainStartUp@12”。
    在C++.NET2003中,向导在生成Managed DLL时,默认情况是不使用C/C++运行库,并替换掉启动代码。此时如果程序中要使用C/C++运行库,就可能在运行时会出错。
    MSDN的解决方法是:
    1. 链接器选项加入“/NOENTRY /INCLUDE:__DllMainStartUp@12”。
    2. C++编译选项命令行中去掉“/zl”。
    3. “输入->附加依赖项”去掉“nochkclr.obj”,以及加入对应的msvcrt.lib。
    4. 必须在此Dll中首先调用CRT的起动代码(__crt_dll_initialize();),并在Dll不用的时候,调用结束代码(__crt_dll_terminate();)。如果有多个这样的Dll,则要保证每个Dll都必须调用至少一次。
    5. 根据Dll形态不同(如COM,含有Export的Dll),可以用不同的方法来调用CRT起动和终结函数,具体请参考MSDN的“Converting Managed Extensions for C++ Projects from Pure Intermediate Language to Mixed Mode”
  • [.NET的序列化]

    .NET反序列化是一个比较奇怪的过程.
    它是按广度优先的顺序构造的. 而且, .NET1.0和.NET1.1有着明显的区别.
    首先, 它先构造顶层对象(调用反序列化构造函数), 在构造时, 引用类型的子对象此时是没有意义的. 在.NET1.0中, 引用类型的子对象空间被分配, 但所有的成员数据没有意义; 而在.NET1.1中, 所有的引用类型的子对象的值为Nothing. 如果子对象是结构类型, 则会优先反序列化.
    在上层对象的反序列化构造函数完成后, 再继续调用引用类型的子对象的反序列化构造函数.
  • [.NET Framework 接收BeforeNavigate2事件BUG的替代方法]

    这是一篇微软网络上找到的文章,现在也许不适用了,但如果仍用.NET Framework 1.0,则只有这个方法可以解决这个问题了。


    Liju Thomas [@online.microsoft.com]
    Regarding BeforeNavigate2 it a bug as mentioned in the KB article.
    But you can connect directly to IWebBrowserEvents (NOT IWebBrowserEvents2)
    and sink to the BeforeNavigate event.

    sample code:

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Data;
    using SHDocVw;

    namespace InetTest
    {
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form, DWebBrowserEvents
    {
    private AxSHDocVw.AxWebBrowser axWebBrowser1;
    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
    private UCOMIConnectionPoint icp;
    private int cookie = -1;

    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;
    private Object obj;
    public Form1()
    {
    //
    // Required for Windows Form Designer support
    //
    InitializeComponent();

    // Sink to webbrowser events
    UCOMIConnectionPointContainer icpc =
    (UCOMIConnectionPointContainer)axWebBrowser1.GetOcx();

    Guid g = typeof(DWebBrowserEvents).GUID;
    icpc.FindConnectionPoint(ref g, out icp);
    icp.Advise(this, out cookie);
    }
    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing
    {
    if( disposing
    {
    // Release event sink
    if (-1 != cookie) icp.Unadvise(cookie);
    cookie = -1;

    if (components != null)
    {
    components.Dispose();
    }
    }
    base.Dispose( disposing ;
    }
    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
    System.Resources.ResourceManager resources = new
    System.Resources.ResourceManager(typeof(Form1));
    this.axWebBrowser1 = new AxSHDocVw.AxWebBrowser();
    this.button1 = new System.Windows.Forms.Button();
    this.label1 = new System.Windows.Forms.Label();

    ((System.ComponentModel.ISupportInitialize)(this.axWebBrowser1)).BeginInit()
    ;
    this.SuspendLayout();
    //
    // axWebBrowser1
    //
    this.axWebBrowser1.Enabled = true;
    this.axWebBrowser1.Location = new System.Drawing.Point(16, 8);
    this.axWebBrowser1.OcxState =
    ((System.Windows.Forms.AxHost.State)(resources.GetObject("axWebBrowser1.OcxS
    tate"));
    this.axWebBrowser1.Size = new System.Drawing.Size(456, 208);
    this.axWebBrowser1.TabIndex = 0;
    //
    // button1
    //
    this.button1.Location = new System.Drawing.Point(16, 232);
    this.button1.Name = "button1";
    this.button1.TabIndex = 1;
    this.button1.Text = "button1";
    this.button1.Click += new System.EventHandler(this.button1_Click);
    //
    // label1
    //
    this.label1.Location = new System.Drawing.Point(112, 232);
    this.label1.Name = "label1";
    this.label1.Size = new System.Drawing.Size(344, 23);
    this.label1.TabIndex = 2;
    this.label1.Text = "label1";
    //
    // Form1
    //
  • [SMS PDU模式,数据格式和长度计算]

    手机短信发送时,使用SMS PDU模式,数据格式和长度计算方法:

    UDL 为UD的字节长度
    如:7bit数据 "hello"
    UDL = 05, UD = E8 32 9B FD 06
    又如:16bit数据 "abc"
    UDL = 06, UD = 00 61 00 62 00 63

    CSMS 长度,为去掉SCA后的字节长度
    如:00 11 00 07 81 21 43 56 F7 00 00 AA 05 E8 32 9B FD 06
    其中SCA 为00,一个字节
    AT+CSMS=17

    又如:07 91 94 71 01 67 00 00 11 00 07 81 21 43 65 F7 00 F6 AA 05 68 65 6C 6C 6F
    其中SCA 为07 91 94 71 01 67 00 00 (+491710760000),8个字节
    AT+CSMS=17

    数据格式有3种:
    7bit,8bit,16bit。
    其中7bit采用GSM字符集,8bit采用ASCII字符集,16bit采用Unicode字符集。
    字节序采用网络字节顺序。
  • [VsaEngine脚本无法执行新的代码]

    .NET1.0 升级到 .NET1.1时,执行VsaEngine.Run后,脚本更新也无法执行新的代码的问题的替代解决方案。

    经测试,发现,VsaEngine可以更新源码,并能正确编译出目标代码。但是一旦执行了目标代码,VsaEngine内存中的目标代码将再也不更新,导致了上面的问题。而.NET1.0中可以正常更新。
    无奈,使用替代方案来完成:即使用CodeDom.Compiler来编译代码,并直接使用编译后的Assembly来获取函数信息或执行相关的函数。
    潜在的问题:
    潜在的问题是,编译后的Assembly无法卸载,除非进程关闭。这样,只要编译并使用过Assembly一次,Assembly将永驻内存。这个问题同样也存在于.NET1.0的VsaEngine。可能就是基于这个原因,.NET1.1的VsaEngine才改变策略,使得Assembly不再更新,也就不会出现Assembly越来越多的驻留内存的情况。而这恰好不是我们所希望的。
    .NET中,只有AppDomain才能被卸载,AppDomain中的所有Assembly也会跟着被卸载。但是一个AppDomain中的Assembly不能被其它AppDomain访问,除非它是可以被跨AppDomain访问的(但是这样的Assembly也就不会与AppDomain一起被卸载)。
  • 2004-04-08

    搬家了 - [程序人生]

    [搬家了]

    今天找到了 yourblog,发现这边访问特别快,以前的我登记的blog网站实在慢得难受!
    搬家了,只是,把以前的文章都转过来,也有些累,不过工作总是要做的。[face01] 不过,转文章时,不打算把文章以前发表的日期也保留,最多可能在正文中注一下,必竟也是一种新的审视吧(其实是以前文章太少了)。

    昨天到北京出差,这回估计要到五一才能回去了。想不到家里下雨,北方却艳阳高照,比家乡热了好多!总算这回办事处宽带费交上了,可以上网,否则不闷死才怪。
  • [C++实现Sealed类]

    今天看到《软件研发5》有一篇译自CUJ的文章“使类不可继承”,方法很好,但有几个毛病:
    1. 正如译者说的,还是有办法进行继承,虽然方法有点变态
    2. 最主要的毛病却是,这种办法会造成运行时的开销。因为至少会增加VTABLE指针。

    所以,针对以上两个问题,作了改动,如下:

    #ifdef _DEBUG
    namespace internalSealed
    {
    template<typename T>
    class Class_Is_Sealed
    {
    protected:
    Class_Is_Sealed(){};
    };
    };

    template<typename T>
    class Sealed: private virtual internalSealed::Class_Is_Sealed<T>
    {
    friend typename T;
    };

    #else

    template<typename T>
    class Sealed
    {
    };

    #endif

    这样子,在Debug方式下,只要一个类从Sealed<T>继承,就不可再被继承了。
    同时,在Release方式下,因为不再检查是否可以被继承,因而不产生开销(空类会被编译器优化掉)。

    使用例子:
    #include "Sealed.h"

    class test: Sealed<test>
    {
    public:
    int print()
    {
    return 1;
    }
    };

    class ttt: public test //, Sealed<test>, Sealed<ttt>
    {
    public:
    int print()
    {
    return 2;
    }
    };

    int main(int argc, char* argv[])
    {
    test t;
    printf("%d\n", t.print());

    ttt t1;
    printf("%d\n", t1.print());
    return 0;
    }