解读 Windows 错误码——Win32 错误、HRESULT、NTSTATUS 的三层结构

· 更新日期: · · Windows, 错误码, HRESULT, NTSTATUS, Win32 API, 故障排查, 调试, Windows 开发

更新记录(仅首版,2026年08月20日 发布)
首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22176057)

以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。

Go Komura(2026)。《解读 Windows 错误码——Win32 错误、HRESULT、NTSTATUS 的三层结构》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/windows-error-codes-win32-hresult-ntstatus/

DOI(已登记存档)
10.5281/zenodo.22176057
DOI(上次登记版本)
10.5281/zenodo.22176058

“应用程序界面上出现了 0x80004005 这个错误,是什么意思?”——这是故障排查中的经典咨询。把数字原样拿去搜索,排在眼前的是 Windows Update、共享文件夹、VBA、数据库连接等互不相干场景的处理办法。

搜索结果之所以分散,是因为 0x80004005(E_FAIL)是一个只表示“详情不明的失败”的通用代码。相比之下,0x80070005 可以拆解成“把 Win32 错误的第 5 号=拒绝访问重新封装进 HRESULT 的结果”。外观相似,但能从代码里读出的信息量并不相同。

Windows 中存在 Win32 错误码、HRESULT、NTSTATUS 三套体系,代码跨层传递时会被转换。想让排查更快,与其死记数字,不如学会分辨“是哪一层的谁返回的”“重新封装之前的代码是什么”。

本文是面向中小企业信息系统负责人和 Windows 应用开发者的实务指南。依据 2026 年 8 月时点的 Microsoft Learn 与公开规范 [MS-ERREF],依次串起三套体系的分辨方法、HRESULT 的拆解、与 .NET 异常的关系,以及用 err.exe 和 PowerShell 查询的做法。

1. 先说结论

首先要掌握的是以下三点。

  1. 统一记法,分辨代码体系。Win32 错误码、HRESULT、NTSTATUS 是彼此独立的体系。十进制和十六进制是同一个值的不同记法,HRESULT 还可能以带符号的负数显示。先统一成 8 位十六进制,再结合返回它的 API 和它出现的位置来解读。123
  2. 0x8007xxxx 要拆解,E_FAIL 则转向上下文排查。0x80070005 是 FACILITY_WIN32(7)的 HRESULT,低 16 位为 5=ERROR_ACCESS_DENIED。而 0x80004005(E_FAIL)表示“未指定的失败”,仅凭代码无法缩小原因范围。456
  3. 代码的含义要和失败的操作、对象成对排查。同样是错误 5,原因可能是 ACL、权限提升、安全软件等等。错误 2 的对象有时是依赖 DLL,文件被占用则会表现为错误 32。查到名称之后,就要借助日志和 Process Monitor 推进到“哪个 API 对什么失败了”。1

工具方面,按场合选用 Windows 自带的 certutil -error 和 net helpmsg、PowerShell 的 Win32Exception、开发机上的 err.exe,以及转储分析过程中 WinDbg 的 !error。789 即使已经变成 .NET 异常,原始的 HRESULT 仍可从 Exception.HResult 追溯。10

按目的选择读法

想了解的内容 阅读的章节
想分辨手上的代码并立刻查询 先用第 2 章统一记法,再到第 7 章的命令与第 8 章的排查步骤
想把 Win32 API 的失败正确记入日志 第 3 章的 GetLastError 与 FormatMessage、6.3 节的 P/Invoke
想理解 0x80004005、0x80070005 与 .NET 异常的关系 第 4 章的 HRESULT 拆解、第 6 章的 COM 与 .NET
想查 0xC0000005 等崩溃相关的代码 第 5 章的 NTSTATUS 与停止代码、7.4 节的 WinDbg
想避开排查中常见的混淆 第 9 章的误读示例

一句话概括,Windows 错误码排查的套路就是“把记法统一成十六进制→判定属于哪一层的代码→拆解出本质代码→结合上下文解读”。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 18 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. Windows 有三套错误码体系

先看整体。即使数值相同,作为哪套体系的值传过来,读法也不一样。把主要的返回方和典型外观排在一起,就是下面这张表。

体系 主要返回方 典型外观 代表示例
Win32 错误码 Win32 API(GetLastError)、命令的退出码 较小的十进制数(0~15999) 5 = ERROR_ACCESS_DENIED
HRESULT COM 组件、Shell、安装程序、许多框架 以 0x8 开头的 8 位十六进制,或十进制负数 0x80004005 = E_FAIL
NTSTATUS 内核、驱动程序、本机 API(ntdll) 错误是以 0xC 开头的 8 位十六进制 0xC0000005 = STATUS_ACCESS_VIOLATION

表中的“外观”只是寻找体系的线索。它和第 8 章的判定表一样,只能当作首选假设,还要与返回它的 API 或组件信息相互印证。

从历史上看,继承 MS-DOS 编号的 Win32 错误码、NT 内核内部的 NTSTATUS,以及引入 COM 时为把成功/失败和发生源塞进 32 位而设计的 HRESULT,就这样层层叠加了下来。

在现在的 Windows 上,内核的 NTSTATUS→Win32 错误码→COM 层的 HRESULT 这样的转换每天都在发生。从上层看到的代码反向追溯到下层,是排查的基本功。114

跨三套体系的转换流程内核返回的 NTSTATUS 由 Win32 子系统转换为 Win32 错误码,COM 层再把它重新封装为 HRESULTWin32 子系统转换COM 层重新封装内核与驱动程序NTSTATUS(错误以 0xC… 开头)Win32 错误码(例如 5)HRESULT(0x8007xxxx)

图1:跨层转换的流程。内核的 NTSTATUS 变成 Win32 错误,再被重新封装为 HRESULT。

2.1. 熟悉十进制与十六进制的互换

判断体系之前,先消除记法上的差异。因为同一个代码会以十进制、十六进制、带符号负数三种形式显示。

显示出的值 换成另一种记法 含义
错误 5 0x5 ERROR_ACCESS_DENIED
错误 1223 0x4C1 ERROR_CANCELLED
0x80070005 -2147024891 同一个 HRESULT

用 PowerShell 可以像下面这样一行完成换算。

