Pertanyaan Fungsi hooking di C ++?


Dengan "mengaitkan" maksud saya kemampuan untuk secara non-intrusif mengesampingkan perilaku suatu fungsi. Beberapa contoh:

  • Cetak pesan log sebelum dan / atau setelah badan fungsi.
  • Bungkus tubuh fungsi dalam tubuh tangkapan yang dicoba.
  • Ukur durasi suatu fungsi
  • dll ...

Saya telah melihat berbagai penerapan dalam berbagai bahasa pemrograman dan pustaka:

  • Pemrograman Berorientasi Aspek
  • Fungsi kelas pertama JavaScript
  • Pola dekorator OOP
  • WinAPI subclassing
  • Ruby method_missing
  • SWIG's %exception kata kunci yang dimaksudkan untuk membungkus semua fungsi dalam blok try / catch dapat (ab) digunakan untuk tujuan hooking

Pertanyaan saya adalah:

  • IMO ini adalah fitur yang sangat bermanfaat yang saya ingin tahu mengapa itu tidak pernah dilaksanakan sebagai fitur bahasa C ++. Apakah ada alasan yang mencegah hal ini menjadi mungkin?
  • Apa saja teknik atau pustaka yang direkomendasikan untuk menerapkan ini dalam program C ++?

32
2017-10-12 17:03


asal


Jawaban:


Jika Anda berbicara tentang menyebabkan metode baru untuk dipanggil sebelum / setelah badan fungsi, tanpa mengubah fungsi tubuh, Anda dapat menggunakannya ini, yang menggunakan suatu kebiasaan shared_ptr deleter untuk memicu fungsi after-body. Ini tidak bisa digunakan try/catch, karena sebelum dan sesudah perlu fungsi terpisah menggunakan teknik ini.

Juga, versi di bawah ini menggunakan shared_ptr, tetapi dengan C ++ 11 Anda harus bisa menggunakannya unique_ptr untuk mendapatkan efek yang sama tanpa biaya menciptakan dan menghancurkan pointer bersama setiap kali Anda menggunakannya.

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

13
2017-10-12 17:54



Ada fitur-fitur khusus kompilator yang dapat Anda manfaatkan seperti, seperti GCC fungsi -finstrument. Compiler lain kemungkinan akan memiliki fitur yang serupa. Lihat ini Pertanyaan SO untuk detail tambahan.

Pendekatan lain adalah menggunakan sesuatu seperti Pembungkus fungsi Bjarne Stroustrup teknik.


5
2017-10-12 17:55



Untuk menjawab pertanyaan pertama Anda:

  • Sebagian besar bahasa dinamis memilikinya method_missing konstruk, PHP memiliki metode sulap (__call dan __callStatic) dan Python memiliki __getattr__. Saya pikir alasan ini tidak tersedia di C + + yang bertentangan dengan sifat C ++ yang diketik. Menerapkan ini di kelas berarti bahwa setiap kesalahan ketik akan berakhir memanggil fungsi ini (saat runtime!), Yang mencegah penangkapan masalah ini pada waktu kompilasi. Mencampur C ++ dengan mengetik bebek tampaknya bukan ide yang bagus.
  • C ++ mencoba untuk secepat mungkin, jadi fungsi kelas satu tidak mungkin.
  • AOP. Sekarang ini lebih menarik, secara teknis tidak ada yang mencegah hal ini ditambahkan ke standar C ++ (terlepas dari fakta bahwa menambahkan lapisan kompleksitas lain ke standar yang sudah sangat kompleks mungkin bukan ide yang bagus). Bahkan ada compiler yang mampu melambaikan kode, AspectC ++ adalah salah satunya. Setahun yang lalu atau lebih itu tidak stabil tetapi sepertinya sejak itu mereka berhasil merilis 1.0 dengan suite uji yang cukup layak sehingga mungkin melakukan pekerjaan sekarang.

Ada beberapa teknik, inilah pertanyaan terkait:

Menirukan CLOS: sebelum,: sesudah, dan: di sekitar dalam C ++.


4
2017-10-12 17:38



