Çalışma zamanında bir yöntemi değiştirmek mümkün müdür?

Çalışma zamanında bir yöntemi geçersiz kılma özelliğine sahip bir eklenti sistemi yapmak istiyorum.

Bazı cevaplar fonksiyon işaretçileri diyor, peki ya tanımlanmış bir fonksiyon ya da sınıf?

Bunun gibi:

class foo
{
  public:
    bar(int foobar);
}

Bunun için işlev göstergesini almanın ya da değiştirmenin bir yolu var mı?

Btw, kancalama bir cevap olarak kabul edilmez çünkü platform çok özel ve tehlikelidir.

3
@ ibre5041: Linux çekirdeği, işleri basitleştiren C ++ 'ı kullanmaz. Örneğin, (operatör) aşırı yükleme yok.
katma yazar MSalters, kaynak
C ve C ++ farklı dillerdir.
katma yazar Yu Hao, kaynak
Mümkün ve platforma özgüdür. Örneğin Linux çekirdeğinde (ksplice) kullanılır. Yürütülebilir formatla bir dilden daha çok ilgilidir. Not: Ne elde etmek istiyorsun?
katma yazar ibre5041, kaynak

8 cevap

Eklenti sistemi yapmak için çalışma zamanında bir sınıfın yöntemini değiştirmeniz gerekmez.

Yöntemin ne yaptığını polimorfizm 'i veya ' e başka bir yolla değiştirebilirsiniz. bir nesneyi yapılandırın .

Check out the answers to the following question: What's safe for a C++ plug-in system?

9
katma

Çalışma zamanı işlevi "değiştirme" birkaç teknikten biriyle sağlanabilir:

  1. Polymorphism
  2. Standard library facilities such as std::function
  3. Third party libraries or platform specific techniques to translate or dispatch function calls.

Hangi seçeneğin daha iyi olduğu, kullanım amacına ve hedef ortamlara büyük ölçüde bağlıdır. Örneğin; Eklenti sistemleri polimorfizmden iyi bir şekilde faydalanabilir (uygun fabrikalarla ve muhtemelen şablon yöntemi modeli ) dahili işlev yönlendirmesi std :: function öğesini kullanabilir.

Bu teknikler işlevlerin hiçbirini gerçekten "değiştiremez", ancak işlev çağrısını gerektiği gibi yönlendirmek için çalışma zamanında ayarlanabilir.

Note I've focused on the C++ aspects of the question (it was tagged C and C++ but the sample code is C++).

5
katma

Doğrudan bir yöntemi değiştiremezsiniz, ancak bu başka bir dolaylı katmanla çözülebilir.

#include 
#include 

class Foo
{
private:
    void default_bar(int value)
    {
        std::cout << "The default function called\n";
    }
    std::function the_function = &Foo::default_bar;
public:
    void replace_bar(std::function new_func)
    {
        the_function = new_func;
    }
    void bar(int value)
    {
        the_function(this, value);
    }
    void baz(int value)
    {
        std::cout << "baz called\n";
    }
};

void non_member(Foo* self, int value)
{
    std::cout << "non-member called\n";
}

int main()
{
    Foo f;
    f.bar(2);
    f.replace_bar(&Foo::baz);
    f.bar(2);
    f.replace_bar(non_member);
    f.bar(2);
    f.replace_bar([](Foo* self, int value){ std::cout << "Lambda called\n"; });
    f.bar(2);
}

Şu anda bu, örneğin yönteminin yerini alıyor. Bir sınıfın yöntemini değiştirmek istiyorsanız, the_function statik hale getirin (daha da iyisi, statik başlatma sırası fiyaskounu önlemek için statik bir değişken döndüren statik bir yöntem yapın)

4
katma

Asıl soruya dönelim: "C/C ++ 'da çalışma zamanında bir yöntemi değiştirmek mümkün mü?" Mümkündür ve bunun için bazı kullanım durumları vardır, ancak (diğerlerinin dediği gibi) bu kullanım durumlarının çoğu geçerli değildir sana. Bu da çok basit değil.