# 十进制 → 十六进制
'0x{0:X8}' -f 1223          # 0x000004C1
'0x{0:X8}' -f -2147024891   # 0x80070005(负数=把 HRESULT 转成十六进制)

# 十六进制 → 十进制
0x4C1                        # 1223

看到以“-214…”开头的十进制负数,就条件反射地转成十六进制。仅此一条,就能大幅减少排查开局阶段的迷路。

同一个代码的三种外观十进制的错误 5、十六进制的 0x5 以及 0x80070005 的低 16 位,都指向同一个 ERROR_ACCESS_DENIED十进制记法“错误 5”ERROR_ACCESS_DENIED十六进制记法“0x5”0x80070005 的低 16 位只是记法不同,代码相同

图2:十进制、十六进制与 HRESULT 的低 16 位,只是同一个代码的不同记法。

3. Win32 错误码——GetLastError 与 FORMAT_MESSAGE

3.1. GetLastError 的基本行为

先确认返回值,再保存最后错误

CreateFile 等许多 Win32 API 用返回值(FALSE、NULL、INVALID_HANDLE_VALUE 等)表示失败,而把详细信息留在每个线程各自的“最后错误代码”里。对这种方式的 API,要在确认失败之后立刻用 GetLastError 取值。12

不过,取错误的方式要逐个 API 确认。例如 RegOpenKeyEx 用的不是最后错误,而是返回值本身就是错误码。请把它与使用 GetLastError 的例子区分开来。13

使用 GetLastError 时有以下两点需要注意。12

  1. 要在失败之后立刻读取。中间若插入别的 API 调用(例如日志输出函数),那次调用可能会覆盖最后错误代码。
  2. 不要指望成功时的值。有的 API 在成功时会把最后错误代码清零,有的则根本不碰它。原则是先用返回值确认失败,再读取。
GetLastError 要在失败之后立刻读取用返回值确认失败后不要插入其他 API 调用,立刻用 GetLastError 取得最后错误代码Win32 API应用程序Win32 API应用程序中间插入别的 API 就可能被覆盖调用 CreateFile表示失败的返回值GetLastError代码 5

图3:最后错误代码要在失败之后立刻读取。中间插入别的 API 调用就可能被覆盖。

把保存下来的代码连同消息一起写进日志

要从代码取得消息字符串,使用带 FORMAT_MESSAGE_FROM_SYSTEM 标志的 FormatMessage。顺序是先保存代码,之后再转成字符串。1

#include <windows.h>
#include <stdio.h>

void PrintLastError(const wchar_t* apiName)
{
    DWORD code = GetLastError();   // 失败后立刻调用(中间不插入其他 API)
    wchar_t message[512] = L"";
    FormatMessageW(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr, code, 0, message, 512, nullptr);
    wprintf(L"%s failed: %lu (0x%08lX) %s", apiName, code, code, message);
}

在自研应用的日志里,像这样同时留下十进制、十六进制和消息正文,日后排查会快上一个档次。

由代码查出消息并写进日志给 FormatMessage 指定 FORMAT_MESSAGE_FROM_SYSTEM 标志取得错误码的消息字符串,日志里同时留下十进制、十六进制和消息正文错误码(例如 5)用 FormatMessage 取得字符串消息正文记入日志十进制、十六进制与正文并列

图4:错误码用 FormatMessage 转换成消息字符串,日志里并列留下十进制、十六进制与正文。

3.2. 现场高频出现的代表性代码

Win32 错误码定义在 0~15999 的范围内,Microsoft Learn 上有完整清单。1 其中在故障排查中反复遇到的是下面这几位常客。

十进制 十六进制 符号名 含义
2 0x2 ERROR_FILE_NOT_FOUND 找不到指定的文件
3 0x3 ERROR_PATH_NOT_FOUND 找不到指定的路径
5 0x5 ERROR_ACCESS_DENIED 拒绝访问
32 0x20 ERROR_SHARING_VIOLATION 另一个进程正在使用,无法访问
87 0x57 ERROR_INVALID_PARAMETER 参数不正确
122 0x7A ERROR_INSUFFICIENT_BUFFER 传入的缓冲区太小
998 0x3E6 ERROR_NOACCESS 对内存位置的访问无效
1223 0x4C1 ERROR_CANCELLED 操作已被用户取消

这里最容易混淆的是 5 号和 998 号。998(ERROR_NOACCESS)不是“拒绝访问”,而是内存访问冲突在 Win32 层的表述。它相当于后文提到的 NTSTATUS 的 STATUS_ACCESS_VIOLATION 被转换到 Win32 层之后的样子。

另外,1223(ERROR_CANCELLED)会在用户于 UAC 提升对话框中选择“否”等场合出现。不要把它单纯当成故障,而应读作表示操作“被中止”的代码。

错误 998 和 5 是两回事998 是 NTSTATUS 的访问冲突被转换到 Win32 层后的内存访问冲突,与表示拒绝访问的 5 含义不同转换到 Win32 层NTSTATUS 0xC0000005错误 998(ERROR_NOACCESS)含义是内存访问冲突错误 5(拒绝访问)权限问题。与 998 是两回事

图5:错误 998 是 NTSTATUS 的访问冲突转换到 Win32 层后的样子,与表示拒绝访问的 5 是两回事。

3.3. 同一个代码在不同上下文里含义不同

只记住代表性代码,是定位不了原因的。代码能告诉你的只到“失败的种类”为止,再往前还留着需要排查的对象。

代码 随上下文变化的部分 接下来要确认的事
错误 5(拒绝访问) NTFS 的 ACL 不足、没有管理员权限却写入受保护区域、杀毒软件或 AppLocker 的拦截、服务账户权限不足等 是哪个 API 对哪个对象的访问被拒绝
错误 2(找不到文件) 未必是指定的文件,也可能是隐式加载的依赖 DLL、因 32 位/64 位注册表重定向而看向别处的配置文件、环境变量展开失败的路径等 究竟是“哪个文件”没找到
错误 32(共享冲突) 另一个进程正在使用目标对象 是“哪个进程”占着它
错误 5 的原因由上下文决定同样是拒绝访问,候选原因也有 ACL 不足、没有管理员权限等多种,必须确定是哪个 API 对什么失败错误 5(拒绝访问)ACL 不足没有管理员权限安全软件拦截服务权限不足用 Procmon 确定失败的对象

