Welcome to MSDN Blogs Sign in | Join | Help

张羿的MSDN Blog

All about CLR/Interop, COM, Win32, C++
MSDN Webcast预告:.NET 4.0中的新特性系列课程(3):.NET 4.0 Beta1 Interop 新特性介绍 (Level 200)

在最新一期的.NET 4.0新特性系列课程中,我和我的同事将给大家介绍Beta1中的一些新特性,具体信息如下:

.NET 4.0中的新特性系列课程(3):.NET 4.0 Beta1 Interop 新特性介绍 (Level 200)

讲 师:张羿、朱永泰 

课程简介:.NET 4.0 Beta1在Interop,也就是互操作功能上有了较大的改进,主要是能够帮助开发者更自由的自定义互操作的行为,以及查找互操作中出现的错误。这次讲座我们主要介绍4个新特性:NOPIA、Customization of Com interop stubs、interop stub diagnostics, Custom QI。

Update:因为时间关系,我们只介绍Stub Method Redirection(也就是Customization of COM interop stubs), Custom QI, IL stub diagnostics。其中Stub Method Redirection我们已经有一篇文章提及,详情请点击这里

Posted Wednesday, June 17, 2009 10:34 AM by yzha | 0 Comments

Filed under: , ,

CLR 4.0 Beta1新功能:Stub Method Redirection

.NET Framework v4.0和VisualStudio 2010 Beta1已经出来有阵子了,估计有些喜欢尝鲜的朋友已经下载试用了。这一次发布包含了大量的新功能。我们上海CLR开发团队会编写一系列的文章介绍Interop的相关新功能。我来给大家简单介绍一下Stub Method Redirection功能。这个功能是CLR上海开发团队设计、开发并测试的新功能之一,这一次我们上海CLR小组共开发了下面几个功能

1. Managed TlbImp (Rewrite)

2. Stub Method Redirection

3. IL Stub ETW Diagnostics

4. Custom QueryInterface

而在CodePlex上面:

