TN035:通过 Visual C++ 使用多个资源文件和头文件

注释

自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。

此说明介绍了 Visual C++ 资源编辑器如何支持在单个项目中共享或跨多个项目共享的多个资源文件和头文件,以及如何利用该支持。 此说明回答以下问题:

  • 何时可能需要将项目拆分为多个资源文件和/或头文件,以及如何执行此作

  • 如何在两.RC个文件之间共享通用头.H文件

  • 如何将项目资源划分为多个 .RC 文件

  • 如何(和工具)管理和文件之间的.RC.CPP.H生成依赖项

应注意,如果将其他资源文件添加到项目,ClassWizard 将无法识别所添加文件中的资源。

此说明的结构如下,用于回答上述问题:

  • Visual C++如何管理资源文件和头文件 概述概述了 Visual C++中的“资源集包括”命令如何让你在同一项目中使用多个资源文件和头文件。

  • AppWizard 创建的 .RC 分析和 .H 文件查看由 AppWizard 创建的应用程序使用的多个资源和头文件。 这些文件作为其他资源文件和头文件的良好模型,你可能希望添加到项目中。

  • 包括其他头文件 描述你可能希望包含多个头文件的位置,并提供有关如何执行此作的详细信息。

  • 在两 .RC 个文件之间共享头文件 显示了如何在不同项目中的多个 .RC 文件之间共享一个头文件,或者在同一项目中共享一个头文件。

  • 在同一项目中使用多个资源文件 描述你可能希望将项目分解为多个 .RC 文件的位置,并提供有关如何执行此作的详细信息。

  • 强制实施不可编辑的 Visual C++ 文件 介绍了如何确保 Visual C++不会编辑和无意中重新格式化自定义资源。

  • 管理由多个 Visual C++ 编辑 .RC 的文件共享的符号 介绍了如何跨多个 .RC 文件共享相同的符号以及如何避免分配重复的 ID 数值。

  • 管理依赖项.RC.CPP.H文件描述 Visual C++如何避免不必要的重新编译.CPP依赖于资源符号文件的文件。

  • Visual C++如何管理“集包括”信息 提供有关 Visual C++如何跟踪文件中包含的多个(嵌套) .RC 文件和多个头文件 .RC 的技术详细信息。

Visual C++如何管理资源文件和头文件概述

视觉对象C++将单个 .RC 资源文件和相应的 .H 头文件作为紧密耦合的文件对进行管理。 在文件中编辑和保存资源 .RC 时,间接编辑并保存相应 .H 文件中的符号。 尽管你可以一次打开和编辑多个 .RC 文件(使用 Visual C++ 的 MDI 用户界面),但对于任何 .RC 给定文件,你间接编辑一个对应的头文件。

“资源视图的资源包括”对话框

若要访问 “资源包括”,请打开 “资源视图 ”,然后右键单击 .RC 该文件并选择“ 资源包括”。

符号头文件

默认情况下,Visual C++始终命名相应的头文件,而不考虑资源文件 RESOURCE.H的名称(例如 MYAPP.RC)。 Visual C++“资源包括”对话框中的“符号头文件:”部分可用于更改此头文件的名称。 在分区的编辑框中输入新的文件名。

注释

资源文件不位于与 .RC 文件相同的目录中,必须附加一个具有 escaped-'\' 的相对路径才能正确读取。

Read-Only 符号指令

尽管 Visual C++仅编辑任何给定 .RC 文件的一个头文件,但 Visual C++支持对其他只读头文件中定义的符号的引用。 “资源包含”对话框中的“Read-Only 符号指令:”部分允许将任意数量的附加只读头文件指定为 Read-Only 符号指令。 “只读”限制意味着在文件中添加新资源 .RC 时,可以使用在只读头文件中定义的符号。 但是,如果删除资源,符号仍保留在只读头文件中定义。 无法更改分配给只读符号的数值。

Compile-Time 指令

Visual C++还支持资源文件的嵌套,其中一个文件通过使用指令包含在另一 .RC#include 文件中。 使用 Visual C++ 编辑给定 .RC 文件时,包含的文件中的任何资源都看不到。 但是,编译 .RC 文件时,也会编译包含的文件。 “资源包括”对话框中的“Compile-Time 指令:”部分允许指定要作为 Compile-Time 指令包含的任意数量的.RC文件。