图6:代码只能告诉你“失败的种类”。错误 5 的候选原因有多个,必须确定具体对象。

能实测“哪个 API、对哪个对象名、返回了什么结果”的工具是 Process Monitor。用法在“Process Monitor(ProcMon)实战指南”里有详细介绍。查错误码含义的工作,和确定失败对象的工作,请当作车的两个轮子。

4. HRESULT——读懂塞进 32 位里的结构

4.1. 位布局

HRESULT 是把成功/失败、发生源、详细代码塞进一个 32 位值的格式。先按S 位看成功还是失败、Facility 看发生源、Code 看详情的顺序读,后面的拆解示例就容易跟上。公开规范 [MS-ERREF] 给出的布局如下。2

位位置 名称 含义
31 S 严重性。0=成功,1=失败
30 R 保留(映射 NTSTATUS 时属于严重性的一部分)
29 C Customer 位。为 1 表示由 Microsoft 以外的一方定义的代码
28 N 为 1 表示该值是把 NTSTATUS 映射到 HRESULT 空间的结果
27 X 保留(0)
26–16 Facility 表示发生源的设施码(11 位)
15–0 Code 设施内部的详细代码(16 位)

最高位的 S 位为 1,也就是说十六进制以 0x8 及以上开头的 HRESULT 都是失败。把它按带符号 32 位整数显示就会变成负数,这正是前面那个“-214…”的真身。

S 位与负数显示的关系失败的 HRESULT 最高位的 S 位为 1,因此十六进制以 0x8 及以上开头,按带符号 32 位整数显示时会变成负数S 位=1(失败)十六进制以 0x8 及以上开头带符号显示时变成负数看到负数就转成十六进制再读

图7:失败的 HRESULT 因 S 位为 1 而以 0x8 及以上开头,带符号显示时是负数。

Facility 是下一步该查哪份资料的线索

Facility 的代表值如下。7 表示 Win32 错误的重新封装,4 表示按接口各自定义,据此分辨。5

Facility 值 十六进制外观 含义
FACILITY_NULL 0 0x8000xxxx 广泛通用的代码(E_FAIL、E_UNEXPECTED 等)
FACILITY_RPC 1 0x8001xxxx 来自 RPC
FACILITY_ITF 4 0x8004xxxx 接口定义的错误(含义取决于接口)
FACILITY_WIN32 7 0x8007xxxx Win32 错误码的重新封装
FACILITY_WINDOWS 8 0x8008xxxx Microsoft 定义的追加接口

4.2. 试着拆解 0x80004005 与 0x80070005

把这两个看起来相似的代码拆成 S、Facility、Code 来比较。

读取的项目 0x80004005 0x80070005
S 1(失败) 1(失败)
Facility 0(FACILITY_NULL) 7(FACILITY_WIN32)
Code 0x4005 0x0005=5
定义 E_FAIL:未指定的失败 E_ACCESSDENIED:拒绝访问
下一步排查 查返回它的组件和伴随的日志 按 Win32 错误 5 查被拒绝的操作与对象

0x80004005 仅凭代码无法缩小原因范围。它的 Facility 是 (0x80004005 >> 16) & 0x7FF = 0,属于 FACILITY_NULL 的通用代码 E_FAIL。由于它不带“未指定的失败(Unspecified failure)”以外的任何细节,拆解完就要把排查重心转向“是哪个组件返回的”“同一时刻的事件日志、应用日志里有没有更详细的信息”。6

0x80070005 可以一直追到 Win32 错误 5。Facility=7、Code=5,因此可知它是把 ERROR_ACCESS_DENIED 重新封装进 HRESULT 的值。别名 E_ACCESSDENIED 指的也是同一个值。6

同样是“失败”,拆解后得到的信息量并不相同。不要一口咬定 E_FAIL 就是拒绝访问,而要根据返回的值选择下一步排查。

0x80004005 与 0x80070005 的拆解0x80004005 是 FACILITY_NULL 的通用代码 E_FAIL,不带细节,应转向上下文排查,而 0x80070005 属于 FACILITY_WIN32,可知是 Win32 错误第 5 号拒绝访问的重新封装0x80004005Facility=0(FACILITY_NULL)Code=0x4005 → E_FAIL未指定的失败。转向上下文排查0x80070005Facility=7(FACILITY_WIN32)Code=0x0005 → 5ERROR_ACCESS_DENIED

图8:同样是“失败”,拆解后的信息量却不同。0x80070005 能一直追到 Win32 错误第 5 号。

4.3. 最重要的模式:0x8007xxxx = HRESULT_FROM_WIN32

把 Win32 的失败重新封装成 HRESULT

为了把下层的 Win32 错误传给返回 HRESULT 的 COM 方法和 .NET 运行时,winerror.h 中准备了 HRESULT_FROM_WIN32。在下面这些失败代码的例子里,它把 Win32 错误放进低 16 位、Facility 置为 7、S 位置为 1。4

HRESULT_FROM_WIN32 的行为把 Win32 错误码存进低 16 位,并把 Facility 设为 7、S 位设为 1,组装出 0x8007xxxx 形式的 HRESULTWin32 错误码(例如 5)存进低 16 位Facility 设为 7S 位设为 10x80070005

图9:HRESULT_FROM_WIN32 把 Win32 错误存进低 16 位,并置起 Facility=7 与 S 位。

ERROR_ACCESS_DENIED (5)        --HRESULT_FROM_WIN32-->  0x80070005
ERROR_SHARING_VIOLATION (32)   --HRESULT_FROM_WIN32-->  0x80070020
ERROR_INVALID_PARAMETER (87)   --HRESULT_FROM_WIN32-->  0x80070057 (= E_INVALIDARG)
ERROR_OUTOFMEMORY (14)         --HRESULT_FROM_WIN32-->  0x8007000E (= E_OUTOFMEMORY)

反向解读时,取出低 16 位

要从 0x8007xxxx 读出原来的 Win32 错误,可以用 PowerShell 取出低 16 位。

0x80070005 -band 0xFFFF   # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF   # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)

