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?
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...
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 are 4 Main Types of Preprocessor Directives: Macros. File Inclusion. Conditional Compilation. Other directives.
All preprocessor directives are interpreted before macro expansion begins, so no, you cannot have a macro expand into an #include directive and have it be interpreted as such. Instead, it will be interpreted as (erroneous) C++ code.
Macro expansion overviewThe preprocessor maintains a context stack, implemented as a linked list of cpp_context structures, which together represent the macro expansion state at any one time. The struct cpp_reader member variable context points to the current top of this stack.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With