Как получить значение макроса Visual Studio в директиве pre-процессора?

В моих проектах мне нужно получить доступ к значению макроса $(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")

Использование отрезка приведет к распечатке типа c:\users\lukas\desktop\sandbox\testconsole\main.c(17) : test в окне вывода Visual Studio. Это ускоряет поиск местоположения внутри вашего кода, вызвавшего распечатку, поскольку вы можете просто дважды щелкнуть по строке окна вывода, а 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 приводит к undefined escape-последовательностям, неисчерпаемым строкам и намного более уродливым ошибкам компилятора...

Решение

Основываясь на ответе kfsone, я придумал следующее решение, позволяющее передать любое значение Visual Макрос Studio, такой как $(SolutionDir) в ваш код. Следующее решение не зависит от используемой версии Visual Studio и языка 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) предшествует \", чтобы создать " с символом infront значения $(SolutionDir), но заканчивается одним ". Глядя на командную строку компилятора, показано, что завершающий " экранируется последним \ из $(SolutionDir).

Использование SOLUTION_DIR в вашем коде приводит к неизвестным escape-последовательностям, и строка заканчивается удалением всех \ символов. Это выполняется компилятором, который расширяет SOLUTION_DIR и intepretes \ как начало escape-последовательности.

Использование макроса 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.

Ответ 1

В Visual Studio версии 2013 и выше имеется функция С++ 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" или что-то еще.

Разверните проект и выберите одну из конфигураций, например. debug, дважды щелкните значение "PropertySheet", чтобы открыть "PropertySheet PropertyPages" и выбрать "UserMacros"

The PropertySheet macros page

Нажмите "Добавить макрос"

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

Теперь вы можете использовать запись препроцессора

SOLUTIONDIR=$(RawSolutionDir)