1. 发布了TlbImp的最新版本,包括基于规则的Customization(具体可以参考:这一篇

2. 即将发布IL Stub Diagnostics Tool,可以方便大家直接观看IL Stub,内部使用IL Stub ETW Diagnostics新功能实现

除此之外,还有一些功能是由美国团队开发的:

1. NO PIA

2. IL Stub Everywhere

3. Limit Pumping

4. PreferComThanRemoting

除了NOPIA在我之前的文章已经介绍过之外,其他功能我们会陆续写文章介绍。这次我们先介绍Stub Method Redirection。在介绍这个功能之前,有必要先介绍一下相关的背景知识:

什么是IL Stub

大家都知道,在进行Interop调用的时候,CLR会对参数进行转换(也就是所谓的Marshalling),然后再调用到目标函数。这样一个参数转换和Marshalling实际上是一小段Stub(桩代码)来负责的,比如在调用MessageBox的时候,MessageBox_IL_STUB就是负责Marshalling和参数调用的Stub:

clip_image002

当然了,这里的Stub的内容只是一个简单的抽象,实际的内容会比这个复杂一些。在实际情况下,CLR在第一次执行MessageBox的时候,会动态生成MessageBox对应的IL STUB,使用内部的类似于ReflectionEmit的机制直接输出IL代码的Byte Code,然后交给JIT来编译之,比如MessageBox对应的IL Stub是这样子的:

  1: .maxstack 6 
  2: .locals (native int,int32,native int,int32,native int,native int,int32,native int,int32,int32,int32,int32)
  3: // Initialize {
  4:          /*( 0)*/ call            native int [mscorlib] System.StubHelpers.StubHelpers::GetStubContext() 
  5:          /*( 1)*/ call            void [mscorlib] System.StubHelpers.StubHelpers::DemandPermission(native int) 
  6: // } Initialize 
  7: // Marshal {
  8:          /*( 0)*/ ldc.i4.0         
  9:          /*( 1)*/ stloc.0          
 10: IL_000c: /*( 0)*/ nop             // argument {  
 11:          /*( 0)*/ ldarg.0          
 12:          /*( 1)*/ stloc.1          
 13:          /*( 0)*/ ldc.i4.1         
 14:          /*( 1)*/ stloc.0          
 15:          /*( 0)*/ nop             // } argument 
 16:          /*( 0)*/ nop             // argument {  
 17:          /*( 0)*/ ldc.i4.0         
 18:          /*( 1)*/ stloc.s         0x4 
 19:          /*( 0)*/ ldarg.1          
 20:          /*( 1)*/ brfalse         IL_0037 
 21:          /*( 0)*/ ldarg.1          
 22:          /*( 1)*/ call            instance int32 [mscorlib] System.String::get_Length() 
 23:          /*( 1)*/ ldc.i4.2         
 24:          /*( 2)*/ add              
 25:          /*( 1)*/ stloc.3          
 26:          /*( 0)*/ ldc.i4          0x105 
 27:          /*( 1)*/ ldloc.3          
 28:          /*( 2)*/ clt              
 29:          /*( 1)*/ brtrue          IL_0037 
 30:          /*( 0)*/ ldloc.3          
 31:          /*( 1)*/ localloc         
 32:          /*( 1)*/ stloc.s         0x4 
 33: IL_0037: /*( 0)*/ ldc.i4.1         
 34:          /*( 1)*/ ldarg.1          
 35:          /*( 2)*/ ldloc.s         0x4 
 36:          /*( 3)*/ call            native int [mscorlib] System.StubHelpers.CSTRMarshaler::ConvertToNative(int32,string,native int) 
 37:          /*( 1)*/ stloc.2          
 38:          /*( 0)*/ ldc.i4.2         
 39:          /*( 1)*/ stloc.0          
 40:          /*( 0)*/ nop             // } argument 
 41:          /*( 0)*/ nop             // argument {  
 42:          /*( 0)*/ ldc.i4.0         
 43:          /*( 1)*/ stloc.s         0x7 
 44:          /*( 0)*/ ldarg.2          
 45:          /*( 1)*/ brfalse         IL_006c 
 46:          /*( 0)*/ ldarg.2          
 47:          /*( 1)*/ call            instance int32 [mscorlib] System.String::get_Length() 
 48:          /*( 1)*/ ldc.i4.2         
 49:          /*( 2)*/ add              
 50:          /*( 1)*/ stloc.s         0x6 
 51:          /*( 0)*/ ldc.i4          0x105 
 52:          /*( 1)*/ ldloc.s         0x6 
 53:          /*( 2)*/ clt              
 54:          /*( 1)*/ brtrue          IL_006c 
 55:          /*( 0)*/ ldloc.s         0x6 
 56:          /*( 1)*/ localloc         
 57:          /*( 1)*/ stloc.s         0x7 
 58: IL_006c: /*( 0)*/ ldc.i4.1         
 59:          /*( 1)*/ ldarg.2          
 60:          /*( 2)*/ ldloc.s         0x7 
 61:          /*( 3)*/ call            native int [mscorlib] System.StubHelpers.CSTRMarshaler::ConvertToNative(int32,string,native int) 
 62:          /*( 1)*/ stloc.s         0x5 
 63:          /*( 0)*/ ldc.i4.3         
 64:          /*( 1)*/ stloc.0          
 65:          /*( 0)*/ nop             // } argument 
 66:          /*( 0)*/ nop             // argument {  
 67:          /*( 0)*/ ldarg.3          
 68:          /*( 1)*/ stloc.s         0x8 
 69:          /*( 0)*/ ldc.i4.4         
 70:          /*( 1)*/ stloc.0          
 71:          /*( 0)*/ nop             // } argument 
 72:          /*( 0)*/ nop             // return {  
 73:          /*( 0)*/ nop             // } return 
 74: // } Marshal 
 75: // CallMethod {
 76:          /*( 0)*/ ldloc.1          
 77:          /*( 1)*/ ldloc.2          
 78:          /*( 2)*/ ldloc.s         0x5 
 79:          /*( 3)*/ ldloc.s         0x8 
 80:          /*( 4)*/ call            native int [mscorlib] System.StubHelpers.StubHelpers::GetStubContext() 
 81:          /*( 5)*/ ldc.i4.s        0x30 
 82:          /*( 6)*/ add              
 83:          /*( 5)*/ ldind.i          
 84:          /*( 5)*/ ldind.i          
 85:          /*( 5)*/ calli           unmanaged stdcall int32(int32,native int,native int,int32) 
 86: // } CallMethod 
 87: // UnmarshalReturn {
 88:          /*( 1)*/ nop             // return {  
 89:          /*( 1)*/ stloc.s         0xa 
 90:          /*( 0)*/ ldc.i4.5         
 91:          /*( 1)*/ stloc.0          
 92:          /*( 0)*/ ldloc.s         0xa 
 93:          /*( 1)*/ stloc.s         0x9 
 94:          /*( 0)*/ ldloc.s         0x9 
 95:          /*( 1)*/ nop             // } return 
 96:          /*( 1)*/ stloc.s         0xb 
 97: // } UnmarshalReturn 
 98: // Unmarshal {
 99:          /*( 0)*/ nop             // argument {  
100:          /*( 0)*/ nop             // } argument 
101:          /*( 0)*/ nop             // argument {  
102:          /*( 0)*/ nop             // } argument 
103:          /*( 0)*/ nop             // argument {  
104:          /*( 0)*/ nop             // } argument 
105:          /*( 0)*/ nop             // argument {  
106:          /*( 0)*/ nop             // } argument 
107:          /*( 0)*/ leave           IL_00b3 
108: IL_00b3: /*( 0)*/ ldloc.s         0xb 
109:          /*( 1)*/ ret              
110: // } Unmarshal 
111: // Cleanup {
112: IL_00b6: /*( 0)*/ ldloc.0          
113:          /*( 1)*/ ldc.i4.1         
114:          /*( 2)*/ ble             IL_00ca 
115:          /*( 0)*/ ldloc.s         0x4 
116:          /*( 1)*/ brtrue          IL_00ca 
117:          /*( 0)*/ ldloc.2          
118:          /*( 1)*/ call            void [mscorlib] System.StubHelpers.CSTRMarshaler::ClearNative(native int) 
119: IL_00ca: /*( 0)*/ ldloc.0          
120:          /*( 1)*/ ldc.i4.2         
121:          /*( 2)*/ ble             IL_00df 
122:          /*( 0)*/ ldloc.s         0x7 
123:          /*( 1)*/ brtrue          IL_00df 
124:          /*( 0)*/ ldloc.s         0x5 
125:          /*( 1)*/ call            void [mscorlib] System.StubHelpers.CSTRMarshaler::ClearNative(native int) 
126: IL_00df: /*( 0)*/ endfinally       
127: // } Cleanup 
128: .try IL_000c to IL_00b3 finally handler IL_00b6 to IL_00e0
129: 

可以看到IL代码非常多,这些都是CLR内部自动生成的。因为看到这些代码有助于开发者理解内部工作原理和找到错误(一般来说是开发者本身的问题,比如MarshalAs写错了),我们将发布一个工具可以让你看到IL Stub具体内容,底层是通过调用另外一个CLR V4 Interop的新功能:IL Stub ETW Diagnostics实现的,以后有机会我会写另外一篇文章介绍。至于IL代码本身的相关内容可以参考Experts IL Assembler和Common Language Infrastructure Annotated Standard.

总的来说,一般的IL Stub总要负责下面几件事情:

1. 安全检查

2. 参数转换,包括返回值

3. 调用目标函数,检查返回值,可能会抛出异常

4. 清理临时内存

其实还有一些其他细节问题如切换GC模式等,建立Frame等等,但是这些属于CLR内部细节问题,这里不再赘述。

IL Stub的问题

IL Stub目前为止都工作的很好。其实,CLR内部本来不是所有情况下都是用IL Stub,2.0以前还存在所谓的ML Stub (Marshalling Language),专门工作在x86下,IL则是工作在x64和IA-64上,后来美国团队将之整合,现在就只有IL Stub了。看起来现在的IL Stub就足够了,不过事实上我们认为ILStub仍然存在一些问题:

1. 无法调试

    a. 目前VS暂时不支持调试IL代码

    b. 即使可以调试,绝大多数开发者根本不熟悉IL代码

    c. IL代码是动态生成,增大了调试支持实现的难度

    d. 较难通过工具直接看到(我们即将发布新工具支持看到IL Stub)

2. 不够灵活

    a. IL Stub是CLR根据内置规则生成(也就是MarshalAs那一套),开发者无法加入新的规则

    b. 开发者无法使用自己的Stub来替换ILStub

3. 组件化和维护性:CLR有大量生成IL Stub的代码,这些代码非常复杂,规则繁多,大大增加了CLR的复杂度,而且本身是由C++写成,较难维护

我们的Vision

既然IL Stub本身有这么多问题,那么我们应该如何解决这些问题呢?在开发Stub Method Redirection新功能之前,我们Team内部有一些讨论,达成的共识如下:

1. CLR只支持最简单的calli调用本地代码

2. IL Stub由编译时刻工具生成:ILStubGen.exe

   a. 工具内置数据转换规则

   b. 用户可通过插件自定义

3. 生成的IL Stub通过calli调用本地代码

4. Interop类型和Stub直接嵌入在目标程序中:NO PIA是朝这个方向的正确一步

5. CLR运行时刻加载IL Stub:Stub Method Redirection支持该功能

可以看到,按照如上的方法,CLR可以完全从生成IL stub的任务中解放出来,IL Stub的生成也从动态(运行时)转为静态(编译时),并且可以用C#编写,解决了调试、性能、组件化,维护性的众多问题。为了实现这个美好的Vision,有很多工作要做,而且这些工作显然没法在一个Release之内完成,因此我们采取的方法是迭代渐进式的。也就是说,每个Release都会添加一些功能,和这个Vision更加接近。这个Release,我们做的就是NO PIA,以及Stub Method redirection(的一部分)。

Stub Method Redirection

所谓Stub Method,也就是用户编写的编译时刻决定的Stub,可以用任意语言编写,CLR在运行时刻不会动态生成IL Stub,而是会使用用户自定义的Stub,而实现这个的秘诀就是:

ManagedToNativeComInteropStubMethodAttribute

这个Attribute有两个参数:

1. Type:Stub Method所位于的类

2. Name:Stub Method的名称。虽然我们也想实现所谓的methodof功能(类似typeof),但是让C#在4.0中替我们加上这个功能不是太现实,因此我们就先使用名字来查找,速度稍慢,但是因为相关查找只用进行一次,而且可以通过NGEN来避免查找(NGEN来负责查找然后把查找结果直接写入本地代码中),因此速度上不存在问题。

一旦在接口(非接口不可以)的某个方法上面添加上这个Attribute,CLR就知道根据这个Attribute来找Stub,而非自己生成。

用户可以通过这个功能做下面的事情:

1. 编写自己的Stub

    a. 加以优化(比如内存池之类的)

    b. 提供自定义的类型转换

2. 编写第三方工具自己生成Stub(不过一般来讲这个会是由CLR和.NET Framework提供)

任何编写的Stub Method必须满足下面这些要求:

1. 必须是静态

2. 第一个参数是接口类型

3. 其他参数和对应接口方法完全一致

4. 必须和对应接口位于同一个Assembly,这既是简化,也符合我们的Vision

5. 必须满足访问性要求:从接口的方法必须可以访问到Stub,这个和逻辑上的调用顺序是一致的

6. 不可以是generic

一旦不满足要求,CLR在执行方法的时候会抛出异常,比如:

clip_image004

这个信息是我和PM MM讨论数次之后决定的,目的是让其尽量清晰。

对于一个Stub Method来讲,通常的格式是这样子的:

  1: class FooStubClass
  2: {
  3: 	internal static void ForwardFooStub(IFoo thisObject, string arg) 
  4: 	{
  5: 		try{
  6: 			// Step 1: 	托管参数转换到非托管参数(In)
  7: 			// Step 2: 	获得调用目标函数的地址
  8: 		 	// Step 3:	通过Delegate调用目标函数
  9: 			// Step 4:	非托管参数转换到托管参数(Out) 
 10: 			// Step 5:	转换返回值
 11: 		}
 12: 		finally
 13: 		{
 14: 			// Step 6:	清理工作
 15: 		}
 16: 	}
 17: }
 18: 

下面分别解释一下:

1. 托管参数转换到非托管参数(In):一般这里调用Marshal的对应函数来进行转换,比如Marshal.StringToBSTR

2. 获得调用目标函数的地址:这个稍微复杂一点,注意因为是COM,所以需要通过虚函数表来获得:

  1:                 //
  2:                 // Get interface pointer
  3:                 //
  4:                 IntPtr pIntf = Marshal.GetComInterfaceForObject(_this, typeof(IFoo));
  5: 
  6:                 //
  7:                 // Get target
  8:                 //
  9:                 IntPtr pTarget = IntPtr.Zero;
 10: 
 11:                 unsafe
 12:                 {
 13:                     void** pVtbl = *(void***)pIntf;
 14:                     pTarget = new IntPtr(*(pVtbl + 7)); // IUnknown => 3, IDispatch => 4
 15:                 }
 16: 

比如上面的代码就获得了_this的IFoo指针,然后获取了虚函数表第八项(跳过IUnknown3个函数,IDispatch 4个函数)作为函数指针

3. 通过Delegate调用目标函数:这一步骤需要首先调用Marshal.GetDelegateForFunctionPointer获得函数指针对应的Delegate,注意Delegate的参数必须得是对应非托管的类型,比如MessageBox对应的delgate是(IntPtr, IntPtr, IntPtr, int),然后再调用delegate,传入参数

4. 非托管参数转换到托管参数(Out):转换的时候既要包括IN也要包括OUT,比如[in, out]char []这种情况,必须两种方向都要照顾到,IN在调用之前转换,而OUT则是在调用之后转换

5. 转换返回值:这个没太多好说的,和OUT比较类似

6. 清理工作:转换不要忘记清理中间生成的临时数据,比如string转换到char *需要调用Marshal.StringToCoTaskMemAnsi转换,之后调用Marshal.FreeCoTaskMem释放,释放则是在Cleanup中作

最后是一个完整的例子:

  1: Using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using System.Runtime.InteropServices;
  6: using System.Runtime.CompilerServices;
  7: 
  8: namespace StubMethodDemo
  9: {
 10:     [ComImport]
 11:     [Guid("0741BD5F-549A-46FD-A857-0E3B23620399")]
 12:     interface IFoo
 13:     {
 14:         [MethodImplAttribute(MethodImplOptions.InternalCall)]
 15:         [ManagedToNativeComInteropStubAttribute(typeof(FooStubClass), "IFoo_Hello_Stub")]
 16:         void Hello(string name);
 17:     }
 18: 
 19:     [ComImport]
 20:     [Guid("68389CF3-212B-449D-83CB-0DD4572FEF03")]
 21:     class Foo : IFoo
 22:     {
 23:         [MethodImplAttribute(MethodImplOptions.InternalCall)]
 24:         public extern void Hello(string name);
 25:     }
 26: 
 27:     class FooStubClass
 28:     {
 29:         public delegate int IFoo_Hello_Delegate(IntPtr _this, IntPtr a);
 30: 
 31:         public void IFoo_Hello_Stub(IFoo _this, string name)
 32:         {
 33:             IntPtr nativeArg_name = IntPtr.Zero;
 34: 
 35:             try
 36:             {
 37:                 // 
 38:                 // Marshal CLR => Native
 39:                 //
 40:                 nativeArg_name = Marshal.StringToBSTR(name);
 41: 
 42:                 //
 43:                 // Get interface pointer
 44:                 //
 45:                 IntPtr pIntf = Marshal.GetComInterfaceForObject(_this, typeof(IFoo));
 46: 
 47:                 //
 48:                 // Get target
 49:                 //
 50:                 IntPtr pTarget = IntPtr.Zero;
 51: 
 52:                 unsafe
 53:                 {
 54:                     void** pVtbl = *(void***)pIntf;
 55:                     pTarget = new IntPtr(*(pVtbl + 7)); // IUnknown => 3, IDispatch => 4
 56:                 }
 57: 
 58:                 //
 59:                 // Make the call
 60:                 //
 61:                 Delegate dele = Marshal.GetDelegateForFunctionPointer(pTarget, typeof(IFoo_Hello_Delegate));
 62:                 IFoo_Hello_Delegate targetDelegate = (IFoo_Hello_Delegate)dele;
 63:                 int hr = targetDelegate(pIntf, nativeArg_name);
 64:                 if (hr < 0)
 65:                     Marshal.ThrowExceptionForHR(hr);
 66: 
 67:                 //
 68:                 // Marshal Native => CLR
 69:                 //
 70: 
 71:                 //
 72:                 // Marshal return
 73:                 //
 74:             }
 75:             finally
 76:             {
 77:                 //
 78:                 // Cleanup
 79:                 //
 80:                 if (nativeArg_name != IntPtr.Zero)
 81:                     Marshal.FreeBSTR(nativeArg_name);
 82:                 nativeArg_name = IntPtr.Zero;
 83:             }
 84:         }
 85:     }
 86: 
 87:     class Program
 88:     {
 89:         static void Main(string[] args)
 90:         {
 91:             Foo myFoo = new Foo();
 92:             myFoo.Hello("Foo!");
 93:         }
 94:     }
 95: }
 96: 

 

作者:张羿

转载请注明出处

Posted Wednesday, June 17, 2009 6:31 AM by yzha | 0 Comments

Filed under: , , ,

MSDN中文网络广播预告:公共语言运行库(CLR)开发系列课程(6):.NET中间语言(IL)入门 (Level 200)

这个星期三也就是5月19日下午2点半开始我将为大家带来CLR开发课程系列之六。这一次我打算讲一些.NET比较本质的内容:Metadata和IL代码。欢迎希望对.NET的运作机理和工作方式能够有进一步深入了解的朋友收听。本次讲座的具体信息如下:

公共语言运行库(CLR)开发系列课程(6):.NET中间语言(IL)入门 (Level 200)

讲  师:张羿 

课程简介:.NET本质上是一个执行中间语言(Intermediate Language) 代码的虚拟机,任何在.NET上运行的语言(如C#)都需要将本身翻译为IL代码,然后交给.NET执行。因此,理解IL代码对于理解.NET本身的工作方式、编写正确和高效的代码、查找和定位错误各种运行错误是非常有效的。本次讲座将介绍有关IL代码的基本知识,可以帮助大家理解大多数常见的IL代码。

推荐指数:

Posted Tuesday, May 19, 2009 2:23 PM by yzha | 1 Comments

Filed under:

Visual Studio 2010 + .NET 4.0Beta1发布!

Visual Studio 2010和.NET 4.0的Beta1版本终于发布了!目前Beta1还只是对MSDN的订阅者开放,到美国时间20日星期三,也就是我们的21日星期四的时候,Visual Studio 2010 / .NET 4.0 Beta1将公开对外发布。这个版本是可以安装的Setup,而非上次的虚拟机镜像,因此对于大家的机器要求会放松一些。想要观看安装图片的朋友们可以点击这篇Blog:http://www.itsmywindows.com/visual-studio-2010-first-look-installation

这一次发布距离上次的CTP发布过了大概半年的时间,这半年的时间有数量众多的新特性被开发出来,并被加入到Beta1版本之中。在接下来的几篇Blog和Webcast(具体请参看中文MSDN的相关预告),我将主要介绍.NET 4.0 Beta1中的一些新特性,特别是我们上海CLR开发小组所负责开发的一些关于Interop的新功能,尽请期待!

Posted Tuesday, May 19, 2009 2:14 PM by yzha | 0 Comments

Visual Studio Team System 2010 Architecture系列文章

微软上海VS TeamArch小组的中文Blog上面最近开始了一个系列文章,主要翻译Cameron Skinner的一系列介绍Visual Studio Team System 2010 Architecture CTP的一些新功能的Blog,目前有下面两篇:

VSTS 2010 Architecture 第一章:Modeling Project

Visual Studio Team System 2010 Architecture- 前言

建议对VS 2010 Architecture CTP有兴趣的朋友可以关注一下。

VS TeamArch小组的Blog主页是:http://blogs.technet.com/teamarchchina/

Posted Wednesday, April 29, 2009 12:43 PM by yzha | 1 Comments

Filed under:

使用CorFlags修正Windows Live Writer Backup Utility的Bug

最近在网上发现一个小程序Windows Live Writer Backup Utility可以用来备份Windows Live Writer的Blog设置。我有好几个Blog,使用Windows Live Writer在不同Blog上面发布非常方便,但是因为我有时候会重装一下系统试一下其他的系统,比如Windows 7 Beta,并且马上可能就要最近出RC,因此,如果可以备份Blog的设置的话是非常方便的。这个程序非常简单,勾上你要备份的数据,然后选择Backup即可:

clip_image002[4]

可是点击Backup,这个程序最终居然抛出异常:

clip_image004[4]

仔细看看,发现这个Exception是BadImageFormatException,无法加载CabLib这个Assembly。我的OS是64-bit,会不会是主程序和Assembly之间一个是64-bit一个是32-bit呢?打开TaskManager,找到这个进程,发现这个进程确实是64-bit的,

clip_image006[4]

然后再用Corflags查看一下RarLib:

C:\Program Files (x86)\Windows Live Writer Backup>corflags cablib.dll

Microsoft (R) .NET Framework CorFlags Conversion Tool. Version 3.5.21022.8

Copyright (c) Microsoft Corporation. All rights reserved.

Version : v2.0.50727

CLR Header: 2.5

PE : PE32

CorFlags : 16

ILONLY : 0

32BIT : 0

Signed : 0

发现这个Assembly是PE32,不是ILONLY,因此这个Assembly只能以32-bit执行,再看看主程序:

C:\Program Files (x86)\Windows Live Writer Backup>corflags LiveWriterBackup.exe

Microsoft (R) .NET Framework CorFlags Conversion Tool. Version 3.5.21022.8

Copyright (c) Microsoft Corporation. All rights reserved.

Version : v2.0.50727

CLR Header: 2.5

PE : PE32

CorFlags : 1

ILONLY : 1

32BIT : 0

Signed : 0

这个EXE是PE32,ILONLY,说明这个程序是以Any CPU编译的,也就是说在64-bit机器上缺省64-bit运行(但是也可以在32-bit下运行),在32-bit机器上以32-bit运行。至此问题就很清楚了,主程序以64-bit运行,尝试加载32-bit的CabLib失败。解决方法很简单,强制主程序以32bit运行:

C:\Program Files (x86)\Windows Live Writer Backup>corflags LiveWriterBackup.exe

/32bit+

Microsoft (R) .NET Framework CorFlags Conversion Tool. Version 3.5.21022.8

Copyright (c) Microsoft Corporation. All rights reserved.

之后再次运行LiveWriterBackup程序,问题解决。如果你也正巧正在运行64-bit的系统,并且运行某些.NET程序时候发生了BadImageException,这时候可以怀疑是32-bit/64-bit相关的问题,并使用本文所使用的方法来确定问题并解决。

Posted Friday, April 03, 2009 4:13 PM by yzha | 0 Comments

Filed under:

使用.NET/CLR的Stress Log功能寻找问题

不知道各位使用.NET开发的朋友是否有遇到过一些非常奇怪的问题而不知道如何下手呢?这个时侯CLR本身提供的StressLog功能就非常有用了。这个StressLog可以在很多时候把CLR所做的事情记录下来,比如,对于一个很简单的最后抛出异常的.NET程序Log大致如下:

STRESS LOG:

facilitiesToLog = 0x8000ffff

levelToLog = 16

MaxLogSizePerThread = 0x20000 (131072)

MaxTotalLogSize = 0x2000000 (33554432)

CurrentTotalLogChunk = 6

ThreadsWithLogs = 3

Clock frequency = 0.014 GHz

Start time 22:47:37

Last message time 22:47:44

Total elapsed time 6.520 sec

THREAD TIMESTAMP FACILITY MESSAGE

ID (sec from start)

--------------------------------------------------------------------------------------

1638 6.519607729 : `SYNC` SafeExitProcesses: exitcode = -2146233082

1638 0.842361250 : `GC` CreateHandle: 0000000000261338

1638 0.838161973 : `EH` In CLRVectoredExceptionHandler, Exception = e0434f4d, Context = 00000000001AE4C0, IP = 00000000779B649D SP = 00000000001AEA60

1638 0.837840633 : `GC` CreateHandle: 0000000000261340

1638 0.837838329 : `EH` in Thread::SetLastThrownObject: obj = 0000000002B76238

1638 0.837837281 : `EH` Exception HRESULT = 0x80131600 Message String 0x0000000002B96188 (db will display) InnerException 0000000000000000 MT 0000000000000000 (BAD MethodTable)

1638 0.837834418 : `EH` ******* MANAGED EXCEPTION THROWN: Object thrown: 0000000002B76238 MT 000007FEF40FADC8 (System.ApplicationException) rethrow 0

1638 0.837646265 : `CLASSLOADER` DoRunClassInit: returning SUCCESS for init 000007FEF40F2E10 (System.Collections.HashHelpers) in appdomain 000000000035CCC0

1638 0.837618049 : `CLASSLOADER` RunClassInit: Returned Successfully from class contructor for type 000007FEF40F2E10 (System.Collections.HashHelpers)

1638 0.837617141 : `CLASSLOADER` DoRunClassInit: returning SUCCESS for init 000007FEF40F2E10 (System.Collections.HashHelpers) in appdomain 000000000035CCC0

… (中间省去1000余行)

1638 0.328838232 : `GC` CreateHandle: 00000000002613E0

1638 0.312699170 : `CLASSLOADER` Attempted to set new native file 02088e80, old file was 00000000, location in the image=f3cc1008

1754 0.269468466 : `ALWAYS` SetupThread managed Thread 00000000020815D0 Thread Id = 2

------------ Last message from thread 1754 -----------

1638 0.268254415 : `GC` CreateHandle: 00000000002613E8

1638 0.268252879 : `GC` CreateHandle: 00000000002615F0

1638 0.219663812 : `ALWAYS` SetupThread managed Thread 00000000003B8BC0 Thread Id = 1

1638 0.219644256 : `GC` CreateHandle: 00000000002613F0

1638 0.219643209 : `GC` CreateHandle: 00000000002615F8

1d9c 0.200751492 : `CORDB`ALWAYS` Debugger Thread spinning up

------------ Last message from thread 1d9c -----------

1638 0.187761783 : `GC` CreateHandle: 00000000002613F8

1638 0.187758151 : `GC` CreateHandle: 00000000002611F8

------------ Last message from thread 1638 -----------

---------------------------- 625 total entries ------------------------------------

可以看到这个程序最后(注意Log的最前面是程序最后发生的事情,是反过来的)抛出了一个System.ApplicationException, HR=0x80131600,导致程序终止。当然了,实际的情况会比这个复杂得多,这里只是一个例子而已。这些信息详细说明了CLR的运行情况,主要供CLR小组的开发人员使用。但是这并不意味着这些信息对于一般.NET开发人员没有用处,其实这些信息对于了解托管程序的运行状况是很有用的,并且如果运行中出现了错误,这些错误也会被写到StressLog中。当然了,解决一般的问题也许并不需要使用StressLog,但是如果你手头的问题没有任何线索可循,不妨试一下StressLog,也许会有意想不到的效果。如果想对某条有疑问的具体信息进行解读,除了参考错误信息之外,也可以在Rotor代码中查找相应代码行,从而确定大概是什么意思。比如AppDomain::Unload方法中可以查找到STRESS_LOG宏输出了Unload domain这条信息:

   1: void AppDomain::Unload(BOOL fForceUnload)
   2: {
   3:
   4:     STRESS_LOG3 (LF_APPDOMAIN, LL_INFO100, “Unload domain [%d, %d] %p\n”, GetId().m_dwId, GetIndex().m_dwIndex, this);
   5:
   6: }

不过注意不是所有信息都可以在Rotor中查找到,因为Rotor中并不包含所有CLR 2.0的代码。

获得StressLog的方法如下:

1. 在命令行中输入:set COMPLUS_StressLog=1

2. 在命令行中启动WinDbg,然后通过WinDbg开始调试程序,直到程序运行到出问题的地方

3. 在WinDbg中输入:.loadby sos mscorwks。这一步骤是用来加载SOS的。SOS是一个用来调试CLR的一个WinDbg的Extension,有机会我会专门写篇文章讲用SOS来调试托管程序,CLR小组内部调试托管程序很多时候都用这个。

4. 在WinDbg中输入!DumpLog

5. 在程序启动的目录下查找StressLog.txt文件,StressLog信息就在里面了

第一步第二步也可以在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework建立一个DWORD类型的名为StressLog的值,设置为1也可以,只是效果是全局的,不如set COMPLUS_StressLog灵活。

Posted Thursday, April 02, 2009 4:44 PM by yzha | 0 Comments

Filed under:

.NET 4.0新功能:Corrupted State Exceptions

在.NET 4.0中引入了一个新功能:Corrupted State Exceptions。听上去名字很神秘,实际上这个功能主要是限制对Exception的错误用法:捕获AccessViolationException/SEHException等可能会造成程序状态错误而无法正确继续的种种异常,具体可以参看CLR程序经理Andrew Pardoe的这篇MSDN文章:http://msdn.microsoft.com/en-us/magazine/dd419661.aspx

Posted Monday, March 30, 2009 4:12 PM by yzha | 1 Comments

调试Bug的神兵利器:通过WinDbg条件断点收集Log

前段时间花了几天一直在用WinDbg调试一个比较棘手的Bug。这个Bug是C# Team那边发现的,他们的Testcase跑大概10分钟左右会出一个在CLR内部的ASSERT。比较难调试的主要原因在于ASSERT表明一个全局的数据结构出现了问题,本来不应该用完的数组却已经用完了(因为按照设计,这个数组是边使用边清理的,是不会用完的)。初步想到的有下面几种方案来调试:

1. 设置数据断点

2. 一步一步调试

3. 添加Log代码

设置数据断点的主要问题是不太好确定到底是因为什么原因导致的数据结构问题,而且因为是数组被用完,很难将是到底是哪一个数组元素的加入导致了数组被全部占用,因此无法通过设置数据断点的方法来调试。一步一步的调试显然也没法解决问题,因为这个Testcase本身要跑十分钟,可以想象单步调试运行十分钟的程序会花费多长时间。因此两个方案都被我否决。添加Log代码其实是可以的,只是需要修改代码,每次修改之后需要重新编译代码,然后需要在目标机器上安装,而且C#使用的CLR的Branch并非我们正在开发的Branch,需要重新下载源代码,相对比较麻烦。最后为了解决这个问题,我采取的方法是使用WinDbg的条件断点+Log的方式。大致的方法如下:

第一步:在一个或者多个可疑处设置断点

bu address “command”

bu是WinDbg中的设置Unresolved Breakpoints命令,用起来比较方便,我比较喜欢用。address就是你所要断的代码地址,可以是函数开始,也可以是某一行。Command非常重要,它表示了WinDbg在每次断到address的时候都要执行的命令,不同命令用分号隔开,如:

.echo [Function A]; dv this; kb; g

这几条命令意思是:打印[Function A],打印this指针的值,打印当前调用栈,然后继续执行。大家可以根据实际情况添加一些其他命令打印一些自己所需要的信息。通过上面这套命令打印的内容大致如下:

[FunctionA]

this = 0xABCDEFG

module!FuncA

module!FuncB

module!FuncC

可以看出,这条断点如果反复被断,那么在WinDbg的命令窗口中便会把每次断点被Hit的相关信息通过刚才定义的命令打印出来。如果定义了很多这样的断点,那么在命令窗口中就会把整个程序执行的情况打印出来,起到Log的作用,而且可以显示调用栈等信息,比一般的Log要强大许多。

第二步:设置Log

缺省情况下,WinDbg的Buffer大小是有限的,如果程序运行时间比较长,那么Buffer可能会不够,我们通过条件断点打出的信息会被截断。幸好,WinDbg提供了将命令窗口的内容输出到Log中的功能。选择Edit->Open/Close Log File菜单项,WinDbg会显示如下对话框:

clip_image002

在这个对话框里面输入你想要保存的Log文件名即可。如果是添加新的内容而不是覆盖原有的,则勾上Append。

第三步:分析Log

当获得了Log信息之后,下一步就需要分析Log的内容了,这是一件需要耐心、对数据的敏感、以及一点点运气的事情。分析的时候可能发现Log的信息不足,这时就需要添加新的断点或者修改打印的信息,重新收集Log,再加以分析,直到Log信息足够为止。这时WinDbg设置条件断点的优势就出来了,因为不需要修改代码,编译代码,部署代码这样的一个过程,而是只需要键入不同的命令而已。经过几次调整断点位置和打印的信息并重新收集Log,我最终通过分析发现这个Bug是只有可能在特定情况下RCW没有被GC,并且创建线程退出的时候才会出现,具体的内容因为涉及到.NET 4.0中还没有发布的新功能,这里就不多说了。可以看到,如果采用常规的方法,对于这种在特定的条件下才会重现的问题是很难发现的。

总之,使用WinDbg来设置条件断点,打印相关信息,并且输出到Log文件是一种非常强大的调试方法,可以调试一些非常复杂的Bug,而且具有不需要修改代码的灵活性,可以自由定义自己想需要打印的信息和断点设置的位置,主要的缺点是方法稍显复杂,不过如果适应了之后还是很方便的。我强烈推荐大家在遇到比较复杂的Bug的时候,可以尝试使用一下这种方法,可能具有意想不到的效果哦。

Posted Monday, March 30, 2009 3:19 PM by yzha | 1 Comments

Filed under:

MSDN中文网络广播预告:.NET 4.0新特性系列课程(2):契约式设计 (Level 200)

这次我将为大家讲解如何使用.NET 4.0中的契约式设计(也可以在.NET 2.0+中使用,需要额外下载安装包),欢迎有兴趣的朋友收听。

地址为:http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032406872&Culture=zh-CN

.NET 4.0中的新特性系列课程(2):契约式设计 (Level 200)

讲 师:张羿 

课程简介:.NET 4.0中引入了契约式设计这一概念,允许程序员在函数体中按照固定的格式显式说明函数的入口,出口等地方所必须满足的条件。这一功能可以有效减少程序 Bug数量,让程序员更容易的理解现有代码,并提供静态检查、动态检查等功能。本次讲座将介绍契约式设计的概念,以及在.NET 4.0中的使用方法。

推荐指数:

Posted Monday, March 16, 2009 3:31 PM by yzha | 0 Comments

Filed under: , ,

CodePlex上TlbImp新版本发布:基于规则的自定义功能

大家好。距离上次我们发布在CodePlex上的新版本TlbImp已经过了快半年了。在这半年的时间内,除了主要进行.NET 4.0相关的新功能开发之外,我们上海CLR小组也没有忘记进行TlbImp相关功能的继续开发,于今年3月9日再次发布了TlbImp的一个新版本:

http://www.codeplex.com/clrinterop/Release/ProjectReleases.aspx?ReleaseId=17579

这次版本中我们引入了两个重要功能:

1. 通过规则自定义互操作程序集以及规则自定义编辑器

2. 回归测试工具

基于规则的自定义功能

我们先来看一下自定义功能。这个新版本的TlbImp允许用户通过自定义的一系列的规则来指定TlbImp如何生成最终的互操作程序集。之前有不少用户向我们提到在使用TlbImp的时候,经常需要对TlbImp生成的结果做一些修改,而且必须是自动化的修改。他们通常使用的方法是先使用ILDASM反汇编,使用Perl脚本修改反汇编代码,然后再使用ILASM重新生成互操作程序集。为了解决这个问题,我们引入了一个新功能,允许用户以非常自由的方式来定义他们最终想要看到的结果。

让我们先来看一个简单的例子:假设我们希望改变互操作程序集中的某个类型的名称。先双击打开TlbImpConfigFileEditor.exe启动自定义文件的编辑器,然后打开我们需要自定义的Type Library,这里我们选择发布版本中Samples\ChangeManagedName\ChangeManagedNameSample.tlb文件,如下:

clip_image002

左边显示的是我们需要自定义的Type Library,而右边,则是我们需要自定义的规则,这些规则可以告诉TlbImp如何修改最终生成的互操作程序集。首先,将我们需要修改的IComparable接口结点从左边拖到右边,松开鼠标,出现如下的对话框:

clip_image004

上面这个对话框是用来创建一个新的规则,规则指定TlbImp对于哪些对象应用何种动作。在这个对话框中我们需要定义这个规则所对应的动作(Action),因此需要在Action下拉框中选择ChangeManagedName,然后点击OK即可。之后编辑器状态如下(需要自己展开结点):

clip_image006

大家可以看到右边已经出现了一个新的规则叫做Change interface name,对应的Category是Type,也就是说这个规则是针对互操作程序集中的类型设置的。Condition指定了规则所需要满足的条件,选中Condition节点(或者其子节点)可以在下面的Condition Expression中看到对应的规则表达式,也就是NativeName Equal ‘ICompareable’,意思是该规则是针对任何名字叫做IComparable的类型。注意因为我们是从IComparable节点直接拖到右边,因此这些条件是编辑器自动生成的。大家如果需要也可以自己通过点击Native Equal IComparable条件来修改,或者点击<Empty>来增加新的条件。现在我们可以点击<Empty>节点,在下拉框中选择TypeKind,第二个下拉框选择Equal,第三个下拉框选择Interface,最终的结果如下:

clip_image008

注意表达式节点的组织方式是类似语法树的样子,也就是说And节点下面的互相之间是And关系,最终的结果总是可以在Condition Expression一栏看到:

( NativeName Equal 'IComparable' ) And ( TypeKind Equal 'Interface')

当编辑好了规则的时候,我们需要指定对应的具体动作的参数。因为我们需要修改对象类型的名称,双击Action下面的NewName子结点会弹出如下对话框:

clip_image010

输入我们想修改成的名字,然后点击OK。

这样一个规则就完成了:

clip_image012

修改完毕之后存盘为ChangeInterfaceName.xml,然后在命令行下面调用TlbImp,使用/config参数引用之前存盘的Config文件(黄色加亮部分:

clip_image014

之后通过ILDASM打开我们生成的结果:

clip_image016

可以看到IComparable已经被改名成了IMyInterface。

TlbImp总共支持下面几种动作(Action):

1. ChangeManagedName:修改类型、函数的名称

2. ResolveTo:将一个类型替换为另外一个类型(可以是另外一个程序集的类型)。现在已经有用户在CodePlex上面提出这个功能需求了:http://clrinterop.codeplex.com/WorkItem/View.aspx?WorkItemId=2565

3. AddAttribute:为任意类型添加任意Attribute

4. PreserveSig:为单个函数或者类型中的所有函数添加PreserveSigAttribute并相应修改函数的原型

5. ConvertTo:修改函数中的参数类型

每种对应的动作在Samples目录下面都有对应的例子,有兴趣的朋友可以参照文档自行实验。

回归测试工具

为了帮助用户在修改TlbImp代码的时候可以更容易保证自己的修改不会引起其他问题,我们引入了一个简单的回归测试工具,大家可以到这里下载:

http://clrinterop.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=17579

下载完毕之后打开Bin目录下面的TlbImpRegressionTestTool.exe,然后通过File菜单打开Testcase目录下面的testcases.xml文件:

clip_image018

可以看到所有的Testcase都被列出来了。下一步在Run菜单里面选择Settings,输入TlbImp2.exe和WinDiff.exe所在位置:

clip_image020

完毕之后,选择Run下面的Run All Testcases或者Run Selected Testcases,该工具便会调用TlbImp2.exe依次运行Testcase来检查TlbImp2的相应功能是否正确:

clip_image022

绿色为测试成功,红色为失败。如果有失败的情况,双击该行可以打开WinDiff比较TlbImp2当前生成的结果和应该生成的结果之间的区别。

最后,希望大家能够积极试用TlbImp的新功能。如果有希望看到的TlbImp的新功能,或者对目前的TlbImp有哪些觉得做的不够好的地方,都可以到下面的地址提出你的宝贵意见: http://clrinterop.codeplex.com/WorkItem/List.aspx

Posted Friday, March 13, 2009 9:19 AM by yzha | 1 Comments

Filed under: ,

预告:公共语言运行库(CLR)开发系列课程(4):COM Interop进阶

这次我主要讲RCW的原理,生命周期,引用计数,套间,System.__ComObject,事件调用原理等内容。CCW由于时间限制就不涉及了,毕竟大家还是以使用RCW为主。

感兴趣的朋友可以在下面注册:

公共语言运行库(CLR)开发系列课程(4):COM Interop进阶 (Level 300)

讲 师:张羿 

课程简介:本次课程我们将介绍.NET调用COM组件上使用上的一些常见问题,特别是RCW创建、释放、和套间的交互等比较容易出错的地方。之后,我们将简介CCW的使用方法。

推荐指数:

Posted Monday, February 16, 2009 1:50 PM by yzha | 1 Comments

Filed under: , , ,

System.Runtime.InteropServices.GetHRForException的陷阱

从字面上看,GetHRForException函数的作用很简单:得到Exception所对应的HRESULT的值。但是,GetHRForException还会做一件事情:设置当前线程的IErrorInfo使之指向该Exception(严格来说是获得Exception的CCW中的IErrorInfo接口指针)。如果对IErrorInfo不熟悉的朋友们可以把IErrorInfo看成COM版本的GetLastError或者errno。设置IErrorInfo会导致之后的代码如果使用GetErrorInfo查询IErrorInfo的值,会获得一个非0的结果,那么有些代码可能会认为程序出错而拒绝继续执行。更糟糕的是,如果之后的代码在做COM Interop或者PInvoke,CLR会检查IErrorInfo,如果IErrorInfo非0则认为该调用失败,并抛出异常或者返回错误值(视乎PreserveSigAttribute是否存在)。直接的结果是可能函数调用成功,但是因为IErrorInfo已经被设置而导致该调用最终失败。很有意思的是在.NET中有一段处理资源的代码正好有这个问题,结果间接导致了巴西葡萄牙语版本(是的,你没看错J)的.NET的RegAsm挂掉。其实我个人认为这个函数应该被命名为GetHRForExceptionAndSetIErrorInfo(Exception ex);虽然有些长,但是很清晰,总比简短而错误的名字要来的好。

在正常情况下,如果你需要获得Exception的HRESULT值,应该直接使用Exception.HResult属性。那么什么时候才应该使用GetHRForException呢?当你写了一个.NET的函数准备让非托管代码调用(最好是通过COM),并且返回一个HRESULT,这个时候你有必要把托管函数内部抛出的异常转换为HR,并且把Exception本身的信息设置到当前线程的IErrorInfo中去,这才是最符合COM规范的。

Posted Wednesday, January 07, 2009 3:10 PM by yzha | 1 Comments

Filed under: ,

Vista上远程管理Hyper-V服务器

因为工作需要,我把自己的一台机器装上了Windows Server 2008并配置好了Hyper-V,用来Host我的一些虚拟机。但是我一般远程访问这台服务器,管理虚拟机起来不太方便,特别是在虚拟机上还没有安装好Integration Services更是如此。因此我经常用HyperV为Vista发布的一款MMC来远程管理我的虚拟机。这个MMC包含在Vista SP1 Managed Tools Update for the released version of Hyper-V里面,有需要的朋友可以在这里下载:

http://support.microsoft.com/kb/952627

装好了之后在Administrative Tools中可以找到。

image

Posted Wednesday, January 07, 2009 9:18 AM by yzha | 1 Comments

【预告】1月6日下午14:30 CLR开发系列课程(3):COM Interop基础 (Level 300)

1月6日下午14:30我将在MSDN中文网络广播中主讲.NET中COM和COM Interop的相关基础知识。有兴趣的朋友可以通过下面的链接登记并收听此次网络广播:

 公共语言运行库(CLR)开发系列课程(3):COM Interop基础 (Level 300)

讲 师:张羿 

课程简介:从本次课程开始,今后几次课程将围绕COM和COM Interop技术,展开对托管代码和非托管代码之间通过COM进行互操作的讨论。本次课程将从COM基础知识开始,简单介绍进行COM Interop的所需相关基础知识和概念,为以后的课程做好准备。

推荐指数:

Posted Tuesday, December 30, 2008 1:29 PM by yzha | 1 Comments

Filed under: , ,

More Posts Next page »
Page view tracker