Is there a better alternative to preprocessor redirection for runtime tracking of an external API?
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;
}
精彩评论