请注意,如果读取到 Visual C++包含另一.RC.RC个未指定为 Compile-Time 指令的文件的文件,会发生什么情况。 将 Visual C++ .RC 以前使用文本编辑器手动维护的文件时,可能会出现这种情况。 当 Visual C++读取包含 .RC 的文件时,它将包含的资源合并到父 .RC 文件中。 保存父 .RC 文件时, #include 语句实际上将被包含的资源替换。 如果不希望发生此合并,则应在将语句读取到 Visual C++ 之前从父.RC文件中删除#include该语句;然后使用 Visual C++,添加与 Compile-Time 指令相同的#include语句。

视觉对象C++将 .RC 上述三种类型的“集包括”信息(符号头文件、Read-Only 符号指令和 Compile-Time 指令) #include 保存在指令 和资源TEXTINCLUDETEXTINCLUDE Visual C++管理集包含信息的方式中介绍了这些资源(通常不需要处理的实现详细信息)。

AppWizard 创建 .RC.H 文件分析

检查 AppWizard 生成的应用程序代码可深入了解 Visual C++如何管理多个资源文件和头文件。 下面检查的代码摘录来自 MYAPP 使用默认选项由 AppWizard 生成的应用程序。

AppWizard 创建的应用程序使用多个资源文件和多个头文件,如下图所示:

   RESOURCE.H     AFXRES.H
          \       /
           \     /
          MYAPP.RC
              |
              |
        RES\MYAPP.RC2
        AFXRES.RC
        AFXPRINT.RC

可以使用 Visual C++ File/Set Includes 命令查看这些多个文件关系。

MYAPP.RC
使用 Visual C++ 编辑的应用程序资源文件。

RESOURCE.H 是特定于应用程序的头文件。 它始终由 AppWizard 命名 RESOURCE.H ,与 Visual C++标头文件的默认命名一致。 此 #include 头文件是资源文件中的第一条语句(MYAPP.RC):

//Microsoft Visual C++ generated resource script
//
#include "resource.h"

RES\MYAPP.RC2
包含 Visual C++ 不会编辑但将包含在最终编译 .EXE 文件中的资源。 默认情况下,AppWizard 不会创建此类资源,因为 Visual C++可以编辑所有标准资源,包括版本资源(此版本中的新功能)。 如果希望将自己的自定义格式化资源添加到此文件,则 AppWizard 会生成一个空文件。

如果使用自定义格式化资源,则可以使用 Visual C++ 文本编辑器将它们添加到 RES\MYAPP.RC2 和编辑它们。

AFXRES.RCAFXPRINT.RC 包含框架的某些功能所需的标准资源。 同样 RES\MYAPP.RC2,这两个框架提供的资源文件包含在末尾 MYAPP.RC,并在“设置包含”对话框的 Compile-Time 指令中指定它们。 因此,在 Visual C++ 中编辑 MYAPP.RC 时,不会直接查看或编辑这些框架资源,而是编译为应用程序的二进制 .RES 文件和最终 .EXE 文件。 有关标准框架资源的详细信息,包括修改它们的过程,请参阅 技术说明 23

AFXRES.H 定义标准符号,例如 ID_FILE_NEW框架使用的标准符号,并专门用于 AFXRES.RCAFXRES.H还用于#include包含 WINRES.HVisual C++生成的.RC文件和AFXRES.RC所需的子WINDOWS.H集。 编辑应用程序资源文件时,可在其中 AFXRES.H 定义的符号(MYAPP.RC)。 例如, ID_FILE_NEW 用于 FileNew 文件菜单资源中的 MYAPP.RC 菜单项。 无法更改或删除这些框架定义的符号。

包括其他头文件

AppWizard 创建的应用程序仅包含两个头文件: RESOURCE.HAFXRES.H。 仅 RESOURCE.H 特定于应用程序。 在以下情况下,可能需要包括其他只读头文件:

头文件由外部源提供,或者你想要在同一项目的多个项目或多个部分之间共享头文件。

头文件具有不希望 Visual C++在保存文件时更改或筛选掉的格式和注释。 例如,你可能想要保留使用符号算术的 #define,例如:

#define RED 0
#define BLUE 1
#define GREEN 2
#define ID_COLOR_BUTTON 1001
#define ID_RED_BUTTON (ID_COLOR_BUTTON + RED)
#define ID_BLUE_BUTTON (ID_COLOR_BUTTON + BLUE)
#define ID_GREEN_BUTTON (ID_COLOR_BUTTON + GREEN)