像第二个例子那样,WinINet 和 WinHTTP 的错误(12000 号段)也定义在 Win32 错误码空间里1,所以网络相关的 0x8007xxxx 同样能用这个步骤拆解。让身体记住“看到 0x8007 就把低 4 位十六进制转成十进制”,是本文最希望你带走的一项实务技能。

0x8004xxxx 要去查返回它的组件的资料

不能把同样的读法原样套到 0x8004xxxx(FACILITY_ITF)上。这一类定义含义的主体因接口而异,所以即便是同一个 32 位值,返回方不同含义也可能不同。5

遇到陌生的 0x8004xxxx,不要只凭泛泛的搜索下结论,请到返回它的组件(库、驱动程序 SDK、服务器产品)的文档里去查。

0x8007 与 0x8004 的查法不同FACILITY_WIN32 的 0x8007xxxx 可以靠机械地拆出低 16 位来读,而 FACILITY_ITF 的 0x8004xxxx 因定义含义的主体因接口而异,要到返回它的组件资料里查7 的 WIN324 的 ITFFacility 是多少?把低 16 位转成十进制含义因返回方而异按 Win32 错误解读查返回方的资料

图10:0x8007xxxx 可以机械地拆解,0x8004xxxx 则要去查返回它的组件的资料。

5. NTSTATUS——内核层的代码与崩溃的世界

5.1. 布局与 Severity

NTSTATUS 是内核、设备驱动程序和 ntdll 的本机 API 使用的 32 位代码,布局与 HRESULT 貌似而实不同。3

位位置 名称 含义
31–30 Sev 严重性。00=成功,01=信息,10=警告,11=错误
29 C Customer 位
28 N 保留(为了能映射到 HRESULT 而置 0)
27–16 Facility 设施码(12 位)
15–0 Code 详细代码

与 HRESULT 最大的区别在于,严重性占 2 位,分成功、信息、警告、错误四种。从十六进制的首位看,0xC… 对应错误(11),0x8… 对应警告(10),0x4… 对应信息(01),0x0~0x3… 对应成功。

因此,不能仅凭以 0x8 开头这一外观就断定它是失败的 HRESULT。NTSTATUS 的 0x80000003(STATUS_BREAKPOINT)是断点异常,严重性不是“错误”而是“警告”。314

NTSTATUS 看首位就能分辨类别由于严重性占 2 位,NTSTATUS 可以按十六进制首位分辨,0xC 为错误,0x8 为警告,0x4 为信息,0x0 到 0x3 为成功0xC0x80x40x0~0x3十六进制首位是几?错误警告信息成功例如 0x80000003 是警告

图11:NTSTATUS 看十六进制首位就能分辨类别。0x80000003 是“警告而非错误”。

5.2. 会在哪里遇到——异常代码、停止代码与事件日志

下面分异常代码、停止代码和 Process Monitor 三处,来看会在哪里遇到 NTSTATUS。

应用程序崩溃的异常代码

事件日志的“Application Error(事件 ID 1000)”里记录的“异常代码: 0xc0000005”就是 NTSTATUS。在崩溃和启动失败时常见的代表值有下面这些。14

值 符号名 含义
0xC0000005 STATUS_ACCESS_VIOLATION 访问冲突(非法的内存访问)
0xC0000135 STATUS_DLL_NOT_FOUND 找不到所需的 DLL,无法启动
0xC00000FD STATUS_STACK_OVERFLOW 堆栈溢出
0xC0000374 STATUS_HEAP_CORRUPTION 堆损坏

蓝屏的停止代码属于另一套体系

停止代码(错误检查代码)乍看相似,但像 0x0000009F(DRIVER_POWER_STATE_FAILURE)那样,属于与 NTSTATUS 不同的独立编号体系。要查专门的参考资料。15

关键是把“0xC0000005 属于 NTSTATUS,STOP 0x9F 属于错误检查代码”分开,不要拿 NTSTATUS 的表去查停止代码。

在 Process Monitor 里出现在 Result 列

Procmon 的 Result 列中出现的 NAME NOT FOUND、ACCESS DENIED,是内核返回的 NTSTATUS(STATUS_OBJECT_NAME_NOT_FOUND、STATUS_ACCESS_DENIED)的显示名。在这里可以用 NTSTATUS 的词汇观测文件 I/O 的失败,并确认它被转换成 Win32 错误后送达应用程序这一层与层之间的对应关系。

异常代码与停止代码的分辨事件日志里的异常代码按 NTSTATUS 解读,蓝屏的停止代码则属于另一套体系,要查错误检查代码的专用参考资料异常代码停止代码代码出现在哪里?按 NTSTATUS 解读查错误检查代码的表例如 0xC0000005例如 0x0000009F

图12:事件日志的异常代码是 NTSTATUS,蓝屏的停止代码是另一套体系。不要查错表。

异常代码之后的排查,也就是崩溃转储的采集与分析,请参考“Windows 崩溃转储收集入门”和“用 WinDbg + SOS 解读崩溃转储”。

5.3. 与 HRESULT 的关系——N 位与 RtlNtStatusToDosError

从 NTSTATUS 传给其他体系的路径有两条。映射到 HRESULT 和转换成 Win32 错误要分开来考虑。

路径 做的事 示例
映射到 HRESULT 空间 用 HRESULT_FROM_NT 置起 N 位(0x10000000) 0xC0000005 → 0xD0000005
转换成 Win32 错误 用 RtlNtStatusToDosError 转成对应的编号 STATUS_ACCESS_VIOLATION → ERROR_NOACCESS(998)

映射到 HRESULT 时,通过置起 N 位把 NTSTATUS 的值带进 HRESULT 空间。因此,以 0xD 开头的 HRESULT 要先去掉 N 位,再按 NTSTATUS 解读,这是固定步骤。2

转换成 Win32 错误时使用 ntdll 的 RtlNtStatusToDosError。没有对应关系的值会变成 ERROR_MR_MID_NOT_FOUND。11 0xC0000005 会转成 998,STATUS_OBJECT_NAME_NOT_FOUND(0xC0000034)会转成 ERROR_FILE_NOT_FOUND(2)。内核侧丰富的词汇到了 Win32 层有时会被归并到更粗的分类里,所以要区分转换前后来解读。

