代码之家  ›  专栏  ›  技术社区  ›  Jeremy Bell

对于外部API的运行时跟踪,是否有更好的预处理器重定向替代方案?

  •  1
  • Jeremy Bell  · 技术社区  · 15 年前

    我有一个棘手的问题要解决。首先,概述:

    我有一个不受我控制的外部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 功能。我试图在运行时检测到的一个错误是,当使用尚未设置setAmount的IDX调用getAmount时。

    现在,所有的API调用都包含在一个名称空间中(称之为API),但是它们并不总是在过去。所以,当然,遗留代码只是在stdafx.h文件中抛出了一个“using namespace api”,并称之为good。

    我的第一次尝试是使用预处理器将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重载时不会编译它。

    我可能会重新排序stdafx.h中的includes,以便在legacychincludes.h之后包含formtrackingframework.h,但是这意味着legacychincludes.h include树中的任何代码都不会被跟踪。

    所以我想我现在有两个问题: 1:如何解决API过载问题? 2:有没有其他的方法可以更好地完成我想做的事情?

    注意:我使用的是带有SP1的Visual Studio 2008。

    2 回复  |  直到 8 年前
        1
  •  1
  •   user180326    15 年前

    对于需要重载的情况,可以使用重载的类实例 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__ ,并将其作为一个参数传递到构造函数中。

        2
  •  0
  •   Kaz    8 年前

    问题是,预处理器不知道或不关心您是调用setString还是定义setString重载。

    显然,使用预处理器的原因是它忽略了名称空间。

    一个好的方法是咬紧牙关,重新定位整个大型应用程序以使用不同的名称空间。 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;
    }