Örneğin, linux kernell kpatch veya kGraft . Bu oldukça karmaşık bir mekanizmadır - tabii ki çok taşınabilir değildir ve kullanıcı programlarında çok kullanışlı değildir, çünkü bu teknikler linux çekirdeğinde pişirilen mekanizmalara dayanır.

2
katma

C/C ++ için mümkün değil sanırım.

Fonksiyon kodu ikili olarak derlenmiştir, çarpıcı çalışma zamanını değiştiremezsiniz.

Bence yorumlayıcı diller bu konuda iyi.

1
katma

C'nin hiçbir yöntemi yoktur (yalnızca işlevler), bu nedenle sorunuz C'de anlamsızdır.

C ++ 11 'de değiştirilecek yöntemin sanal olduğu varsayımıyla ve C ++ uygulamanızın, nesnenin başında bulunan bir vtable işaretçisi kullandığını varsayarsak (bu genellikle Linux'ta GCC için geçerlidir) ve hem eski hem de yeni sınıfların aynı boyutta olması ve ortak bir temel sınıftan tek bir miras kullanıyorsanız (ör. FooBase ), new operatörünü yerleştirmeyi kullanabilirsiniz.

class FooBase {
   virtual ~FooBase();
   virtual int bar(int); 
   /// etc
}

class ProgramFoo : public FooBase {
 virtual ~ProgramFoo();
 virtual int bar (int);
 /// other fields and methods
};

ve eklentinizde:

class PluginFoo : public FooBase {
 virtual ~ProgramFoo();
 virtual int bar (int);
 /// other fields and methods
 static_assert(sizeof(PluginFoo) == sizeof(ProgramFoo), 
               "invalid PluginFoo size");
};

o zaman gibi bazı eklenti işlevi olabilir

extern "C" FooBase*mutate_foo(ProgramFoo*basep)
{
   basep->~ProgramFoo();//destroy in place, but don't release memory
   return new(basep) PluginFoo();//reconstruct in same place
}

umarım eski vptr'yi yenisiyle geçersiz kılar.

ancak bu kötü kokuyor , muhtemelen tanımsız davranış C ++ 11 standardına göre ancak bazı C ++ uygulamaları üzerinde çalışabilir ve kesinlikle uygulamaya özeldir. Bu şekilde kodlamayı önermiyorum, bazen "işe yarar" olsa bile.

Deyimsel yol, üye işlev işaretçilerini veya C ++ 11 kapatmaları kullanmaktır.

Eklenti mimariniz yanlış tasarlanmış gibi görünüyor. İyi ilham almak için Qt eklentileri 'ne bakın.

1
katma

ayrıca MSVC derleme seçeneğine/hotpatch'a bakın. Bu, her satırda olmayan yöntemin en az 2 baytlık bir komutla başladığı bir kod oluşturacaktır (kısa jmp göreceli 0 - yani NOP). Daha sonra çalışan uygulamanızın görüntüsünü yeniden yazabilir ve yönteminizin yeni sürümünde uzun jmp depolayabilirsiniz.

Düzeltilebilir Resim Oluşturma bölümüne bakın.

Ayrıca örneğin Linux'ta (uzun zaman önce) iki kez "fopen" adı verilen bir işleve sahip oldunuz. Bunlardan biri glibc kütüphanesinde, diğeri libpthread'de tanımlandı. Sonuncusu iplik güvenli idi. Ve libpthread 'i kaldırdığınızda, "fopen" fonksiyonunun üzerine "atlamacı" üzerine yazılır ve libpthread'den bir fonksiyon kullanılır.

Ama bu gerçekten amacına bağlı.

1
katma
Ve /ob0 ile satır içi kapanır, böylece her yöntem çalışırken düzeltilebilir. Dezavantajı: vector :: operator [] aynı zamanda sıcak şekilde yapıştırılabilir ve bu muhtemelen istediğiniz şey değildir.
katma yazar MSalters, kaynak

On Unix systems (e.g. Linux), dlsym is very useful for loading functions or entire libraries at runtime in C/C++. See e.g. http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html

0
katma