NTSTATUS 通向其他层的两座桥NTSTATUS 通过置起 N 位映射到 HRESULT 空间,或通过 RtlNtStatusToDosError 转换成 Win32 错误码,以这两条路径传给其他层置起 N 位RtlNtStatusToDosErrorNTSTATUS(0xC0000005)HRESULT(0xD0000005)Win32 错误 998(ERROR_NOACCESS)没有定义对应关系时返回 ERROR_MR_MID_NOT_FOUND

图13:NTSTATUS 的桥接有两条。以 0xD 开头的值要去掉 N 位,按 NTSTATUS 解读。

6. COM 与 .NET——错误码如何映射成异常

6.1. COM 的做法——HRESULT + IErrorInfo

COM 的方法基本上都返回 HRESULT。不过,仅靠 32 位的代码能传递的信息是有限的。

补足这一点的是 IErrorInfo。它可以另行传递错误的说明字符串和发生源,在 C++ 中由编译器支持的 _com_error 类把 HRESULT 和 IErrorInfo 一并处理。对话框里显示“代码+说明文字”的应用程序,多半就是靠这个机制来搬运说明文字的。

补足 HRESULT 的 IErrorInfo32 位的 HRESULT 能装下的信息有限,因此错误的说明字符串和发生源由 IErrorInfo 另行传递,在 C++ 中由 _com_error 类把两者一并处理HRESULT(仅 32 位)能装下的信息有限IErrorInfo 搬运说明文字_com_error 一并处理对话框里的代码+说明文字

图14:装不进 32 位 HRESULT 的说明字符串,由 IErrorInfo 另行搬运。

6.2. .NET 的做法——从 HRESULT 到异常类型

.NET 运行时在 COM 互操作中收到失败的 HRESULT 时,会把它转换成异常。已知的 HRESULT 会映射到对应的异常类型,未知的则变成 COMException。10

从 HRESULT 到 .NET 异常的映射COM 互操作中收到的失败 HRESULT,已知的转换成对应的异常类型,未知的转换成 COMException,两种情况下原始值都保留在 Exception.HResult 中有没有失败的 HRESULT有已知的映射吗?转换成对应的异常类型转换成 COMException原始值保留在 Exception.HResult

图15:.NET 把 HRESULT 映射成异常类型,无论哪种异常,原始值都留在 Exception.HResult 里。

HRESULT .NET 的异常类型
E_ACCESSDENIED (0x80070005) UnauthorizedAccessException
E_OUTOFMEMORY (0x8007000E) OutOfMemoryException
E_INVALIDARG (0x80070057) ArgumentException
E_NOTIMPL (0x80004001) NotImplementedException
未定义映射的值 COMException(原始值在 ErrorCode 属性中)

用原始的 HRESULT 给异常处理分支

无论哪种异常,原始的 HRESULT 都保留在 Exception.HResult 中。例如在文件 I/O 中“只想在共享冲突时重试”,就可以拿这个值作为条件。下面的例子就是只捕获表示共享冲突的 0x80070020 的那一段。

try
{
    using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80070020))
{
    // 0x80070020 = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)
    // 另一个进程正占着这个文件——可以稍等片刻再重试,等等
}

6.3. P/Invoke 与 GetLastError

用 P/Invoke 直接调用 Win32 API 时,要把声明和取值方式配成一套。

  1. 在 DllImport(或 LibraryImport)上指定 SetLastError = true。
  2. 确认失败之后立刻用 Marshal.GetLastWin32Error 取值。.NET 6 及以后可以使用等效的 GetLastPInvokeError。16

不要采用把 GetLastError 本身声明为 P/Invoke 再调用的做法。因为运行时内部的 API 调用可能覆盖这个值,导致取不到正确的失败原因。16

P/Invoke 中取得最后错误把 SetLastError 设为 true 并用 Marshal.GetLastWin32Error 取值才是正确做法,直接把 GetLastError 声明为 P/Invoke 会因运行时覆盖而不准确用 P/Invoke 调用 Win32 API指定 SetLastError=true用 GetLastWin32Error 取值直接声明并调用 GetLastError运行时会覆盖,结果不准确

图16:P/Invoke 中要把 SetLastError=true 与 Marshal.GetLastWin32Error 配成一套使用。直接调用 GetLastError 并不准确。

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFileW(string fileName, uint access, uint share,
    IntPtr security, uint disposition, uint flags, IntPtr template);

// 返回值不用 IntPtr 而用 SafeFileHandle 接收,并用 using 确保关闭
//(放着 IntPtr 不管会泄漏内核句柄)
using var handle = CreateFileW(@"C:\ProgramData\MyApp\config.dat",
    0x80000000 /*GENERIC_READ*/, 0, IntPtr.Zero, 3 /*OPEN_EXISTING*/, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
    int code = Marshal.GetLastWin32Error();              // 例如 5
    var message = new Win32Exception(code).Message;       // 例如:拒绝访问。
    logger.LogError("CreateFileW failed: {Code} (0x{Code:X8}) {Message}",
        code, code, message);
}

Win32Exception 会根据 Win32 错误码查出操作系统的消息字符串,因此可以直接用于在日志里同时留下代码和消息。关于异常应该在哪一层 catch、应该如何记入日志的设计讨论,见“异常处理中,catch 与日志应该放在哪里”。

7. 转换与排查工具实务——可直接复制粘贴的速查

首先根据手上的环境和目的选择工具。下面各节都放了可以直接使用的例子。

场景 工具 能查到的内容
想不额外安装就确认 certutil -error、net helpmsg 代码对应的名称与消息(7.2 节)
想横跨多套体系查定义 err.exe 头文件中的候选定义(7.1 节)
想转换和拆解数值 PowerShell 十进制与十六进制的转换、低 16 位、映射到 .NET 异常(7.3 节)
想在转储分析过程中确认含义 WinDbg 的 !error 按 Win32 或 NTSTATUS 的解释(7.4 节)

7.1. err.exe(Microsoft Error Lookup Tool)

这是 Microsoft 发布的单个可执行文件形式的错误查询工具。它横跨 winerror.h、ntstatus.h 等大量头文件,列出与指定代码相符的定义和消息。7

err 0x80070005
err 5
err 0xC0000005

