Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a Visual Studio Macro Value into a pre processor directive?

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.

like image 824
Lukas Thomsen Avatar asked May 25 '15 12:05

Lukas Thomsen


People also ask

Is macro a preprocessor directive?

There are 4 Main Types of Preprocessor Directives: Macros. File Inclusion. Conditional Compilation. Other directives.

Are preprocessor directives used for macro expansion?

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.

What is macro expansion in preprocessor?

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.


1 Answers

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"

The PropertySheet macros page

Click "Add Macro"

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

You should now be able to use the preprocessor entry

SOLUTIONDIR=$(RawSolutionDir)
like image 63
kfsone Avatar answered Oct 07 '22 01:10

kfsone