开发者

Is there a better alternative to preprocessor redirection for runtime tracking of an external API?

开发者 https://www.devze.com 2022-12-21 02:31 出处:网络
I have sort of a tricky problem I\'m attempting to solve. First of all, an overview: I have an external API not under my control, which is used by a massive amount of legacy code.

I have sort of a tricky problem I'm attempting to solve. First of all, an overview:

I have an external API not under my control, which is used by a massive amount of legacy code.

  • There are several classes of bugs in the legacy code that could potentially be detected at run-time, if only the external API was written to track its own usage, but it is not.
  • I need to find a solution that would allow me to redirect calls to the external API into a tracking framework that would track api usage and log errors.
  • Ideally, I would like the log to reflect the file and line number of the API call that triggered the error, if possible.

Here is an example of a class of errors that I would开发者_如何学编程 like to track. The API we use has two functions. I'll call them GetAmount, and SetAmount. They look something like this:

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

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

These are regular C functions. One bug I am trying to detect at runtime is when GetAmount is called with an Idx that hasn't already been set with SetAmount.

Now, all of the API calls are contained within a namespace (call it api_ns), however they weren't always in the past. So, of course the legacy code just threw a "using namespace api_ns;" in their stdafx.h file and called it good.

My first attempt was to use the preprocessor to redirect API calls to my own tracking framework. It looked something like this:

// 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__))

Then, in stdafx.h:

// in stdafx.h

#include "theAPI.h"

#include "FormTrackingFramework.h"

#include "LegacyPCHIncludes.h"

Now, this works fine for GetAmount and SetAmount, but there's a problem. The API also has a SetString(short Idx, const char* str). At some point, our legacy code added an overload: SetString(short Idx, const std::string& str) for convenience. The problem is, the preprocessor doesn't know or care whether you are calling SetString or defining a SetString overload. It just sees "SetString" and replaces it with the macro definition. Which of course doesn't compile when defining a new SetString overload.

I could potentially reorder the #includes in stdafx.h to include FormTrackingFramework.h after LegacyPCHIncludes.h, however that would mean that none of the code in the LegacyPCHIncludes.h include tree would be tracked.

So I guess I have two questions at this point: 1: how do I solve the API overload problem? 2: Is there some other method of doing what I want to do that works better?

Note: I am using Visual Studio 2008 w/SP1.


Well, for the cases you need overloads, you could use a class instance that overloads operater() for a number of parameters.

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

then, make a 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);
      }
 };

This is very much pseudocode but I think you get the idea. Whereever in your legacy code the particular function name is mentioned, it is replaced by a functor object, and the function is actually called on the functor. Do consider you only need to do this for functions where overloads are a problem. To reduce the amount of glue code, you can create a single struct for the parameters __FILE__, __LINE__, and pass it into the constructor as one argument.


The problem is, the preprocessor doesn't know or care whether you are calling SetString or defining a SetString overload.

Clearly, the reason the preprocessor is being used is that it it oblivious to the namespace.

A good approach is to bite the bullet and retarget the entire large application to use a different namespace api_wrapped_ns instead of api_ns.

Inside api_wrapped_ns, inline functions can be provided which wrap counterparts with like signatures in api_ns.

There can even be a compile time switch like this:

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
}

Also, the wrapping can be brought in piecemeal:

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;
}
0

精彩评论

暂无评论...
验证码 换一张
取 消