查看结果时有两点需要注意。

  • 从多个候选中选出符合上下文的那个。例如“5”除了 Win32 的 ERROR_ACCESS_DENIED 之外,还会匹配到别的定义。不要把命中的名称直接当成原因。
  • 确认收录定义的时间点。下载文件名带版本号(撰稿时为 Err_6.4.5.exe),代码的定义基于打包时的头文件。7
err.exe 的检索结果要结合上下文挑选err.exe 横跨大量头文件列出相符的定义,因此同一个数值出现多个候选时,要结合上下文选出合适的那个输入 err 5横跨大量头文件检索命中多个定义结合上下文选出合适的候选

图17:err.exe 是跨头文件检索,候选可能有多个,合适的那个要靠上下文挑选。

7.2. Windows 自带的命令

不用额外安装就能使用的是 certutil 和 net helpmsg。certutil 的 -error 选项会显示错误码对应的消息文本,十六进制的 HRESULT 和十进制都接受。8

certutil -error 0x80070005
certutil -error 5
net helpmsg 5

net helpmsg 只接受十进制的 Win32 错误码。在中文环境下消息会以中文返回,因此也能直接拿来向用户说明。要查十六进制的 HRESULT 时,就像上面的例子那样选用 certutil -error。

自带命令的分工十进制的 Win32 错误码可以用 net helpmsg 查,包含十六进制的代码则用 certutil 的 -error 选项查十进制的 Win32含十六进制手上的代码是什么形式?net helpmsgcertutil -error返回中文消息十六进制和十进制都接受

图18:自带命令的分工。十进制的 Win32 错误用 net helpmsg,含十六进制则用 certutil -error。

7.3. PowerShell 单行命令集

这是把记法转换、取得 Win32 消息、拆解 HRESULT 以及映射到 .NET 异常汇总起来的速查。确认代码含义之后,仍然需要另行排查失败的操作和对象。

# Win32 错误码 → 操作系统的消息字符串
[System.ComponentModel.Win32Exception]::new(5).Message
# → 拒绝访问。

# 十进制负数 → 十六进制记法(确认 HRESULT 的真身)
'0x{0:X8}' -f -2147467259     # 0x80004005

# 0x8007xxxx → 低 16 位的 Win32 错误码
0x80070005 -band 0xFFFF        # 5

# HRESULT → 确认 .NET 映射到的异常
[System.Runtime.InteropServices.Marshal]::GetExceptionForHR(-2147024891)
# → UnauthorizedAccessException (0x80070005)

# Win32 错误码 → HRESULT(重现重新封装的过程)
'0x{0:X8}' -f (0x80070000 -bor 32)   # 0x80070020

7.4. WinDbg 的 !error

在转储分析过程中查代码,用 WinDbg 的 !error 扩展最快。默认按 Win32 错误码解释,给第二个参数传 1 则按 NTSTATUS 解释。9

0:000> !error 5
Error code: (Win32) 0x5 (5) - 拒绝访问。

0:000> !error 0xc0000005 1
Error code: (NTSTATUS) 0xc0000005 - <访问冲突>

在崩溃转储里 !analyze -v 会自动显示异常代码(NTSTATUS),所以流程就是从那里用 !error <code> 1 确认含义。

在 WinDbg 中确认异常代码的流程崩溃转储中 analyze 命令会自动显示异常代码,把该代码传给 error 扩展并加上第二个参数 1,按 NTSTATUS 确认含义打开崩溃转储执行 !analyze -v显示出异常代码用 !error 代码 1 确认含义

图19:转储分析中把 !analyze -v 显示出的异常代码,加上标志 1 交给 !error 查询。

8. 排查步骤——从判定层次到与上下文比对

实际排查要按以下五个阶段依次推进。要点是不要查到名称就收手,最后一定要确认失败的操作和对象。

  1. 统一记法。十进制负数要转换成 8 位十六进制。不足 8 位的十六进制要补零再读。
  2. 判定属于哪一层的代码。用下面的表从开头几位缩小出首选假设,再与返回它的 API 和它出现的位置比对。
  3. 拆解出本质代码。0x8007xxxx 形式的 HRESULT 取低 16 位,0xDxxxxxxx 形式的 HRESULT 则去掉 N 位再读。
  4. 用工具查名称和定义。用 err.exe、certutil、!error 等确认符号名和消息。
  5. 与上下文比对。用应用日志、事件日志和 Procmon 确定是哪个应用、在哪个操作中、哪个 API 对什么失败了。代码给出的是“失败的种类”,上下文给出的才是“原因所在”。
错误码排查的步骤把记法统一成十六进制,用开头几位判定层次,拆解出本质代码,用工具查出名称和定义,最后与上下文比对,这就是排查的套路十进制记法0x80070xC0xD统一记法(负数转成 8 位十六进制)开头几位是什么?按 Win32 错误解读把低 16 位转成十进制按 NTSTATUS 解读去掉 N 位再读用工具查名称和定义与上下文比对(Procmon 等)

图20:排查的套路。统一记法,判定层次并拆解,查出名称之后再与上下文比对。

外观 首选假设 拆解与转换的方法
1~5 位的十进制数(5、1223 等) Win32 错误码 直接交给 net helpmsg 或 err.exe
十进制负数(-2147024891 等) HRESULT 先转成 8 位十六进制,再按下面几行判定
0x8007xxxx HRESULT(FACILITY_WIN32) 把低 16 位转成十进制,按 Win32 解读
0x8004xxxx HRESULT(FACILITY_ITF) 到返回它的组件的文档里查
0x8000xxxx HRESULT(FACILITY_NULL) E_FAIL 等通用代码。把重心转向上下文排查
0xCxxxxxxx NTSTATUS(错误) !error <code> 1,必要时转换成 Win32 再读
0xDxxxxxxx NTSTATUS 的 HRESULT 映射 去掉 N 位(0x10000000)后按 NTSTATUS 解读
0x8024xxxx 等专属 Facility 特定功能领域的 HRESULT 由 Facility 值确定领域,再查专用资料(0x8024… 是 Windows Update)2

步骤 5 的具体示例:从 0x80070002 追到找不到的路径

即使应用程序只显示“0x80070002”,看一眼 Procmon 的 Result 列,就能知道“哪个进程、对哪个路径、被返回了 NAME NOT FOUND”。这正是从代码拆解推进到确定实际失败对象的观测手段。

