如何将Visual Studio宏值转换为预处理器指令? [英] How to get a Visual Studio Macro Value into a pre processor directive?

查看:102
本文介绍了如何将Visual Studio宏值转换为预处理器指令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的项目中,我需要在运行时访问$(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")

使用片段将在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 的答案,我已经提出以下解决方案,该解决方案可以将Visual Studio宏的任何值(例如$(SolutionDir))传递到您的代码中.以下解决方案独立于所使用的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中删除"字符.

解决方案

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))?"

如果这很麻烦,则可以在属性表中添加一个自定义宏.转到属性资源管理器",右键单击您的项目,添加一个新的属性表,将其称为"ProjectMacros.props"或其他名称.

展开您的项目,然后选择一种配置,例如调试,双击"PropertySheet"值以打开"PropertySheet PropertyPages"并选择"UserMacros"

点击添加宏"

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

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

SOLUTIONDIR=$(RawSolutionDir)

Within my projects I need to access the value of the $(SolutionDir) macro at runtime. To do that I've tried adding pre processor entries like DEBUG_ROOT=$(SolutionDir) or DEBUG_ROOT=\"$(SolutionDir)\" but this results in various compiler errors due to invalid escape sequences since $(SolutionDir) contains single \ characters (e.g. $(SolutionDir) = c:\users\lukas\desktop\sandbox\).

Is there an easy way to pass the value of the $(SolutionDir) macro into my code?

Backround

I am utilizing function OutputDebugString(..) quite a lot within my debug builds to see what my code is doing.

/* 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")

Using the snipped will cause a printout like c:\users\lukas\desktop\sandbox\testconsole\main.c(17) : test within the output window of Visual Studio. This speeds up finding the location within your code that caused the printout since you can simply double click on the line of the output window and Visual Studio automatically jumps to the specified code location.

Since depending on the location of the solution the absolute path (__FILE__ expands to the absolute path) the "header" of the debug strings may get quite long. I've seen that Visual Studio is smart enough to understand relative paths to e.g. the solution root directory. To reduce the length of the strings I'm checking if __FILE__ is within a DEBUG_ROOT directory and if so I'm replacing DEBUG_ROOT with a simple '.' to generate a relative path to DEBUG_ROOT. So if I write #define DEBUG_ROOT "c:\\users\\lukas\\desktop\\sandbox" the final debug string of the example above will be .\testconsole\main.c(17) : test. Currently I'm setting the value of DEBUG_ROOT within the preprocessor definitions of the project.

Since several people are working on the project it is not a smart move to have an absolute path within the project settings since each team member may check-out the source files to a different root directory. So I've tried to use the $(SolutionDir) macro to create something like DEBUG_ROOT=\"$(SolutionDir)\\". But by doing so I'm running into trouble. Since $(SolutionDir) = c:\users\lukas\desktop\sandbox\ expanding of DEBUG_ROOT leads to undefined escape sequences, unterminated strings and a lot more ugly compiler errors...

Solution

Based on the answer of kfsone I've come up with the following solution which makes it possible to pass any value of a Visual Studio macro such as $(SolutionDir) into your code. The following solution is independent of the used Visual Studio version and language C/C++.

Adding SOLUTION_DIR=\"$(SolutionDir)" to the preprocessor entries of your project results in a compiler command line which looks something like that:

/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

Note that $(SolutionDir) is preceeded by a \" to create a " characted infront of the value of $(SolutionDir) but is terminated by a single ". Looking at the compiler's command line shows that the terminating " is escaped by the last \ of $(SolutionDir).

Using SOLUTION_DIR within your code results in unknown escape sequences and the string ends up with all \ characters being removed. This is done by the compiler which expands SOLUTION_DIR and intepretes \ as begin of an escape sequence.

Using the TOSTRING(x) macro of my code posted above solves this issue since it forces the compiler to use the string as it is without further processing.

#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"

From here it is only a simple step to do some string magic to remove the " characters from SOLUTION_DIR2.

解决方案

There is a C++11 feature, raw string literals, available in Visual Studio version 2013 and above, which lets you do this. The syntax is

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

e.g. if you choose "?:?" as your delimiter

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

or if you choose "Foo123"

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

But for this demonstration, I'm going with ? as a single-character delimiter, because we know it's illegal in Windows filenames.

Now you can set the project-level Preprocessor Definition:

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

and then the following code generates the expected output

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

writes

C:\\Temp\\

instead of

C:\Temp\

Now to capture the SolutionDir macro it's as simple as

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

If this is a pain, you can add a custom macro in a Property Sheet. Go to "Property Explorer" and right click your project, add a new property sheet, call it "ProjectMacros.props" or something.

Expand your project and select one of the configurations, e.g. debug, double click the "PropertySheet" value to open "PropertySheet PropertyPages" and select "UserMacros"

Click "Add Macro"

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

You should now be able to use the preprocessor entry

SOLUTIONDIR=$(RawSolutionDir)

这篇关于如何将Visual Studio宏值转换为预处理器指令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