Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Как получить индекс/смещение pure виртуальной ф-ии

401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Необходимо получить индекс виртуальной ф-ии (в vftbl) по ее имени.

Смещение для члена класса (pointer-to-member) можно получить с помощью макроса
 
Код:
#define offsetof(s,m)   (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))

он возвращает смещение члена относительно начала структуры/класса.

Для функций этот макрос не работает. Но чисто виртуальные ф-ии хранятся в таблице, которая находится в начале реализующего класса.

например:
Код:
class IInterface
{
  public:
  virtual int f1() = 0;
  virtual int f2() = 0;
};

class CImplementation: public IInterface
{
  public:
  int f1() {return 0;};
  int f2() {return 0;};
};


Индекс f1 = 0, f2 = 1
Смещение f1 = 0, f2 = 4 (для 32х битных машин)

Можно ли получить смещение или индекс ф-ий по их имени используя определения классов IInterface и/или CImplementation?
3
21 августа 2007 года
Green
4.8K / / 20.01.2000
Т.к. вопрос находится в разделе "общие вопросы", отвечаю: в общем случае - нельзя, т.к. vtbl разными компиляторами реализуется по-разному.
Но даже в конкретном случае для конкретного компилятора если нечто подобное получить можно, то только методом хака, что не есть хорошо для серьезного програмного продукта.
Кроме того, в отличие от поля, по полученному смещению можно будет только читать, писать можно будет опять же только с помощью хака (изменить атрибут доступа к памяти).

Поэтому рекомендую подойти к проблеме с другой стороны: а зачем тебе это вообще надо?
Может, можно все сделать иначе?
309
21 августа 2007 года
el scorpio
1.1K / / 19.09.2006
Не знаю, зачем это может быть нужно, но с практической точки зрения куда полезнее получить адрес метода класса, используя указатель на функцию. Просто нужно, объявляя тип указателя, прописать класс
 
Код:
typedef (int __fastcall *TClassName::TfMethod) (int Val1, int Val2);
TfMethod ptr = &(TMyClass::Method); // Инициализация
TMyClass MyObject; TMyClass *pMyObject = &MyObject;

(MyObject.*)ptr (ПАРАМЕТРЫ); // Прямое использование  
(pMyObject->*)ptr (ПАРАМЕТРЫ); // Косвенное использование

Кстати, если присавоить указателя адрес виртуального (даже абстрактного) метода, то его значением станет элемент таблицы вирт. функций. Соответственно, будет происходить виртуализация в зависимости от реального объекта.
401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Смещение нужно для вызова ф-ии, т.е. доступа "только чтение" вполне хватит.

Сама задача в кратце выглядит так:

имеется базовый класс-интерфейс. имеются несколько классов-интерфейсов наследующие базовый класс. имеется класс-контейнер базового класса.

Код:
class IBase
{
  virtual void notmatter() = 0;
};

class IInterface1: public IBase
{
  public:
    virtual void f1_1() = 0;
    virtual void f1_2() = 0;
};

class IInterface2: public IBase
{
  public:
    virtual void f2_1() = 0;
    virtual void f2_2() = 0;
};

class CContainer
{
  IBase m_pBase[10];
};


В контейнере CContainer в качестве m_pBase могут быть как IInterface1 так и IInterface2. Названия и прототипы ф-ий, содержащихся в IInterface1 отличаются от содержащихся в IInterface2.

Необходимо, чтобы CContainer вызывал заданную ф-ию из всех объектов содержащихся в нем.

Например:
 
Код:
CContainer Container;
...
// инициализация контейнера объектами IInterface2
...
Container.Run(f2_1, /* параметры ф-ии*/);

Container.Run должна запустить ф-ию f2_1 для каждого из m_pBase.

Вот я и думал, если передавать в качестве параметра индекс виртуальной ф-ии, то можно будет вызывать ее по индексу (методом хака :) используя ассемблерную вставку)
3
21 августа 2007 года
Green
4.8K / / 20.01.2000
Я уверен, что это вполне можно сделать по-человечески без хака.
Вот только скажи, каким образом функция f2_1 будет вызываться для екземпляра класса IInterface1, который не имеет такого метода, но может так же содежаться в твоем контейнере?

Кроме того, твой контейнер вызывает так же сомнения. Для чего он нужен? Почему ты задаешь вызов методов содержимого таким странным образом? Каким образом ты собираешься контролировать различные наборы параметров вызываемых методов?

Может лучше иметь контейнер функторов или отложенных вызовов?

Мне кажется, что у тебя серьезная ошибка в проектировании. Думаю (уверен), что можно сделать значительно красивее. Расскажи, постараемся помочь.
401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Цитата: el scorpio
Не знаю, зачем это может быть нужно, но с практической точки зрения куда полезнее получить адрес метода класса, используя указатель на функцию.
...
Кстати, если присавоить указателя адрес виртуального (даже абстрактного) метода, то его значением станет элемент таблицы вирт. функций. Соответственно, будет происходить виртуализация в зависимости от реального объекта.



