Есть ли лучшая альтернатива перенаправлению препроцессора для отслеживания внешнего API во время выполнения?

У меня есть своего рода сложная проблема, которую я пытаюсь решить. Прежде всего, обзор:

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

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

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

// Get an indexed amount
long GetAmount(short Idx);

// Set an indexed amount
void SetAmount(short Idx, long amount);

Это обычные функции C. Одна ошибка, которую я пытаюсь обнаружить во время выполнения, заключается в том, что GetAmount вызывается с Idx, который еще не был установлен с помощью SetAmount.

Теперь все вызовы API содержатся в пространстве имен (назовем его api_ns), однако раньше так было не всегда. Итак, конечно, устаревший код просто выдал «используя пространство имен api_ns;» в своем файле stdafx.h и назвал его хорошим.

Моей первой попыткой было использовать препроцессор для перенаправления вызовов API в мою собственную систему отслеживания. Это выглядело примерно так:

// in FormTrackingFramework.h
class FormTrackingFramework
{
    private:
        static FormTrackingFramework* current;

    public:
        static FormTrackingFramework* GetCurrent();

        long GetAmount(short Idx, const std::string& file, size_t line)
        {
            // track usage, log errors as needed
            api_ns::GetAmount(Idx);
        }
};

#define GetAmount(Idx) (FormTrackingFramework::GetCurrent()->GetAmount(Idx, __FILE__, __LINE__))

Затем в stdafx.h:

// in stdafx.h

#include "theAPI.h"

#include "FormTrackingFramework.h"

#include "LegacyPCHIncludes.h"

Это прекрасно работает для GetAmount и SetAmount, но есть проблема. API также имеет SetString(short Idx, const char* str). В какой-то момент наш унаследованный код добавил перегрузку: SetString(short Idx, const std::string& str) для удобства. Проблема в том, что препроцессор не знает и не заботится о том, вызываете ли вы SetString или определяете перегрузку SetString. Он просто видит «SetString» и заменяет его определением макроса. Что, конечно, не компилируется при определении новой перегрузки SetString.

Я потенциально мог бы изменить порядок #includes в stdafx.h, чтобы включить FormTrackingFramework.h после LegacyPCHIncludes.h, однако это означало бы, что ни один из кодов в дереве включения LegacyPCHIncludes.h не будет отслеживаться.

Итак, я думаю, у меня есть два вопроса на данный момент: 1: как мне решить проблему перегрузки API? 2: Есть ли какой-то другой способ делать то, что я хочу делать, который работает лучше?

Примечание. Я использую Visual Studio 2008 с пакетом обновления 1 (SP1).


person Jeremy Bell    schedule 22.02.2010    source источник


Ответы (2)


Что ж, для случаев, когда вам нужны перегрузки, вы можете использовать экземпляр класса, который перегружает operater() для ряда параметров.

#define GetAmount GetAmountFunctor(FormTrackingFramework::GetCurrent(), __FILE__, __LINE__)

затем создайте GetAmountFunctor:

 class GetAmountFunctor
 {
    public:
      GetAmountFunctor(....) // capture relevant debug info for logging
      {}

      void operator() (short idx, std::string str) 
      {
           // logging here
           api_ns::GetAmount(idx, str);
      }

      void operator() (short idx) 
      {
           /// logging here
           api_ns::GetAmount(Idx);
      }
 };

Это очень много псевдокода, но я думаю, вы поняли идею. Везде, где в вашем унаследованном коде упоминается конкретное имя функции, оно заменяется объектом функтора, и функция фактически вызывается на функторе. Учтите, что вам нужно делать это только для функций, где перегрузки являются проблемой. Чтобы уменьшить объем связующего кода, вы можете создать единую структуру для параметров __FILE__, __LINE__ и передать ее в конструктор в качестве одного аргумента.

person Community    schedule 22.02.2010
comment
Я удивлен, что на это нет ответа... Есть ли что-то непонятное, что мне нужно объяснить? - person ; 23.02.2010
comment
Этот метод намного элегантнее, чем тот, который я использовал (codeproject.com/KB/tips /va_pass.aspx). Спасибо - поиграюсь с этим методом. Я предполагаю, что вы могли бы использовать это с функцией переменного аргумента в стиле printf с соответствующим оператором(), определенным в функторе? - person Jeremy Bell; 23.02.2010
comment
похоже, мне все еще нужен пожиратель стека со страницы codeproject для передачи переменных аргументов из одной функции в другую. Но я все еще могу использовать это для перегрузок. Спасибо еще раз. - person Jeremy Bell; 23.02.2010

Проблема в том, что препроцессор не знает и не заботится о том, вызываете ли вы SetString или определяете перегрузку SetString.

Очевидно, что препроцессор используется потому, что он не обращает внимания на пространство имен.

Хороший подход — стиснуть зубы и перенацелить все большое приложение на использование другого пространства имен api_wrapped_ns вместо api_ns.

Внутри api_wrapped_ns могут быть предусмотрены встроенные функции, которые оборачивают аналоги с похожими сигнатурами в api_ns.

Может даже быть такой переключатель времени компиляции:

namespace api_wrapped_ns {
#ifdef CONFIG_API_NS_WRAPPER
  inline long GetAmount(short Idx, const std::string& file, size_t line)
  {
    // of course, do more than just wrapping here
    return api_ns::GetAmount(Idx, file, line);     
  } 
  // other inlines
#else
  // Wrapping turned off: just bring in api_ns into api_wrapper_ns
  using namespace api_ns;
#endif
}

Также упаковку можно привезти поштучно:

namespace api_wrapped_ns {
  // This function is wrapped;
  inline long GetAmount(short Idx, const std::string& file, size_t line)
  {
    // of course, do more than just wrapping here
    return
  }
  // The api_ns::FooBar symbol is unwrapped (for now)
  using api_ns::FooBar;
}
person Kaz    schedule 12.06.2016