可以使用 Resource Include 命令将语句指定 #include 为第二个 Read-Only 符号指令,以包含其他只读头文件,如下所示:

#include "afxres.h"
#include "second.h"

新的文件关系图现在如下所示:

                   AFXRES.H
    RESOURCE.H     SECOND.H
          \       /
           \     /
          MYAPP.RC
              |
              |
        RES\MYAPP.RC2  
        AFXRES.RC
        AFXPRINT.RC

在两 .RC 个文件之间共享头文件

你可能想要在不同的项目中的两 .RC 个文件之间共享头文件,或者共享同一个项目。 为此,请将上述 Read-Only 指令技术应用于这两 .RC 个文件。 如果两 .RC 个文件适用于不同的应用程序(不同的项目),则结果如下图所示:

     RESOURCE.H   AFXRES.H   RESOURCE.H  
    (for MYAPP1)  SECOND.H   (for MYAPP2)
          \       /     \       /
           \     /       \     /
          MYAPP1.RC      MYAPP2.RC
           /    \        /     \
          /      \      /       \
RES\MYAPP1.RC2  AFXRES.RC     RES\MYAPP2.RC2
                AFXPRINT.RC

下面讨论了第二个头文件由同一应用程序(项目)中的两 .RC 个文件共享的情况。

在同一项目中使用多个资源文件

