Wandering between the native & managed world

CLR/.NET 4.0开发中~~

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  18 随笔 :: 0 文章 :: 19 评论 :: 0 引用

2009年6月17日 #

在最新一期的.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 @ 2009-06-17 17:34 张羿 阅读(13) | 评论 (0)编辑

     摘要: .NET Framework v4.0和VisualStudio 2010 Beta1已经出来有阵子了,估计有些喜欢尝鲜的朋友已经下载试用了。这一次发布包含了大量的新功能。我们上海CLR开发团队会编写一系列的文章介绍Interop的相关新功能。我来给大家简单介绍一下Stub Method Redirection功能。这个功能是CLR上海开发团队设计、开发并测试的新功能之一,这一次我们上海CLR小组... 阅读全文
posted @ 2009-06-17 13:42 张羿 阅读(1261) | 评论 (10)编辑

2009年4月3日 #

最近在网上发现一个小程序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 @ 2009-04-03 23:14 张羿 阅读(15) | 评论 (0)编辑

2009年4月2日 #

不知道各位使用.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 @ 2009-04-02 23:45 张羿 阅读(12) | 评论 (0)编辑

2009年3月30日 #

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

posted @ 2009-03-30 23:13 张羿 阅读(22) | 评论 (0)编辑

前段时间花了几天一直在用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 @ 2009-03-30 22:20 张羿 阅读(51) | 评论 (0)编辑

2009年3月16日 #

这次我将为大家讲解如何使用.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 @ 2009-03-16 22:32 张羿 阅读(52) | 评论 (0)编辑

大家好!距离上次我们发布在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 @ 2009-03-16 22:23 张羿 阅读(19) | 评论 (0)编辑

2009年2月16日 #

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

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

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

讲 师:张羿 

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

推荐指数:

posted @ 2009-02-16 21:51 张羿 阅读(14) | 评论 (0)编辑

2009年1月7日 #

从字面上看,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 @ 2009-01-07 23:11 张羿 阅读(5) | 评论 (0)编辑