原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3505936
在跟踪调试 CLR 代码,或者编写 CLR 宿主 (Host) 代码时,经常会碰到一些 CLR 本身返回的错误代码,如 0x80131010。这些 HRESULT 代码表示 CLR 的某种内部错误状态,虽然可以通过 .NET Framework SDK 的 CorError.h 文件反查到,但每次还需要手工将之转换为 16 进制数,再截取低 16 位的值,才能找到相应的错误代码定义,得到一个并不详细的注释信息。特别是我在同时对多个版本 CLR 进行调试跟踪时,手工查阅非常繁琐,而 VC 自带的那个 Error Lookup 程序,又无法正常处理 CLR 的异常信息。
以下将简单介绍如何实现一个自动针对不同 CLR 版本,查找对应错误代码的 CLR Error Lookup 程序的实现思路。
程序整体思路很简单:
1.获取当前机器上安装的多个不同版本 CLR 的版本号,以及相应的安装路径
2.根据用户选择的 CLR 版本号,载入相应的错误描述信息资源文件
3.根据用户输入的错误代码,查找并显式相应的错误描述信息
首先,我们来看看如何获取当前机器上安装的多个不同版本 CLR 的版本号,以及相应的安装路径。
注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework 中保存着绝大多数机器一级的 CLR 安装信息。其中 InstallRoot 的值就是 .NET Framework 安装的根目录,例如我机器上 InstallRoot = "E:\WINDOWS\Microsoft.NET\Framework\",在此目录下,分别安装了 v1.0.3705/v1.1.4322/v2.0.40607 三个版本的 CLR 环境。
获取这些具体版本信息的方法,既可以通过对 InstallRoot 子目录名遍历,也可以通过更为稳妥的注册表项来获取。注册表键 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy 下会分别为每个 Major.Minor 版本号的 CLR 建立一个子键,保存当前安装的 CLR 版本和此版本适用于的版本范围。例如在 v1.1 这个子键中内容如下:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\v1.1]
"4322"="3706-4322"
其意义是当前安装的 v1.1 版本的 CLR 运行时环境,其 build 号为 4322,并且兼容所有 Build 号为 3706-4322 的 v1.1 版本。也就是说在 InstallRoot 目录下,将有一个 v1.1.4322 的目录保存着我们所需要的 CLR 支持文件。获取这些信息的代码如下:
在读取注册表失败的情况下,上述代码会通过 BCL 提供的 RuntimeEnvironment 获取当前 CLR 的相关路径信息,保障最少有一种 CLR 信息可以获取。这儿的 RuntimeEnvironment.GetRuntimeDirectory() 调用将返回完整的 CLR 安装路径,如 E:\WINDOWS\Microsoft.NET\Framework\v1.1.4322;而 RuntimeEnvironment.GetSystemVersion() 调用将返回 CLR 的版本信息,如 v1.1.4322。
其次,在获取了版本列表和安装信息后,需要对用户的版本选择进行响应,载入相应版本和相应本地化语言的错误描述信息资源文件。
此资源文件名为 mscorrc.dll,一般在 CLR 的安装目录下会有一个语言无关的版本 (英语),而在相应语言目录中会有本地化后的版本 (中文)。要处理这个载入策略,就需要通过 CultureInfo.CurrentUICulture 获取当前的本地化信息。CurrentUICulture.Name 和 CurrentUICulture.Parent.Name 将分别返回当前语言及其父语言的简单名称,如 zh-CN 及其父语言 zh-CHS。程序需要优先尝试是否有本地化的资源文件,如果没有才载入语言无关的版本。载入错误信息描述资源文件的代码如下:
上述代码通过一个 foreach 循环,遍历可能的三个存储位置,将发现的第一个资源文件,通过 LoadLibrary 函数载入。
此外根据 Junfeng Zhang 的建议,这儿增加了一个 CLR hack code。在几种策略都没有找到资源文件时,通过执行一个肯定不会成功的 CLR 操作,强迫 CLR 自动载入 mscorrc.dll 以抛出异常。然后在 LoadLibrary 时就无需指定完整路径,由操作系统自动根据同名原则加载相应的资源文件。
这儿的文件定位策略可以参考 rotor 中 CCompRC::LoadResourceLibrary (utilcode/posterror.cpp:317) 的实现。
最后,解析用户输入的错误代码,根据用户输入查找并显式相应的错误描述信息。
解析输入时,如果用户输入以 0x 开头,则直接以 16 进制解析解析;否则将首先尝试以 10 进制解析,失败后再尝试 16 机制解析。代码如下:
多解析成功的错误代码,调用 GetErrorMessage 函数,根据其错误代码分类来判断去哪儿获取错误描述信息。对 CLR 来说,所有错误信息 HRESULT 值的严重程度(SEVERITY)都是 SEVERITY_ERROR,而其设备分类号(FACILITY)都是 0x13。获取错误描述信息代码如下:
对 CLR 的信息,从刚刚载入的 mscorrc.dll 中通过 LoadString 获取错误信息;而对于其他的错误代码,则通过构造一个新的 Win32Exception 异常对象,然后借助 Win32Exception.Message 的实现代码,最终通过 FormatMessage 系统调用完成消息的获取和格式化工作。
完整的代码和编译后程序可以从这里下载:
CLR Error Lookup
不过据 Junfeng Zhang 透露,CLR 2.0 中可能会对类似功能提供直接的支持。我注意到 CLR 2.0 beta 中,在 mscoree.dll 中提供了 LoadStringRC/LoadStringRCEx 函数,将前面提到的 CCompRC 类的功能提供给最终使用者。通过这两个函数,可以很容易地实现上述功能,因为大部分工作,如 mscorrc.dll 的载入,都由 CLR 自动完成了。
期待 CLR 2.0 ... :P
在跟踪调试 CLR 代码,或者编写 CLR 宿主 (Host) 代码时,经常会碰到一些 CLR 本身返回的错误代码,如 0x80131010。这些 HRESULT 代码表示 CLR 的某种内部错误状态,虽然可以通过 .NET Framework SDK 的 CorError.h 文件反查到,但每次还需要手工将之转换为 16 进制数,再截取低 16 位的值,才能找到相应的错误代码定义,得到一个并不详细的注释信息。特别是我在同时对多个版本 CLR 进行调试跟踪时,手工查阅非常繁琐,而 VC 自带的那个 Error Lookup 程序,又无法正常处理 CLR 的异常信息。
以下将简单介绍如何实现一个自动针对不同 CLR 版本,查找对应错误代码的 CLR Error Lookup 程序的实现思路。
程序整体思路很简单:
1.获取当前机器上安装的多个不同版本 CLR 的版本号,以及相应的安装路径
2.根据用户选择的 CLR 版本号,载入相应的错误描述信息资源文件
3.根据用户输入的错误代码,查找并显式相应的错误描述信息
首先,我们来看看如何获取当前机器上安装的多个不同版本 CLR 的版本号,以及相应的安装路径。
注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework 中保存着绝大多数机器一级的 CLR 安装信息。其中 InstallRoot 的值就是 .NET Framework 安装的根目录,例如我机器上 InstallRoot = "E:\WINDOWS\Microsoft.NET\Framework\",在此目录下,分别安装了 v1.0.3705/v1.1.4322/v2.0.40607 三个版本的 CLR 环境。
获取这些具体版本信息的方法,既可以通过对 InstallRoot 子目录名遍历,也可以通过更为稳妥的注册表项来获取。注册表键 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy 下会分别为每个 Major.Minor 版本号的 CLR 建立一个子键,保存当前安装的 CLR 版本和此版本适用于的版本范围。例如在 v1.1 这个子键中内容如下:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\v1.1]
"4322"="3706-4322"
其意义是当前安装的 v1.1 版本的 CLR 运行时环境,其 build 号为 4322,并且兼容所有 Build 号为 3706-4322 的 v1.1 版本。也就是说在 InstallRoot 目录下,将有一个 v1.1.4322 的目录保存着我们所需要的 CLR 支持文件。获取这些信息的代码如下:
|
在读取注册表失败的情况下,上述代码会通过 BCL 提供的 RuntimeEnvironment 获取当前 CLR 的相关路径信息,保障最少有一种 CLR 信息可以获取。这儿的 RuntimeEnvironment.GetRuntimeDirectory() 调用将返回完整的 CLR 安装路径,如 E:\WINDOWS\Microsoft.NET\Framework\v1.1.4322;而 RuntimeEnvironment.GetSystemVersion() 调用将返回 CLR 的版本信息,如 v1.1.4322。
其次,在获取了版本列表和安装信息后,需要对用户的版本选择进行响应,载入相应版本和相应本地化语言的错误描述信息资源文件。
此资源文件名为 mscorrc.dll,一般在 CLR 的安装目录下会有一个语言无关的版本 (英语),而在相应语言目录中会有本地化后的版本 (中文)。要处理这个载入策略,就需要通过 CultureInfo.CurrentUICulture 获取当前的本地化信息。CurrentUICulture.Name 和 CurrentUICulture.Parent.Name 将分别返回当前语言及其父语言的简单名称,如 zh-CN 及其父语言 zh-CHS。程序需要优先尝试是否有本地化的资源文件,如果没有才载入语言无关的版本。载入错误信息描述资源文件的代码如下:
|
上述代码通过一个 foreach 循环,遍历可能的三个存储位置,将发现的第一个资源文件,通过 LoadLibrary 函数载入。
此外根据 Junfeng Zhang 的建议,这儿增加了一个 CLR hack code。在几种策略都没有找到资源文件时,通过执行一个肯定不会成功的 CLR 操作,强迫 CLR 自动载入 mscorrc.dll 以抛出异常。然后在 LoadLibrary 时就无需指定完整路径,由操作系统自动根据同名原则加载相应的资源文件。
这儿的文件定位策略可以参考 rotor 中 CCompRC::LoadResourceLibrary (utilcode/posterror.cpp:317) 的实现。
最后,解析用户输入的错误代码,根据用户输入查找并显式相应的错误描述信息。
解析输入时,如果用户输入以 0x 开头,则直接以 16 进制解析解析;否则将首先尝试以 10 进制解析,失败后再尝试 16 机制解析。代码如下:
|
多解析成功的错误代码,调用 GetErrorMessage 函数,根据其错误代码分类来判断去哪儿获取错误描述信息。对 CLR 来说,所有错误信息 HRESULT 值的严重程度(SEVERITY)都是 SEVERITY_ERROR,而其设备分类号(FACILITY)都是 0x13。获取错误描述信息代码如下:
|
对 CLR 的信息,从刚刚载入的 mscorrc.dll 中通过 LoadString 获取错误信息;而对于其他的错误代码,则通过构造一个新的 Win32Exception 异常对象,然后借助 Win32Exception.Message 的实现代码,最终通过 FormatMessage 系统调用完成消息的获取和格式化工作。
完整的代码和编译后程序可以从这里下载:
CLR Error Lookup
不过据 Junfeng Zhang 透露,CLR 2.0 中可能会对类似功能提供直接的支持。我注意到 CLR 2.0 beta 中,在 mscoree.dll 中提供了 LoadStringRC/LoadStringRCEx 函数,将前面提到的 CCompRC 类的功能提供给最终使用者。通过这两个函数,可以很容易地实现上述功能,因为大部分工作,如 mscorrc.dll 的载入,都由 CLR 自动完成了。
|
期待 CLR 2.0 ... :P