IMO ini adalah fitur yang sangat berguna, jadi mengapa itu bukan fitur bahasa C ++? Apakah ada alasan yang mencegah hal ini menjadi mungkin?

C ++ bahasa tidak menyediakan sarana apa pun untuk melakukannya secara langsung. Namun, itu juga tidak menimbulkan kendala langsung terhadap ini (AFAIK). Jenis fitur ini lebih mudah untuk diimplementasikan di interpreter daripada di native code, karena interpretasinya adalah software, bukan instruksi mesin CPU streaming. Anda dapat menyediakan juru bahasa C ++ dengan dukungan untuk kait jika Anda menginginkannya.

Masalahnya adalah Mengapa orang menggunakan C ++. Banyak orang menggunakan C ++ karena mereka menginginkan kecepatan eksekusi yang tinggi. Untuk mencapai tujuan tersebut, kompiler menampilkan kode asli dalam format yang disukai sistem operasi dan mencoba kode keras sebagai banyak barang ke dalam file yang dapat dieksekusi. Bagian terakhir sering berarti alamat komputasi pada waktu kompilasi / tautan. Jika Anda memperbaiki alamat fungsi pada saat itu (atau bahkan lebih buruk, sebariskan fungsi badan) maka tidak ada lagi dukungan untuk kait.

Itu dikatakan, di sana adalah cara untuk membuat hooking murah, tetapi membutuhkan ekstensi compiler dan sama sekali tidak portabel. Raymond Chen membuat blog tentang caranya hot patching diimplementasikan dalam API Windows. Dia juga merekomendasikan penggunaannya dalam kode reguler.


2
2017-10-12 17:42



Setidaknya pada framework c ++ yang saya gunakan menyediakan satu set kelas virtual murni

class RunManager;
class PhysicsManager;
// ...

Masing-masing mendefinisikan satu set tindakan

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

yang merupakan NOP, tetapi dapat ditimpa oleh pengguna yang berasal dari kelas Orangtua.

Gabungkan dengan kompilasi bersyarat (ya, saya tahu "Yuk!") dan Anda bisa mendapatkan apa yang Anda inginkan.


1
2017-10-12 17:59



Ini bukan hal C ++, tetapi untuk menyelesaikan beberapa hal yang Anda sebutkan, saya telah menggunakan variabel lingkungan LD_PRELOAD dalam sistem * nix. Contoh yang bagus dari teknik ini dalam beraksi adalah faketime perpustakaan yang menghubungkan ke fungsi waktu.


0
2017-10-12 17:46



  1. Harus ada cara untuk mengimplementasikan fungsi tanpa mempengaruhi kinerja kode yang tidak menggunakan fungsi tersebut. C ++ dirancang berdasarkan prinsip bahwa Anda hanya membayar biaya kinerja untuk fitur yang Anda gunakan. Memasukkan jika cek di setiap fungsi untuk memeriksa apakah sudah ditimpa akan sangat lambat untuk banyak proyek C ++. Secara khusus, membuatnya bekerja sehingga tidak ada biaya kinerja sementara masih memungkinkan untuk kompilasi independen dari fungsi yang ditimpa dan mengesampingkan akan menjadi rumit. Jika Anda hanya mengizinkan waktu kompilasi menimpa, maka lebih mudah untuk melakukan performantly (linker dapat menangani alamat timpa), tetapi Anda membandingkan dengan ruby ​​dan javascript yang memungkinkan Anda mengubah hal-hal ini saat runtime.

  2. Karena itu akan menumbangkan sistem jenis. Apa artinya bagi sebuah fungsi untuk menjadi pribadi atau non-virtual jika seseorang dapat menimpa perilakunya?

  3. Keterbacaan akan sangat menderita. Fungsi apa pun mungkin memiliki tingkah lakunya yang ditimpa di tempat lain dalam kode! Semakin banyak konteks yang Anda butuhkan untuk memahami apa fungsi yang dilakukan, semakin sulit untuk mencari basis kode yang besar. Hooking adalah bug, bukan fitur. Setidaknya jika bisa membaca apa yang Anda tulis beberapa bulan kemudian adalah persyaratan.


0
2017-10-12 17:58