视觉C++和资源编译器通过#include包含一个文件在另一个文件中的指令支持同一.RC项目中的多个.RC文件。 允许多个嵌套。 有多种原因可将项目的资源拆分为多个 .RC 文件:

  • 如果将资源拆分为多个 .RC 文件,则更容易管理多个项目团队成员之间的大量资源。 如果使用源代码管理包签出文件并签入更改,将资源拆分为多个 .RC 文件,则可以更好地控制管理对资源的更改。

  • 如果要对资源的某些部分使用预处理器指令(例如 #ifdef#endif#define),则必须将它们隔离在资源编译器编译的只读资源中。

  • 组件 .RC 文件在 Visual C++ 中加载和保存的速度比一个复合 .RC 文件更快。

  • 如果要以人工可读形式维护具有文本编辑器的资源,则应将其保存在独立于 .RC 视觉对象C++编辑的文件中。

  • 如果需要将用户定义的资源保存在可由另一个专用数据编辑器解释的二进制或文本窗体中,则应将其保存在单独的 .RC 文件中,以便 Visual C++不会将格式更改为十六进制数据。 .WAV MFC 高级概念示例 SPEAKN 中的 (sound) 文件资源是一个很好的示例。

可以在“设置包括”对话框中的 Compile-Time 指令中包括 SECOND.RC

#include "res\myapp.rc2"  // non-Visual C++ edited resources
#include "second.rc"  // THE SECOND .RC FILE

#include "afxres.rc"  // Standard components
#include "afxprint.rc"  // printing/print preview resources

下图演示了结果:

   RESOURCE.H     AFXRES.H
          \       /
           \     /
          MYAPP.RC
              |
              |
        RES\MYAPP.RC2
        SECOND.RC  
        AFXRES.RC
        AFXPRINT.RC

使用 Compile-Time 指令,可以将 Visual C++ 可编辑和非可编辑资源组织到多个.RC文件中,其中主MYAPP.RC文件只执行其他.RC文件。#include 如果使用 Visual Studio C++项目 .MAK 文件,则应在项目中包括主 .RC 文件,以便所有包含的资源都与应用程序一起编译。

强制实施不可编辑的 Visual C++ 文件

AppWizard 创建 RES\MYAPP.RC2 的文件是一个文件示例,其中包含不希望意外读取到 Visual C++的资源,然后以丢失格式信息写回。 若要防止此问题,请将以下行放在文件的开头 RES\MYAPP.RC2

#ifdef APSTUDIO_INVOKED
    #error this file is not editable by Visual C++
#endif //APSTUDIO_INVOKED

当 Visual C++编译 .RC 该文件时,它将同时定义 APSTUDIO_INVOKEDRC_INVOKED. 如果 AppWizard 创建的文件结构已损坏,并且 Visual C++读取上面的 #error 行,则会报告致命错误并中止文件的读取 .RC

管理由多个 Visual C++ 编辑 .RC 的文件共享的符号

将资源拆分为要在 Visual C++ 中单独编辑的多个 .RC 文件时,会出现两个问题:

  • 你可能希望跨多个 .RC 文件共享相同的符号。

  • 你需要帮助 Visual C++避免将相同的 ID 数值分配给不同的资源(符号)。

下图说明了处理第一个问题的组织 .RC.H 文件:

              MYAPP.RC
             /         \
            /           \
MYSTRS.H   / MYSHARED.H  \  MYMENUS.H
     \    /    /      \   \    \
      \  /    /        \   \    \
      MYSTRS.RC         MYMENUS.RC

在此示例中,字符串资源保存在一个资源文件中, MYSTRS.RC菜单保存在另一个 MYMENUS.RC资源文件中。 某些符号(如命令)可能需要在两个文件之间共享。 例如,可能是 ID_TOOLS_SPELL “工具”菜单中“拼写”项的菜单命令 ID;也可能是框架在应用程序主窗口状态栏中显示的命令提示符的字符串 ID。

符号 ID_TOOLS_SPELL 保存在共享头文件中 MYSHARED.H。 使用文本编辑器手动维护此共享头文件;视觉C++不会直接编辑它。 在两个资源文件中MYSTRS.RCMYMENUS.RC,使用 Resource Includes 命令在 Read-Only 指令MYAPP.RC中指定#include "MYSHARED.H",如前所述。

在尝试使用它来标识任何资源之前,预测要共享的符号是最方便的。 将符号添加到共享头文件,如果尚未在文件的 Read-Only 指令 .RC 中包含共享头文件,请在使用符号之前执行此作。 如果不希望以这种方式共享符号,则必须手动(使用文本编辑器)将符号的 #define 语句从例如 MYMENUS.H ,移动到 MYSHARED.H 使用它 MYSTRS.RC之前。

在多个 .RC 文件中管理符号时,还必须帮助 Visual C++避免将相同的 ID 数值分配给不同的资源(符号)。 对于任何给定 .RC 的文件,Visual C++增量分配四个 ID 域中的每一个 ID。 在编辑会话之间,Visual C++跟踪在文件符号头文件 .RC 的每个域中分配的最后一个 ID。 下面是空文件(新).RC文件的值:APS_NEXT

#define _APS_NEXT_RESOURCE_VALUE  101
#define _APS_NEXT_COMMAND_VALUE   40001
#define _APS_NEXT_CONTROL_VALUE   1000
#define _APS_NEXT_SYMED_VALUE     101

_APS_NEXT_RESOURCE_VALUE 是用于对话框资源、菜单资源等的下一个符号值。 资源符号值的有效范围为 1 到 0x6FFF。

_APS_NEXT_COMMAND_VALUE 是将用于命令标识的下一个符号值。 命令符号值的有效范围0x8000 0xDFFF。

_APS_NEXT_CONTROL_VALUE 是将用于对话控件的下一个符号值。 对话控件符号值的有效范围为 8 到 0xDFFF。

_APS_NEXT_SYMED_VALUE 是使用符号浏览器中的“新建”命令手动分配符号值时发出的下一个符号值。

视觉C++从创建新文件时的最低法定值 .RC 略高的值开始。 AppWizard 还将将这些值初始化为更适合 MFC 应用程序的内容。 有关 ID 值范围的详细信息,请参阅 技术说明 20

现在,每次创建新资源文件(即使在同一项目中)Visual C++都定义相同的 _APS_NEXT_ 值。 这意味着,如果在两个不同的 .RC 文件中添加多个对话,很可能将相同的 #define 值分配给不同的对话。 例如,IDD_MY_DLG1在第一个.RC文件中,可以分配与第二.RC个文件中相同的数字 101IDD_MY_DLG2

若要避免此问题,应为相应 .RC 文件中四个 ID 域中的每个域保留一个单独的数值范围。 通过在开始添加资源之前手动更新_APS_NEXT每个.RC文件中的值来设置范围。 例如,如果第一个 .RC 文件使用默认值 _APS_NEXT ,则可能需要将以下 _APS_NEXT 值分配给第二 .RC 个文件:

#define _APS_NEXT_RESOURCE_VALUE  2000
#define _APS_NEXT_COMMAND_VALUE   42000
#define _APS_NEXT_CONTROL_VALUE   2000
#define _APS_NEXT_SYMED_VALUE     2000

当然,Visual C++仍然可以在第一个 .RC 文件中分配这么多 ID,数值开始与为第二 .RC 个文件保留的 ID 重叠。 应保留足够大的范围,以免发生此冲突。

管理和文件之间的.RC.CPP.H依赖关系

当 Visual C++保存 .RC 文件时,它还会将符号更改保存到相应的 RESOURCE.H 文件。 .CPP引用文件中资源.RC的任何文件都必须用于#include包含RESOURCE.H该文件,通常来自项目的主头文件中。 由于开发环境的内部项目管理(扫描源文件中的标头依赖项),此包含会导致不良副作用。 每次在 Visual C++ 中添加新符号时,都需要重新编译具有#include "RESOURCE.H"指令的所有.CPP文件。

视觉C++,通过将以下注释作为文件的第一行RESOURCE.H来规避依赖项RESOURCE.H

//{{NO_DEPENDENCIES}}

开发环境通过忽略更改 RESOURCE.H 来解释此注释,以便不需要重新编译依赖 .CPP 文件。

视觉对象C++保存文件时始终将 //{{NO_DEPENDENCIES}} 注释行添加到 .RC 文件中。 在某些情况下,规避生成依赖项 RESOURCE.H 可能会导致在链接时未检测到运行时错误。 例如,如果使用符号浏览器更改分配给资源符号的数值,则如果未 .CPP 重新编译引用资源的文件,则不会在应用程序运行时正确找到资源并加载该资源。 在这种情况下,应显式重新编译 .CPP 任何已知受符号更改 RESOURCE.H 影响的文件,或选择“ 全部重新生成”。 如果需要频繁更改特定组资源的符号值,可能会发现将这些符号分解为单独的只读头文件更加方便和更安全,如上述部分中所述, 包括其他头文件

Visual C++如何管理集包括信息

如上所述,“文件”菜单“包括”命令允许你指定三种类型的信息:

  • 符号头文件

  • Read-Only 符号指令

  • Compile-Time 指令

下表介绍了 Visual C++如何在文件中维护此信息 .RC 。 无需此信息即可使用 Visual C++,但它可能会增强理解,以便可以更自信地使用“设置包括”功能。

上述三种类型的 Set Includes 信息都以两种形式存储在 .RC 文件中:(1)作为 #include 或资源编译器可解释的其他指令,(2)作为仅由 Visual C++ 解释的特殊 TEXTINCLUDE 资源。

资源的目的是 TEXTINCLUDE 安全地将“设置包含”信息存储在 Visual C++的“设置 包含 ”对话框中的窗体中。 TEXTINCLUDE 是由 Visual C++ 定义的 资源类型 。 视觉对象C++识别具有资源标识号 1、2 和 3 的三个特定 TEXTINCLUDE 资源:

TEXTINCLUDE 资源 ID 集包括信息的类型
1 符号头文件
2 Read-Only 符号指令
3 Compile-Time 指令

这三种类型的“设置包括”信息都由 AppWizard 创建的默认 MYAPP.RCRESOURCE.H 文件进行说明,如下所述。 RC 语法需要额外的\0标记和END""块来BEGIN分别指定零个终止字符串和双引号字符。

符号头文件

资源编译器解释的符号头文件信息的形式只是一条 #include 语句:

#include "resource.h"

相应的 TEXTINCLUDE 资源为:

1 TEXTINCLUDE DISCARDABLE
BEGIN
    "resource.h\0"
END

Read-Only 符号指令

Read-Only 符号指令包含在资源编译器可解释的以下形式顶部 MYAPP.RC

#include "afxres.h"

相应的 TEXTINCLUDE 资源为:

2 TEXTINCLUDE DISCARDABLE
BEGIN
   "#include ""afxres.h""\r\n"
   "\0"
END

Compile-Time 指令

Compile-Time 指令包含在资源编译器可解释的以下形式末尾 MYAPP.RC

#ifndef APSTUDIO_INVOKED
///////////////////////
//
// From TEXTINCLUDE 3
//
#include "res\myapp.rc2"  // non-Visual C++ edited resources

#include "afxres.rc"  // Standard components
#include "afxprint.rc"  // printing/print preview resources
#endif  // not APSTUDIO_INVOKED

#ifndef APSTUDIO_INVOKED 指令指示 Visual C++跳过 Compile-Time 指令。

相应的 TEXTINCLUDE 资源为:

3 TEXTINCLUDE DISCARDABLE
BEGIN
"#include ""res\myapp.rc2""  // non-Visual C++ edited resources\r\n"
"\r\n"
"#include ""afxres.rc""  // Standard components\r\n"
"#include ""afxprint.rc""  // printing/print preview resources\r\n"
"\0"
END

另请参阅

按编号列出的技术说明
按类别列出的技术说明