如何将 Visual Studio 宏值放入预处理器指令中?

Posted

技术标签:

【中文标题】如何将 Visual Studio 宏值放入预处理器指令中?【英文标题】:How to get a Visual Studio Macro Value into a pre processor directive? 【发布时间】:2015-05-25 12:56:43 【问题描述】:

在我的项目中,我需要在运行时访问 $(SolutionDir) 宏的值。为此,我尝试添加诸如 DEBUG_ROOT=$(SolutionDir)DEBUG_ROOT=\"$(SolutionDir)\" 之类的预处理器条目,但这会由于无效的转义序列导致各种编译器错误,因为 $(SolutionDir) 包含单个 \ 字符(例如 $(SolutionDir) = c:\users\lukas\desktop\sandbox\)。

有没有一种简单的方法可以将$(SolutionDir) 宏的值传递到我的代码中?

背景

我在调试版本中大量使用函数OutputDebugString(..) 来查看我的代码在做什么。

/* debug.h */
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define LOCATION __FILE__ "(" TOSTRING(__LINE__) ") : "

#if !defined(DEBUG_ROOT)
#define DEBUG_ROOT    "#"   /* escape string to force strstr(..) to fail */
#endif

/*
**  DBGMSG macro setting up and writing a debug string.
**  Note: copying the strings together is faster than calling OutputDebugString(..) several times!
**  Todo: Ensure that size of dbgStr is not exceeded!!!
*/
#define DBGMSG(text) \
     \
        char dbgStr[1024]; \
        char *pFile; \
        pFile = strstr(LOCATION, DEBUG_ROOT); \
        if (pFile == LOCATION) \
         \
            wsprintf(dbgStr, ".%s", pFile + strlen(DEBUG_ROOT)); \
         \
        else \
         \
            wsprintf(dbgStr, "%s", LOCATION); \
         \
        wsprintf(dbgStr, "%s%s", dbgStr, text); \
        OutputDebugString(dbgStr); \
    


/* somewhere in the code */
DBGMSG("test")

使用 snipped 会在 Visual Studio 的输出窗口中产生类似 c:\users\lukas\desktop\sandbox\testconsole\main.c(17) : test 的打印输出。这加快了在代码中查找导致打印输出的位置,因为您只需双击输出窗口的行,Visual Studio 就会自动跳转到指定的代码位置。

由于绝对路径(__FILE__ 扩展为绝对路径)取决于解决方案的位置,因此调试字符串的“标题”可能会变得很长。我已经看到 Visual Studio 足够聪明,可以理解到例如的相对路径。解决方案根目录。为了减少字符串的长度,我正在检查__FILE__ 是否在DEBUG_ROOT 目录中,如果是,我将用简单的'.' 替换DEBUG_ROOT 以生成到DEBUG_ROOT 的相对路径。所以如果我写#define DEBUG_ROOT "c:\\users\\lukas\\desktop\\sandbox",上面示例的最终调试字符串将是.\testconsole\main.c(17) : test。目前我正在项目的预处理器定义中设置DEBUG_ROOT 的值。

由于有几个人在处理项目,因此在项目设置中设置绝对路径并不是明智之举,因为每个团队成员都可能将源文件检出到不同的根目录。所以我尝试使用$(SolutionDir) 宏来创建类似DEBUG_ROOT=\"$(SolutionDir)\\" 的东西。但是这样做我遇到了麻烦。由于$(SolutionDir) = c:\users\lukas\desktop\sandbox\ 扩展DEBUG_ROOT 会导致未定义的转义序列、未终止的字符串和更多丑陋的编译器错误...

解决方案

根据kfsone 的回答,我提出了以下解决方案,可以将$(SolutionDir) 等Visual Studio 宏的任何值传递到您的代码中。以下解决方案与使用的 Visual Studio 版本和语言 C/C++ 无关。

SOLUTION_DIR=\"$(SolutionDir)" 添加到项目的预处理器条目会导致编译器命令行如下所示:

/Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "SOLUTION_DIR=\"C:\Users\Lukas\Desktop\sandbox\""
/Gm /EHsc /RTC1 /MDd /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W3 /nologo /c /Wp64 /ZI /TP
/errorReport:prompt

请注意,$(SolutionDir) 前面有一个 \",以在 $(SolutionDir) 的值前面创建一个 " 字符,但由单个 " 终止。查看编译器的命令行显示终止 "$(SolutionDir) 的最后一个 \ 转义。

在您的代码中使用SOLUTION_DIR 会导致未知的转义序列,并且字符串最终会删除所有\ 字符。这是由编译器完成的,它扩展 SOLUTION_DIR 并将 \ 解释为转义序列的开头。