Использование адреса метода класса (классический pointer-to-member-function) не подходит, поскольку приходится завязываться на конкретный класс:
можно вызвать IInterface2->*pf2_1, но не IBase->*pf2_1

если присвоить указателю адрес абстрактного метода, то он показывает на элемент статической таблицы виртуальных функций. А "виртуализация" достигается инициализированием регистра ecx (в MS-компиляторе так) перед вызовом. ecx = this pointer (или не через ecx, а через стек)

401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Цитата: Green
Я уверен, что это вполне можно сделать по-человечески без хака.
Вот только скажи, каким образом функция f2_1 будет вызываться для екземпляра класса IInterface1, который не имеет такого метода, но может так же содежаться в твоем контейнере?

Кроме того, твой контейнер вызывает так же сомнения. Для чего он нужен? Почему ты задаешь вызов методов содержимого таким странным образом? Каким образом ты собираешься контролировать различные наборы параметров вызываемых методов?

Мне кажется, что у тебя серьезная ошибка в проектировании. Думаю (уверен), что можно сделать значительно красивее. Расскажи, постараемся помочь.




отвечаю по пунктам: :)
ф-ия f2_1 не должна вызываться для IInterface1. за это отвечает программист. Контейнер состоит только из IInterface1 или только из IInterface2.

Контейнер содержит динамически поключаемые плагины (dll), которые инициализируются одинаковым способом, но имеют разные интерфейсы. все они выведены из IBase. На самом деле там не плоская цепочка (массив) элементов, а древовидная структура. Какой из child'ов дерева запускать определяет значение, которое вернула вызванная ф-ия parent'а. Т.е. берем root объект, вызываем ему, например, f2_2. ф-ия вернула, например, 3. Берем третий child, вызываем ему f2_2 и все повторяется до конца дерева.

Можно специализировать дерево например с помощью калбэков. Т.е. чтобы контейнер делал обход дерева, а в калбэк ф-ии производился собственно вызов f2_2 или f1_2 и т.д., но не хочется писать для каждой ф-ии каждого интерфейса IInterface* свою калбэк ф-ию.
Можно написать класс, наследующий CContainer и написать обертки для каждой из ф-ий IInterface*, но опять же - не хочется.. если изменится интерфейс - переписывать все обертки.

различные параметры можно передавать через ... (Variable Number of Arguments), а внутри Run() обрабатывать va_list и пихать их в стек (ассемблерная вставка)

ошибка в проектировании - согласен. но все-таки заманчивая идея, чтобы дерево само вызывало нужные ф-ии своих объектов :)

3
21 августа 2007 года
Green
4.8K / / 20.01.2000
Цитата: Br@in RIPper
Использование адреса метода класса (классический pointer-to-member-function) не подходит, поскольку приходится завязываться на конкретный класс:
можно вызвать IInterface2->*pf2_1, но не IBase->*pf2_1

если присвоить указателю адрес абстрактного метода, то он показывает на элемент статической таблицы виртуальных функций. А "виртуализация" достигается инициализированием регистра ecx (в MS-компиляторе так) перед вызовом. ecx = this pointer (или не через ecx, а через стек)


Ерунда!

Убедись сам:

Код:
struct A
{
    virtual void func() = 0;
};

struct B :A
{
    virtual void func() {
        std::cout << "B" << std::endl;
    }
};

struct C :A
{
    virtual void func() {
        std::cout << "C" << std::endl;
    }
};

int main()
{
    void (A::*pf)() = &A::func;

    A* pB = new B;
    A* pC = new C;

    (pB->*pf)();
    (pC->*pf)();

    delete pB;
    delete pC;

    return 0;
}


P.S. Не допускай глупой ошибки - пытаться оперировать языком С++, понятиями ассемблера. При чем тут регистры?
3
21 августа 2007 года
Green
4.8K / / 20.01.2000
Цитата: Br@in RIPper

отвечаю по пунктам: :)
ф-ия f2_1 не должна вызываться для IInterface1. за это отвечает программист. Контейнер состоит только из IInterface1 или только из IInterface2.


Тогда есть смысл создать два разных контейнера для двух разных типов.
В этом тебе может помочь шаблон.

Цитата: Br@in RIPper

Контейнер содержит динамически поключаемые плагины (dll), которые инициализируются одинаковым способом, но имеют разные интерфейсы. все они выведены из IBase. На самом деле там не плоская цепочка (массив) элементов, а древовидная структура. Какой из child'ов дерева запускать определяет значение, которое вернула вызванная ф-ия parent'а. Т.е. берем root объект, вызываем ему, например, f2_2. ф-ия вернула, например, 3. Берем третий child, вызываем ему f2_2 и все повторяется до конца дерева.


Если вызываются виртуальные методы с одним именем (f2_2), то в чем проблема создать контейнер таких однотипных (с т.з. интерфейса) объектов?

Цитата: Br@in RIPper

