The Resource Compiler’s preprocessor is not the same as the C preprocessor

A customer had a project written in Visual C++, and the Build step failed with an error from the Resource Compiler:

Fatal error RC1015: cannot open include file 'vcruntime.h'

For reasons known (or perhaps not) only to the original developer of the project, this particular RC file had an enormous number of #included header files, with many levels of nesting. Most of the header files employed the #pragma once directive to avoid multiple inclusion. But upon reading of the documentation for the Resource Compiler, it seems that the RC.EXE compiler doesn't recognize #pragma once.

The customer was able to replicate the problem by creating a pair of mutually-including header files:

// file1.h
#include "file2.h"

// file2.h
#include "file1.h"

which produced the same "cannot open include file" error message.

From this investigation, we are fairly confident that this is the root cause of the original error message. If this had been a problem with include files arise in C++ code, we could have used the /showincludes command line switch to troubleshoot the problem, but the RC.EXE compiler does not provide any switches for diagnosing problems with include files.

The customer wanted us to confirm their conclusions, and also to indicate whether the behavior with RC.EXE is by design, or whether they should file a defect report.

The Resource Compiler's preprocessor is not the same as the C preprocessor, even though they superficially resemble each other. In particular, #pragma is conspiciously missing from the table of supported preprocessor directives.

In order to get the effect of #pragma once in the Resource Compiler, you need to use the old-fashioned include guard technique.

Or better would be to avoid including so much junk. Many header files use #ifdef RC_INVOKED to detect whether they are being included by the Resource Compiler. When included by the Resource Compiler, they define only the identifiers needed for resource files and skip over all the other junk.

Bonus chatter: The Resource Compiler differs from the C preprocessor in another significant way: As noted on the same page that has the list of preprocessor directives, if a file has the extension .c or .h, then the Resource Compiler ignores all lines that aren't preprocessor directives. If you want to include another file that has resource content, you need to give it another extension. Typically, that extensions is .rc, although you may find the extension .dlg in older code.

Bonus bonus chatter: The Resource Compiler has two expression evaluators. The one used by the preprocessor follows the C language rules. But the one used by the resource parser follows its own weird rules. For example, in resource files, you can say 3 | NOT 2, which is equivalent to 3 & ~2. It means that in resource files, the | operator is not symmetric!

Comments (11)

  1. skSdnW says:

    And MIDL.exe uses the C compiler to pre-process its input files.

  2. AberAber says:

    Just to throw out the question – is there a specific reason why the resource compiler does not support #pragma once? Are you not supposed to be doing anything remotely like that there?

    1. The Resource Compiler was written in 1983, before #pragma was invented. And you shouldn’t be doing anything that fancy with your rc headers anyway.

  3. Peter Esik says:

    Raymond, where can I report bugs of Windows SDK tools? For Visual Studio for example, there is Connect, and also As far as I know, there is no public place like that for the Windows SDK. We’ve hit an rc.exe bug recently that I’d like to report (with a nice Word document describing the problem, and a shiny minimal repro).

    1. There is a “Developer Platform” section in Feedback Hub. (Or just email it to me and I’ll try to find the right person.)

  4. AlexShalimov says:

    Also found this when tried to use some already existing include files in rc. For example, it doesn’t support stringizing (and probably token concatenation).

    1. laonianren says:

      Stringizing can be persuaded to work in rc files. This converts version numbers into a dotted string suitable for the text part of a version resource:

      #define STRINGIZE(x) #x
      #define EXPAND(x) STRINGIZE(x)

      #define MAJOR_VERSION 10
      #define MINOR_VERSION 0
      #define RELEASE_NUMBER 14393
      #define BUILD_NUMBER 0


      1. AlexShalimov says:

        How did you know I tried to convert version number to string?! Thanks, I’ll try this.

        1. GregM says:

          I would guess that it’s the most common usage of stringizing in RC files. I’ve written that same thing multiple times. :)

  5. Billy O'Neal says:

    And that’s how the begin and end blobs of the standard library got to add *yet another* thing to deal with because people do strange things.

  6. alegr1 says:

    RC is such a clunky POS. It doesn’t recognize BOM in an UTF-8 file. Does accept UTF-16 files, but doesn’t recognize BOM. There was that time, when I had a UTF-16 file mangled because of wrong .gitattributes. RC didn’t complain, but didn’t produce the resources, either.

Skip to main content