如何将 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