使用我上面发布的代码的TOSTRING(x) 宏解决了这个问题,因为它强制编译器按原样使用字符串而无需进一步处理。

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

#define SOLUTION_DIR2   TOSTRING(SOLUTION_DIR)

// the following line may cause compiler warnings (unrecognized character escape sequence)
printf("%s\n", SOLUTION_DIR);    // prints C:UsersLukasDesktopsandbox 

// the following line compiles without any warnings
printf("%s\n", SOLUTION_DIR2);   // prints "C:\Users\Lukas\Desktop\sandbox"

从这里开始,只需一个简单的步骤即可执行一些字符串魔术以从 SOLUTION_DIR2 中删除 " 字符。

【问题讨论】:

从您的问题看来,您可以在 C++ 中使用$SolutionDir,但只会遇到反斜杠的问题。是这样吗?如果是这样,您是如何做到的(我无法弄清楚)。还有你用的是什么版本的VS? 嗯,不,它不是 $(SolutionDir)。您的 main.c 存储在项目目录中。当然不适用于库项目中的代码。您需要在运行时修剪路径,通过 argv[0] 和 GetCurrentDirectory() 将项目目录交给银盘。 @Motti:我不能直接在我的代码中使用$(SolutionDir)。因此,我通过在 Visual Studio 的项目设置中定义预处理器指令 DEBUG_ROOT=\"$(SolutionDir)\\" 来使用解决方法将 $(SolutionDir) 传递到我的代码中。但显然我得到了无效的转义序列,因为 Visual Studio 使用 '\' 而不是 '/' 似乎您只需要在启动时正确转义 DEBUG_ROOT 一次,或者可能是第一次调用 log 函数并使用该变量。 @HansPassant:我已经发布了在运行时修剪路径的整个代码。你是对的,我用我也修复的路径做了一个复制/粘贴错误。使用argv[0]GetCurrentDirectory() 会引入某种初始化,这也具有Visual Studio 找不到嵌套库文件的缺点。这就是我将解决方案目录用作DEBUG_ROOT 的原因,这将确保正确修剪所有调试消息。所以我正在寻找一种将解决方案目录传递到我的代码中的方法,但我担心使用 $(SolutionDir) 宏将不起作用。 【参考方案1】:

Visual Studio 2013 及更高版本中提供了 C++11 功能,即原始字符串文字,可让您执行此操作。语法是

'R"' <delimiter> '(' <string> ')' <delimiter> '"'

例如如果您选择“?:?”作为你的分隔符

R"?:?(don't\escape)?:?"

或者如果你选择“Foo123”

R"Foo123(don't\escape)Foo123"

但是对于这个演示,我会选择 ?作为单字符分隔符,因为我们知道它在 Windows 文件名中是非法的。

现在您可以设置项目级预处理器定义:

DIR=R"?(C:\\Temp\\)?"

然后下面的代码生成预期的输出

#include <iostream>
int main() 
    std::cout << DIR << '\n';

C:\\Temp\\

而不是

C:\Temp\

现在要捕获 SolutionDir 宏,就这么简单

DIR=R"?($(SolutionDir))?"

如果这很麻烦,您可以在属性表中添加自定义宏。转到“Property Explorer”并右键单击您的项目,添加一个新的属性表,将其命名为“ProjectMacros.props”或其他名称。

扩展您的项目并选择其中一种配置,例如调试,双击“PropertySheet”值打开“PropertySheet PropertyPages”并选择“UserMacros”

点击“添加宏”

Name: RawSolutionDir
Value: R"?path?($(SolutionDir))?path?"

您现在应该可以使用预处理器条目

SOLUTIONDIR=$(RawSolutionDir)

【讨论】:

关于一个我直到现在才知道的功能的非常有趣的信息!我已经尝试过您的解决方案,它非常适用于 Visual Studio 2013 和 C++。不幸的是,我也有很多 C 代码导致SOLUTIONDIR = "?"。但是你的建议给了我一些想法……如果它们有效,我会在这里分享。 我接受了您的回答,因为它是一个可行的解决方案,并帮助我创建了一个不依赖于 Visual Studio 版本和 C++ 语言的解决方案。感谢您的帮助!

以上是关于如何将 Visual Studio 宏值放入预处理器指令中?的主要内容,如果未能解决你的问题,请参考以下文章

Microsoft Visual Studio 2010 没有将构建工具放入 PATH 中以获得干净的目标?

如何在 Visual Studio 中自动将预处理器和注释块插入到新的 C++ 头文件中?

代码段:在Visual Studio中自动完成ArgumentNullException

Visual Studio 2015 如何将分支合并到 master?

使用offset获取宏值的子集

阻止 Visual Studio 将 using 指令放在命名空间之外