Можно специализировать дерево например с помощью калбэков. Т.е. чтобы контейнер делал обход дерева, а в калбэк ф-ии производился собственно вызов f2_2 или f1_2 и т.д., но не хочется писать для каждой ф-ии каждого интерфейса IInterface* свою калбэк ф-ию.


Это я не понял.

Цитата: Br@in RIPper

различные параметры можно передавать через ... (Variable Number of Arguments), а внутри Run() обрабатывать va_list и пихать их в стек (ассемблерная вставка)


Плохая идея. Очень плохая.
Отказывайся от va_list и пр. void*, тем более если пишешь на C++.

Цитата: Br@in RIPper

ошибка в проектировании - согласен. но все-таки заманчивая идея, чтобы дерево само вызывало нузные ф-ии своих объектов :)


Ну вообще-то это обычная практика.
Только делается это несколько иначе.
См. паттерн "посетитель".

Почему бы тебе не сделать единый интерфейс для инициализации?
И передавать в него не набор параметров, а один параметр traits или context, т.е. объект содержащий всю необходимую для инициализации информацию.

401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Цитата: Green
Ну вообще-то это обычная практика.
Только делается это несколько иначе.
См. паттерн "посетитель".

Почему бы тебе не сделать единый интерфейс для инициализации?
И передавать в него не набор параметров, а один параметр traits или context, т.е. объект содержащий всю необходимую для инициализации информацию.



Ну в принципе у меня похоже и реализовано сейчас (если я правильно понял суть "посетителя"), я вот как раз думал как от этого уйти, потому, что для вызова каждой ф-ии надо писать обертку: метод Visit, объект traits или context и т.д...

401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Для лучшего понимания можно предельно упростить задачу.
Имеем классы:
Код:
class CNode
{
  public:
    void f1();
    int f2(int param);
    ...
    complex f99(int param1, ..., float param12);
};

class CContainer
{
    CNode m_Node[10];
};


Нужно иметь возможность вызвать для каждого из m_Node любую из ф-ий f1-f99
Схематически можно записать так:
 
Код:
CContainer Container;
Container.f1();  // вызывается ф-ия f1 для каждого из m_Node
int i = Container.f2(12);  // вызывается ф-ия f2 для каждого из m_Node
// и т.д.


Одно условие: цикл перебирающий m_Node должен происходить внутри CContainer
3
21 августа 2007 года
Green
4.8K / / 20.01.2000
Ну так в чем проблема?
Код:
class CContainer
{
    CNode m_Node[10];
public:
    void f1() {
        for(int i=0; i<sizeof(m_Node)/sizeof(m_Node[0]); i++) {
            m_Node.f1();
        }
    }

    int f2(int param) {
        for(int i=0; i<sizeof(m_Node)/sizeof(m_Node[0]); i++) {
            m_Node.f2(param);
        }
        return some_value;
    }
};
401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Цитата: Green
Ну так в чем проблема?



Ну вот проблема, что не хочется писать для каждой из 99и ф-ий обертку. Потому, что интерфейс может измениться и придется обертки переделывать.
И еще. Наряду с вызовом ф-ии производится еще некая обработка. Цикл выглядит будет примерно так для всех ф-ий f*:

 
Код:
for(int i=0; i<sizeof(m_Node)/sizeof(m_Node[0]); i++) {
    m_Node.Init();
    EnterCriticalSection(&CS);
    m_Node.f1(); // m_Node.f2(param), m_Node.f99(...), etc
    LeaveCriticalSection(&CS);
    m_Node.Free();
  }


Т.е. имеется некоторое количество кода, универсального для циклов всех ф-ий f1-f99, который не хочется просто так копировать. Единственное различие собственно вызов m_Node.f*();

В остальном такой метод хорош :)
3
21 августа 2007 года
Green
4.8K / / 20.01.2000
А ты все 99 методов используешь?
Можно, конечно, сделать шаблонный класс из CContainer, который все сделает автоматически. Но это довольно сложно.
Значительно проще выяснить все ли 99 методов используются или привести все их или некоторые (т.е. сделать несколько групп однотипных вызовов) к одному виду, т.е. сделать одинаковые списки аргументов и возвращаемое значение.

По большому счету, даже типы аргументов не главное. Главное, чтоб количество аргументов было одинаковое.

Отсюда вывод: можно сделать все полностью автоматическим, но при этом придется нафигачить столько вариантов частной специализации шаблонов, сколько различных вариантов количества аргументов может быть.
Например, если количество аргументов от 0 до 16, значит должнео быть как минимум 17 вариантов специализации шаблонов.
401
21 августа 2007 года
Br@in RIPper
289 / / 15.02.2003
Ну 99 это я для примера. Пока используется штук 7-10, но думаю добавится еще не мало.
В общем путь, где используются обертки понятен, можно использовать его в нескольких разных подходах. Интересно, можно ли что-нибудь сделать не используя обертки?

P.S. сделать аргументы одинаковыми у всех ф-ий - не получится. А если и получится, то тогда что - использовать pointer-to-member-function?
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог