图片 11

Visual Studio 2015的坑:中文字符串编译后成乱码

.maxstack  2
IL_0000:  ldarg.0
IL_0001:  ldstr      bytearray (0A 4E 00 4E 75 98 )                               // .N.Nu.
IL_0006:  stfld      string BlogServer.Web.Controls.Pager::PreviousText
IL_000b:  ldarg.0
IL_000c:  ldstr      bytearray (0B 4E 00 4E 75 98 )                               // .N.Nu.
IL_0011:  stfld      string BlogServer.Web.Controls.Pager::NextText

IL DASM 基础

1.图标含义

图片 1

使用IL反编译出项目代码

图片 2

MANIFEST:是一个附加信息列表,主要包含程序集的一些属性,如程序集名称、版本号、哈希算法等;
Democode:项目名称
Democodeing.Common:命名空间
Democodeing.ICar:接口
Democodeing.Program:类,主要查看存类下面的内容。

.class 类信息项代码:

.class private auto ansi beforefieldinit DemoCoding.Program
       extends [mscorlib]System.Object
{
} // end of class DemoCoding.Program

1).class,表示Program是一个类。并且它继承自程序集—mscorlib的System.Object类;
2)private,表示访问权限;
3)auto,表示程序的内存加载全部由CLR来控制;
4)ansi,是为了在没有托管代码与托管代码之间实现无缝转换。这里主要指C、C++代码等;
5)beforefieldinit,是用来标记运行库(CLR)可以在静态字段方法生成后的任意时刻,来加载构造器(构造函数);

.ctor 方法代码:

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // 代码大小       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0006:  ret
} // end of method Program::.ctor

1)cil managed:表示其中为IL代码,指示编译器编译为托管代码;
2).maxstack:表示调用构造函数.otor期间的评估堆栈(Evaluation Stack) ;
3)  IL_0000:标记代码行开头;
4)ldarg.0:表示转载第一个成员参数,在实例方法中指的是当前实例的引用;
5)call:call一般用于调用静态方法,因为静态方法是在编译期就确定的。而这里的构造函数.otor()也是在编译期就制定的。而另一指令callvirt则表示调用实例方法,
它是在运行时确定的,因为如前述,当调用方法的继承关系时,就要比较基类与派生类的同名函数的实现方法(virtual和new),以确定调用的函数所属的Method
Table;
6)ret:表示执行完毕,返回;

Main() 静态方法代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       19 (0x13)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  call       string [mscorlib]System.Console::ReadLine()
  IL_0011:  pop
  IL_0012:  ret
} // end of method Program::Main

1)
hidebysig:表示当把此类作为基类,存在派生类时,此方法不被继承,同上构造函数;
2).entrypoint:指令表示CLR加载程序时,是首先从.entrypoint开始的,即从Main方法作为程序的入口函数;
3)nop:为空该指令,主要给外部设备或者指令间隙准备时间;
4)ldstr:创建String对象变量”Hello World.” ;
5)pop:取出栈顶的值。当我们不需要把值存入变量时使用;

public class Pager : Control
{
    protected string PreviousText = "ÉÏÒ»Ò³";
    protected string NextText = "ÏÂÒ»Ò³";
    //...
}

VS中增加IL DASM工具

我们在安装VS同时都会自动安装ildasm工具,无需另行安装。ildasm工具打开方法如下图:

图片 3

我们也可以直接wind+R.输入:C:\Program Files (x86)\Microsoft
SDKs\Windows\v7.0A\bin\ildasm.exe (window 7 64位 操作系统安装目录)
同样可以打开ildasm。
我们也可以把ildasm工具增加到我们常用的VS中。
1.工具(Tools)–>外部工具(External Tools..)

图片 4

2.添加内容填写对应信息。命令:C:\Program Files (x86)\Microsoft
SDKs\Windows\v7.0A\bin\ildasm.exe
(window 7 64位 操作系统安装目录) 。

图片 5

已上信息填写完成后,在“工具”选择卡中能找到我们刚增加的外部工具名称(IL_DASM)。增加完成后可以小试一把。
国际惯例来段”Hello
World”。代码编写完后直接F6生成exe文件,然后工具–>IL_DASM–>确认(无需修改任何参数,默认目标文件路径)。系统会弹出IL工具,我们双击Main方法。

图片 6

这时可以看到Main方法在IL中编译的代码。感觉有点陌生不易看懂。
还有IL编译出现的三角型,正方型都是啥!

接着,用ILSpy反编译了VS2015所编译出的程序集的IL代码之后,真相大白:

IL DASM反编译工具

  使用C#的猿人或多或少都会对微软的IL反编译工具(ildasm.exe)有所认识。我最早接触到这工具是公司同事使用他反编译exe程序,进行研读和修改。感觉他还是很强大。
  IL是微软平台上的一门中间语言,我们常写的C#代码在编译器中都会自动转换成IL,然后在由即时编译器(JIT
Compiler)转化机器码,最后被CPU执行。ildasm.exe反编译工具将IL汇编成可跨平台可执行的(pe)文件。可供我们了解别人代码和修改。有了他我们看待问题可以不用停留在编辑器层面,可深入中间层。

查看出现乱码问题的.cs文件编码,发现用的是ANSI编码。于是以UTF-8编码另存该文件,然后用VS2015重新编译,问题解决。

使用IL DASM 修改EXE程序代码

1.打开IL工具,选择所要修改的EXE程序。

图片 7

2.文件–>转储。确定后选择另存路径,会生成二个文件:*.il 和 *.res

图片 8

3.用记事本打开*.il修改里面的内容:

 .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // 代码大小       19 (0x13)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World-[已使用il工具修改过...]"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  call       string [mscorlib]System.Console::ReadLine()
    IL_0011:  pop
    IL_0012:  ret
  } // end of method Program::Main

4.把修改后的代码编译成EXE程序。

ilasm /exe /output=C:\CK.exe /Resource=C:\Users\Ck\Desktop\coding.res C:\Users\Ck\Desktop\coding.il

图片 9

修改就这么简单。运行修改后的EXE程序,值已修改。

图片 10

 

.maxstack  2
IL_0000:  ldarg.0
IL_0001:  ldstr      bytearray (C9 00 CF 00 D2 00 BB 00 D2 00 B3 00 ) 
IL_0006:  stfld      string BlogServer.Web.Controls.Pager::PreviousText
IL_000b:  ldarg.0
IL_000c:  ldstr      bytearray (CF 00 C2 00 D2 00 BB 00 D2 00 B3 00 ) 
IL_0011:  stfld      string BlogServer.Web.Controls.Pager::NextText

在GitHub上提交Issue之后,从回复中得知这个问题与Roslyn检测文件编码的处理方式有关。

今天有园友反馈向我们反馈,个人博客分页显示随笔列表的页面中,“上一页”“下一页”显示乱码:

* Roslyn can’t detect 932
encoding

可是昨天我们并没有更改这部分代码,肯定不是我们昨天代码修改引起的。

大家使用 Visual Studio 2015 时需要注意一下这个问题。

VS2015 RC中没这个问题。

而这个地方的“上一页”“下一页”字符串恰恰是在我们昨天发布的程序集中定义的:

public class Pager : Control
{
    protected string PreviousText = "上一页";
    protected string NextText = "下一页";

    //...
}

* VS2015 (MSBuild/14) compiler can’t detect file ecoding
correctly

【问题原因与临时解决方法】

图片 11

昨天,我们用VS2015编译了博客程序中的一个程序集并发布上线。

【补充】

(2015年8月5日更新:微软已经修复了Roslyn的这个bug,详见
https://github.com/dotnet/roslyn/pull/4303

用ildasm查看VS2015编译出来的程序集的IL代码(乱码):

原来是VS2015所用的编译器惹的祸,而这个编译器就是大名鼎鼎的 Roslyn

用ildasm查看VS2013编译出来的程序集的IL代码(未乱码):

* Chinese string is compiled to garbage
characters

于是,我们改用VS2013重新编译了一下这个程序集,更新之后,乱码立马消失。

【GitHub上的相关链接】

* .NET compiler produces incorrect string constants in MSIL when C#
source files encoded with non-UTF-8
encoding

*
VS2015打开非unicode编码的代码,其中变量名有中文就无法编译的bug