Как я могу получить список всех значений параметров функций и связать их с классом журнала в Visual С++?

У нас есть огромный набор функций, открытых как внешний API, и нам нужно отслеживать все значения аргументов в каждом вызове. В настоящее время у нас есть макрос, который принимает список всех параметров:

 void SomeApiFunction( bool parameter1, const wchar_t* parameter2 )
 {
     LOG_PARAMETERS( parameter1 << parameter2 )
     //function payload
 }

Макрос расширяется следующим образом:

 #define LOG_PARAMETERS( x ) GlobalLoggerObject << x;

и GlobalLoggerObject имеет тип class CLogger, который имеет operator << перегружен для всех возможных типов параметров - что-то вроде этого:

template<class TypeToLog>
CLogger& operator << (const TypeToLog& parameter)
{
   //logging code
   return *this;
}

чтобы мы могли иметь список параметров любой длины и благодаря цепочке из <<, все они могут быть переданы в макрос, и необходимые конкретные версии operator << создаются в зависимости от типа параметра.

Отлично, за исключением того, что мы должны поддерживать список параметров, полностью синхронных с параметром окружающей функции.

Есть ли какой-то способ просто сразу взять все параметры - что-то вроде этого:

TRACE_ALL_PARAMETERS( UNIVERSAL_MAGIC )

так что нам не нужно явно указывать каждый параметр?

Ответ 1

В C/С++ нет способа сделать это, но это может быть выполнено с помощью внешнего генерации кода.

  • Программист при написании функции ставит TRACE_ALL_PARAMETERS() (с пустым списком аргументов), когда это необходимо.
  • A script анализирует источник, чтобы найти все экземпляры /^\s*TRACE_ALL_PARAMETERS\(/, анализирует ближайший список имен параметров и заменяет все в круглых скобках, следующих за TRACE_ALL_PARAMETERS, извлеченным списком параметров.

Этот script может быть вызван как шаг пользовательской сборки или как макрос IDE.

Тем не менее, мне не кажется целесообразным вложение времени и усложнение процесса управления кодом. Из моего личного опыта редко требуется просто распечатать все параметры функции. Обычно нужно указать произвольное форматирование для некоторых параметров, особым образом получить доступ к некоторым параметрам (например, нет смысла печатать MyStruct* в качестве указателя, обычно я хотел бы печатать p_my_struct->field_1 и p_my_struct->field_2 вместо этого), и т.д.

Итак, я бы продолжил ручную регистрацию и использовал внешний инструмент для оптовой трассировки, если это необходимо. Например. в Windows, WinAPIOverride32, но с дополнительной работой для описания функций в формате, который он понимает (я сделал это один раз, используя GCC-XML для генерации описаний функций).

Ответ 2

Если это действительно проблема, одна из идей может заключаться в том, чтобы добавить проверку времени выполнения для ваших модульных тестов.

Как отмечает Nawaz в одном комментарии, существует макрос __FUNCSIG__, в котором перечислены все типы пареметров. Например, для моей основной версии она расширяется до "int __cdecl main(int,char *[])".

Теперь, что вы можете сделать, это использовать эту строку для выполнения проверки времени выполнения (возможно, даже типов) параметров, зарегистрированных в журнале, и если у вас есть несоответствие, используйте какой-либо механизм вашей тестовой среды, чтобы сигнализировать, что регистрация функций "сломанный".

Что касается проблемы с невозможностью разделения передаваемых параметров на макрос регистрации: я бы попробовал что-то вроде этого:

#define LOG_WITH_CHECK(ValidationString, Params)                          \
    cout << "Validate [" ValidationString << "] vs. [" << #Params << "]\n"; \
    cout << "Now feed params to logging: {" << Params << "}";               \
/**/

int main(int argc, char* argv[])
{
  LOG_WITH_CHECK(__FUNCSIG__, argc << argv[0]);
  ...

То есть вы также проверяете переданные параметры как << разделяемую строку.

Ответ 3

Нет стандартного подтверждающего способа делать то, что вы хотите.

Ответ 4

Вы можете добавить проверку времени компиляции для параметров, переданных в журнал.

#include <iostream>
#include <boost/type_traits.hpp>

template<typename F>
void log(F*
    , typename boost::function_traits<F>::arg1_type p1
    )
{ std::cout << p1 << std::endl; }

template<typename F>
void log(F*
    , typename boost::function_traits<F>::arg1_type p1
    , typename boost::function_traits<F>::arg2_type p2
    )
{ std::cout << p1 << p2 << std::endl; }

// ... more overloads

void foo(int x)
{
    log(&foo, x);
}

int main()
{
    foo(42);
}

Или вы можете избежать этих перегрузок, выведя тип boost.fusion из типа функции.

Ответ 5

Вы можете попробовать использовать нестандартный макрос VA_ARGS. Несмотря на нестандартность, большинство компиляторов поддерживают его.

Очень простой подход состоял бы в том, чтобы "выровнять" все аргументы и напечатать это. С небольшим творческим потенциалом я уверен, что вы можете придумать что-то лучшее:

#include <iostream>
#define ALLARGS( ... ) std::cout << #__VA_ARGS__  << std::endl;

int main( int argc, char* argv[] )
{
    ALLARGS( "two arguments", 1.0 );
    ALLARGS( "five arguments", 1.0, 1.0, true, 'c' );
}