事件日志一侧的查法,也请参考“Windows 事件日志、ETW 入门”。

9. 常见的误读——让排查绕远路的几种模式

最后列举在实际咨询中见到的误读模式。

误读 1:以为 0x80004005 是“指明特定原因的代码”

E_FAIL 表示“未指定的失败”,Windows Update、网络、数据库都会出现同一个值。拿这个代码去搜索,再把搜到的处理办法逐个试一遍,几乎肯定是在绕远路。请不要从代码入手,而要从“哪个应用、哪个操作、同一时刻的其他日志”来缩小范围。6

误读 2:没意识到十进制负数就是 HRESULT

有人会把“发生错误 -2147467259”这样的日志原样拿去搜索,或者困惑于“还有负数的错误?”。看到负数就转成十六进制。仅此一步就能知道它是 0x80004005(E_FAIL),从而接上误读 1 的知识。

误读 3:把 0x8007xxxx 整整 8 位拿去查,却不看低位的 Win32 错误

0x80070005 的本质是“5=拒绝访问”。与其拿完整的 8 位去搜索,不如取出低 16 位,思考“Win32 的错误 5 在这个操作的上下文里意味着什么”,这样能更快触及核心。

误读 4:认定“代码相同=原因相同”

有过“错误 5 是杀毒软件引起的”这种经历之后,下次再遇到错误 5 就容易直接套用同样的处理。即使代码相同,只要失败的 API 和目标资源不同,原因就是另一回事。确认代码含义与用 Procmon 等确定对象,每次都要成对进行。

误读 5:把 Win32 的错误 5 与 0xC0000005、停止代码与 NTSTATUS 混为一谈

因为都带“5”就把 ERROR_ACCESS_DENIED 和 STATUS_ACCESS_VIOLATION 等同起来,排查就会偏向权限问题与程序缺陷这两个完全不同的方向。另外,蓝屏的停止代码与 NTSTATUS 是两套体系,拿 0x9F 去查 NTSTATUS 的表得不到有意义的答案。15

错误 5 与 0xC0000005 的排查方向不同Win32 的错误 5 要按权限问题来查,NTSTATUS 的 0xC0000005 要按程序缺陷来查,把两者等同起来排查就会跑偏Win32 的错误 5排查权限问题NTSTATUS 0xC0000005排查程序缺陷属于不同体系、互不相干的代码

图21:不要因为都带“5”就等同看待。错误 5 指向权限问题,0xC0000005 指向程序缺陷。

10. 总结

先分辨体系。Windows 的主要错误码分为 Win32 错误码、HRESULT、NTSTATUS 三套体系。先理顺十进制、十六进制、负数这些记法上的差异,再确认是哪一层的谁返回的。NTSTATUS 会在崩溃的异常代码和 Procmon 的 Result 列里遇到,但 Win32 的错误 5 与 0xC0000005 是两回事,停止代码更是另一套体系。

接着读结构,取出需要的信息。HRESULT 由 S/R/C/N/X 位、11 位的 Facility 和 16 位的 Code 构成。最重要的模式是 0x8007xxxx,可以从低 16 位读出 Win32 错误。COM 互操作中 HRESULT 会映射到 .NET 的异常类型,原始值留在 Exception.HResult 里。P/Invoke 中要把 SetLastError=true 和 Marshal.GetLastWin32Error 配成一套使用。

最后与上下文比对。E_FAIL(0x80004005)不是指明原因的代码。一旦确认拆解也换不来更多信息,就停止深挖代码,转向操作、对象和同一时刻的日志。工具方面,按场合选用 certutil 与 net helpmsg、err.exe、PowerShell 以及 WinDbg 的 !error。

排查的套路就是“统一记法→判定层次→拆解→查名称→与上下文比对”。代码能告诉你的只到失败的种类为止。下次遇到陌生的数字时,在把它粘进搜索框之前,先确认开头几位和它的出处。这第一步拆解,决定了之后要去哪里查。

相关文章

相关咨询领域

小村软件有限公司承接以错误码为起点的故障排查(“看不懂这个错误码的含义”“只有特定环境才出现 0x80070005”)、Win32 API 与 COM、.NET 混用的应用程序的错误处理设计,以及使用崩溃转储和 Process Monitor 定位原因的工作。哪怕只有一张错误对话框的截图,也欢迎来咨询。

