Function hooking in C++?

后端 未结 7 676
一整个雨季
一整个雨季 2020-12-13 02:11

With \"hooking\" I mean the ability to non-intrusively override the behavior of a function. Some examples:

  • Print a log message before and/or after the function
相关标签:
7条回答
  • 2020-12-13 02:38

    To answer your first question:

    • Most dynamic languages have their method_missing constructs, PHP has a magic methods (__call and __callStatic) and Python has __getattr__. I think the reason this isn't available in C++ that it goes against the typed nature of C++. Implementing this on a class means that any typos will end up calling this function (at runtime!), which prevents catching these problems at compile time. Mixing C++ with duck typing doesn't seem to be a good idea.
    • C++ tries to be as fast as possible, so first class functions are out of question.
    • AOP. Now this is more interesting, techincally there's nothing that prevents this being added to the C++ standard (apart from the fact that adding another layer of complexity to an already extremly complex standard is might not be a good idea). In fact there are compilers which are able to wave code, AspectC++ is one of them. A year ago or so it wasn't stable but it looks like since then their managed to release 1.0 with a pretty decent test suite so it might does the job now.

    There are a couple of techniques, here's a related question:

    Emulating CLOS :before, :after, and :around in C++.

    0 讨论(0)
  • 2020-12-13 02:40

    If you're talking about causing a new method to be called before/after a function body, without changing the function body, you can base it on this, which uses a custom shared_ptr deleter to trigger the after-body function. It cannot be used for try/catch, since the before and after need to be separate functions using this technique.

    Also, the version below uses shared_ptr, but with C++11 you should be able to use unique_ptr to get the same effect without the cost of creating and destroying a shared pointer every time you use it.

    #include <iostream>
    #include <boost/chrono/chrono.hpp>
    #include <boost/chrono/system_clocks.hpp>
    #include <boost/shared_ptr.hpp>
    
    template <typename T, typename Derived>
    class base_wrapper
    {
    protected:
      typedef T wrapped_type;
    
      Derived* self() {
        return static_cast<Derived*>(this);
      }
    
      wrapped_type* p;
    
      struct suffix_wrapper
      {
        Derived* d;
        suffix_wrapper(Derived* d): d(d) {};
        void operator()(wrapped_type* p)
        {
          d->suffix(p);
        }
      };
    public:
      explicit base_wrapper(wrapped_type* p) :  p(p) {};
    
    
      void prefix(wrapped_type* p) {
         // Default does nothing
      };
    
      void suffix(wrapped_type* p) {
         // Default does nothing
      }
    
      boost::shared_ptr<wrapped_type> operator->() 
      {
        self()->prefix(p);
        return boost::shared_ptr<wrapped_type>(p,suffix_wrapper(self()));
      }
    };
    
    
    
    
    template<typename T>
    class timing_wrapper : public base_wrapper< T, timing_wrapper<T> >
    {
      typedef  base_wrapper< T, timing_wrapper<T> > base;
      typedef boost::chrono::time_point<boost::chrono::system_clock, boost::chrono::duration<double> > time_point;
    
      time_point begin;
    public:
      timing_wrapper(T* p): base(p) {}
    
    
      void prefix(T* p) 
      {
        begin = boost::chrono::system_clock::now();
      }
    
      void suffix(T* p)
      {
        time_point end = boost::chrono::system_clock::now();
    
        std::cout << "Time: " << (end-begin).count() << std::endl;
      }
    };
    
    template <typename T>
    class logging_wrapper : public base_wrapper< T, logging_wrapper<T> >
    {
      typedef  base_wrapper< T, logging_wrapper<T> > base;
    public:
      logging_wrapper(T* p): base(p) {}
    
      void prefix(T* p) 
      {
        std::cout << "entering" << std::endl;
      }
    
      void suffix(T* p) 
      {
        std::cout << "exiting" << std::endl;
      }
    
    };
    
    
    template <template <typename> class wrapper, typename T> 
    wrapper<T> make_wrapper(T* p) 
    {
      return wrapper<T>(p);
    }
    
    
    class X 
    {
    public:
      void f()  const
      {
        sleep(1);
      }
    
      void g() const
      {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
      }
    
    };
    
    
    
    int main () {
    
      X x1;
    
    
      make_wrapper<timing_wrapper>(&x1)->f();
    
      make_wrapper<logging_wrapper>(&x1)->g();
      return 0;
    }
    
    0 讨论(0)
  • 2020-12-13 02:43
    1. There has to be a way to implement the functionality without affecting the performance of code that doesn't use the functionality. C++ is designed on the principle that you only pay performance costs for the features you use. Inserting if checks in every function to check if its been overridden would be unacceptably slow for many C++ projects. In particular, making it work so that there's no performance cost while still allowing for independent compilation of the overridden and overriding functions will be tricky. If you only allow for compile time overriding, then it's easier to do performantly (the linker can take care of overwriting addresses), but you're comparing to ruby and javascript which let you change these things at runtime.

    2. Because it would subvert the type system. What does it mean for a function to be private or non-virtual if someone can override its behavior anyway?

    3. Readability would greatly suffer. Any function might have its behavior overridden somewhere else in the code! The more context you need to understand what a function does, the harder it is to figure out a large code base. Hooking is a bug, not a feature. At least if being able to read what you wrote months later is a requirement.

    0 讨论(0)
  • 2020-12-13 02:58

    At least on c++ framework that I use provides a set of pure virtual classes

    class RunManager;
    class PhysicsManager;
    // ...
    

    Each of which defined a set of actions

    void PreRunAction();
    void RunStartAction()
    void RunStopAction();
    void PostRunAction();
    

    which are NOPs, but which the user can override where deriving from the Parent class.

    Combine that with conditional compilation (yeah, I know "Yuk!") and you can get what you want.

    0 讨论(0)
  • 2020-12-13 02:59

    This is not a C++ thing, but to accomplish some of things you mention, I have used the LD_PRELOAD environment variable in *nix systems. A good example of this technique in action is the faketime library that hooks into the time functions.

    0 讨论(0)
  • 2020-12-13 03:01

    There are compiler-specific features you can leverage such as, such as GCC's -finstrument-functions. Other compilers will likely have similar features. See this SO question for additional details.

    Another approach is to use something like Bjarne Stroustrup's function wrapping technique.

    0 讨论(0)
提交回复
热议问题