参考链接

  1. Microsoft Learn, Debug system error codes. 关于 Win32 系统错误码(0~15999)清单的索引,用带 FORMAT_MESSAGE_FROM_SYSTEM 标志的 FormatMessage 取得 GetLastError 返回代码的消息,WinINet/WinHTTP 错误(12000 号段)也定义在这个空间里,以及用 Microsoft Error Lookup Tool 和 !err 命令排查的方法。 ↩ ↩2 ↩3 ↩4 ↩5

  2. Microsoft Open Specifications, [MS-ERREF]: HRESULT. 关于 HRESULT 的位布局(S、R、C、N、X 位,11 位的 Facility,16 位的 Code),N 位表示该值是把 NTSTATUS 映射到 HRESULT 空间的结果,以及包含 FACILITY_WINDOWS_UPDATE(36) 在内的设施码清单。 ↩ ↩2 ↩3 ↩4

  3. Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. 关于 NTSTATUS 的位布局(2 位的 Sev、C 位、N 位、12 位的 Facility、16 位的 Code),以及严重性分为成功(00)、信息(01)、警告(10)、错误(11) 四种。 ↩ ↩2 ↩3

  4. Microsoft Learn, HRESULT_FROM_WIN32 macro. 关于把 Win32 系统错误码映射为 HRESULT 值的 winerror.h 宏的定义。 ↩ ↩2 ↩3

  5. Microsoft Learn, Structure of COM Error Codes. 关于 HRESULT 的严重性位与设施字段的作用,FACILITY_NULL、FACILITY_RPC、FACILITY_ITF、FACILITY_WIN32、FACILITY_WINDOWS 的各个值,以及 FACILITY_ITF 的代码含义由各个接口定义、同一个值也可能含义不同。 ↩ ↩2 ↩3

  6. Microsoft Learn, Common HRESULT values. 关于 E_FAIL(0x80004005) 表示“Unspecified failure(未指定的失败)”,以及 E_ACCESSDENIED(0x80070005)、E_INVALIDARG(0x80070057)、E_OUTOFMEMORY(0x8007000E) 等高频 HRESULT 值的定义。 ↩ ↩2 ↩3 ↩4

  7. Microsoft Learn, The Microsoft Error Lookup Tool. 关于它是横跨 Winerror.h 等各种头文件显示十六进制状态码所关联消息文本的单体工具,下载文件名为 Err_6.4.5.exe,以及需要注意其收录的定义来自编译时点。 ↩ ↩2 ↩3

  8. Microsoft Learn, certutil. 关于 certutil 的 -error 选项会显示错误码所关联的消息文本,以及采用类似 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND) 这种包含符号名的错误表示形式。 ↩ ↩2

  9. Microsoft Learn, !error. 关于 WinDbg 的 !error 扩展会解码并显示 Win32、Winsock、NTSTATUS、NetAPI 的错误值,以及把标志指定为 1 时按 NTSTATUS 解释。 ↩ ↩2

  10. Microsoft Learn, How to: Map HRESULTs and exceptions. 关于 COM 的 HRESULT 与 .NET 异常相互映射的机制,E_NOTIMPL→NotImplementedException 等对应表,没有明确映射的 HRESULT 会转换成 COMException,以及异常的 Message、Source 等会由 IErrorInfo 的信息初始化。 ↩ ↩2

  11. Microsoft Learn, RtlNtStatusToDosError function (winternl.h). 关于该函数把 NTSTATUS 代码转换为对应的 Win32 系统错误码,未定义对应关系时返回 ERROR_MR_MID_NOT_FOUND,以及不存在做逆向转换的函数。 ↩ ↩2

  12. Microsoft Learn, Last-Error Code. 关于最后错误代码按线程各自保存,应在失败之后立刻用 GetLastError 取得,成功时把代码覆盖为 0 的 API 与不这么做的 API 混杂存在,以及第 29 位保留给应用程序定义的代码。 ↩ ↩2

  13. Microsoft Learn, RegOpenKeyExW function. 关于成功时返回 ERROR_SUCCESS,失败时把 Winerror.h 中定义的非零错误码作为返回值返回。 ↩

  14. Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. 关于包含 STATUS_ACCESS_VIOLATION(0xC0000005)、STATUS_DLL_NOT_FOUND(0xC0000135)、STATUS_STACK_OVERFLOW(0xC00000FD)、STATUS_HEAP_CORRUPTION(0xC0000374)、STATUS_BREAKPOINT(0x80000003) 在内的 NTSTATUS 值清单。 ↩ ↩2

  15. Microsoft Learn, Bug check code reference. 关于蓝屏上显示的错误检查代码(停止代码)清单,以及用 WinDbg 的 !analyze 扩展显示代码信息的方法。从清单中可以确认它与 NTSTATUS 是不同的独立编号体系。 ↩ ↩2

  16. Microsoft Learn, Marshal.GetLastWin32Error Method. 关于它是取得设置了 SetLastError 标志的 P/Invoke 调用的最后错误代码的方法,直接把 GetLastError 声明为 P/Invoke 会因运行时内部的 API 调用覆盖而不可靠,以及 .NET 6 及以后推荐使用 GetLastPInvokeError。 ↩ ↩2

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

错误 0x80004005 是什么意思?
0x80004005 是 HRESULT 的 E_FAIL,含义是“未指定的失败(Unspecified failure)”。也就是说,它只表示“发生了无法报告详细原因的失败”,并不是指明原因本身的代码。网络、Windows Update、VBA、数据库驱动程序等互不相干的场合会出现同一个 0x80004005,正是这个缘故。看到这个代码时,不要深挖代码的含义,而要从它出现在哪个应用的哪个操作这一上下文,以及事件日志和详细日志中留下的其他错误信息来缩小原因范围。
像 -2147467259 这样的负数错误码是什么?
那是把 32 位的 HRESULT 按带符号十进制显示的结果。HRESULT 失败时最高位为 1,因此按带符号整数显示一定是负数。在 PowerShell 中执行 '0x{0:X8}' -f -2147467259 就能还原成十六进制记法(本例为 0x80004005=E_FAIL)。在日志或脚本的错误消息里看到以 -214… 开头的负数时,先转成十六进制再查是惯例做法。
查错误码含义最省事的方法是什么?
不用额外安装就能使用的是命令提示符的 net helpmsg 5(用于十进制的 Win32 错误)和 certutil -error 0x80070005。certutil 也接受十六进制的 HRESULT,会显示符号名和消息正文。用 PowerShell 的话,[System.ComponentModel.Win32Exception]::new(5).Message 可以取得中文消息。在开发机上备好 Microsoft 官方的错误查询工具 err.exe(Microsoft Error Lookup Tool),就能横跨 Win32、HRESULT、NTSTATUS 一次检索出相符的定义,非常方便。
0xC0000005 是什么错误?
那是 NTSTATUS 的 STATUS_ACCESS_VIOLATION,也就是访问冲突(非法的内存访问)。它是应用程序崩溃时在事件日志的“异常代码”和崩溃转储里最常见的代码,表示无效指针解引用、访问已释放内存等程序缺陷。它与 Win32 错误的 5(ERROR_ACCESS_DENIED=拒绝访问)名字相似,但属于另一套体系、互不相干的代码,请不要混淆。要定位原因,可靠的做法是采集崩溃转储并用 WinDbg 分析。
为什么代码相同,原因每次却不一样?
因为错误码只表示“哪一种失败”,“什么失败、为什么失败”由调用的上下文决定。例如错误 5(拒绝访问)可能来自 NTFS 访问权限不足、缺少管理员权限、杀毒软件拦截等完全不同的原因,却得到同一个代码。即便是相似的场景,只要别的进程还开着该文件,就会变成另一个代码(错误 32=共享冲突),正确分辨代码就会改变要去查的地方。错误 2(找不到文件)也常常不是主程序本身,而是依赖 DLL 或配置文件没找到。查过代码含义之后,再用 Process Monitor 等确认是哪个 API 对哪个资源失败,才是定位原因的捷径。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表