LINUX.ORG.RU

виртуальные колбэки с/с++

 ,


0

3

Всем привет!

Встала задачка подсовывать одной сишной библиотеке свои крестовые колбэки, да вот только я хочу, чтобы они были во всю полиморфными, а библиотека естественно требует или статик-мемберы класса, или не-члены класса. Пробовал примерно вот так обмануть компилятор - не вышло на этапе выполнения:

typedef (void *pF)(int) foo;
class A
{
    private:
        static foo pf;
    public:
        void f(int);
        void register_cb()
        {
            // some library usage code
            l.callback=pf;
        }
        A()
        {
            pf=foo(&A::f);
        }
};
foo A::pf=NULL;

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

Если кому-то кажется, что я хочу странного (создавать виртуальные колбэки), то пусть он расскажет, как внутри устроены, например, boost::thread/std::thread и почему им достаточно callable-объектов, ибо задача в конечном итоге та же (что там в pthread передается)?

★★

Встала задачка подсовывать одной сишной библиотеке свои крестовые колбэки, да вот только я хочу, чтобы они были во всю полиморфными

А если это на нормальном языке раскрыть, так, чтобы было понятно не только вам?

Ну и какой конкретно формат у коллбэков вашей сишной либы?

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Вообще, если совсем изначально, то захотелось в качестве упражнения написать с++-обертку к этому SDL-плееру.

Соответственно, формат колбэков - SDL_AudioCallback. В том коде по ссылке прям очень хочется сделать my_audio_callback виртуальным членом класса.

aido ★★ ()
Ответ на: комментарий от aido

Когда у коллбэков есть отдельный параметр вроде void *user_data, то типичный подход вот такой:

class callback_base {
public:
  virtual ~callback_base() = default;

  static void callback(void * user_data, Uint8 * stream, int len);

protected:
  virtual void on_callback(Uint8 * stream, int len) = 0;
};

void callback_base::callback(void * user_data, Uint8 * stream, int len) {
  auto handler = reinterpret_cast<callback_base*>(user_data);
  handler->on_callback(stream, len);
}

После чего:

class my_callback : public callback_base {
protected:
  void on_callback(Uint8 * stream, int len) override {
    ... // bla-bla-bla
  }
};
my_callback my_handler;
wav_spec.callback = &callback_base::callback;
wav_spec.userdata = &my_handler;

eao197 ★★★★★ ()
Ответ на: комментарий от aido

Но если не хочется связываться с традиционным ООП, то можно и по-молодежному:

class my_handler {
  ... // bla-bla-bla
public:
  void handle(Uint8 * stream, int len) {
    ... // bla-bla-bla
  }
};
...
my_handler handler;
wav_spec.callback = [](void * user_data, Uint8 * stream, int len) {
  (reinterpret_cast<my_handler*>(user_data))->handle(stream, len);
};
wav_spec.userdata = &handler;

eao197 ★★★★★ ()
Ответ на: комментарий от lberserq

ты гонишь, регистранчик. Либе похер где и что передает компилятор. У нее указатель есть, который она вернет в код. А там хоть через астрал передавай.

anonymous ()
Ответ на: комментарий от eao197

Предпочитаю при кастах из void* использовать reinterpret_cast, лучше заметно, что здесь могут быть грабли.

Лол :-) Есть пионэры, которые боятся цепепе из-за всевозможных «segmentation fault» :-) А есть старожилы, которые боятся void* :-) Лол :-)

anonymous ()
Ответ на: комментарий от xaizek

reinterpret_cast

static_cast достаточно же.

Вот-вот :-) Так вот другой читает код и думает, какого хрена тут, в переносимом коде, используется reinterpret_cast, результат которого почти всегда является непереносимым :-) А оказывается, автор имел в виду «возможные грабли с void*» :-)

anonymous ()
Ответ на: комментарий от aido

Искать другую сишную либу, ибо нельзя доверять творениям уродов, которые не позволяют засунуть в callback свой user_data :)

eao197 ★★★★★ ()
Ответ на: комментарий от aido

Обычно оно есть, если автор либы не совсем мудак.

Но я видел вариант, когда нужно писать определенную TLS переменную для колбеков из либы.

anonymous ()
Ответ на: комментарий от eao197

А для работы со звуком какие адекватные либы есть (желательно плюсовые)? а то у меня из 5 вариантов только SDL смогла что-то воспроизвести.

aido ★★ ()
Ответ на: комментарий от aido

Когда-то давно прикручивал BASS к проекту. Смотрел его?

anonymous ()
Ответ на: комментарий от anonymous

если у тебя в callback написано attribute(cdecl) то в rcx указатель на this не окажется и на оффтопе ты указатель на мембер в callback не засунешь. в лучшем случае если метод публичный ты можешь this прокинуть в свой параметр а потом кастануть, что и предложил eao197 обернув оборачивания в классы.

lberserq ()

Идея, что нестатический член класса должен как-то знать, какой именно this ему использовать, видимо, в голову не приходит.

Miguel ★★★★★ ()

Передай this первым аргументом

mittorn ★★★★★ ()

Почему не SDL2_Mixer?Там и поддержка разных форматов

linuhs_user ()
Ответ на: комментарий от aido

Для работы со звуком есть OpenAL (Linux, macOS, Windows, iOS, tvOS) и OpenSL (Android).
Абстракцию или возьмите готовую, или напишите свою.

andreyu ★★★★★ ()
Ответ на: комментарий от aido

О! пасиб! че-то не подумал про user_data.

А что делать, если б его не было?

выделить mmap'ом страницу памяти, поменять у нее mprotect'ом права на PROT_WRITE, записать туда код функции, которая будет вызывать твой виртуальный callback для фиксированного this (объекта), ну, а затем сделать эту память PROT_EXEC. Полученный адрес использовать для статического callback.

Выглядит страшно, на самом деле тебе нужно скомпилировать объектник с твоим статическим callback, а затем найти в нем эту функцию и по какому смещению будет находится адрес объекта, который тебе нужно будет подменить. А затем просто копировать эту скомпилированную функцию и по нужному смещению подменять адрес объекта с виртуальным callback. Чтобы компилятор не «соптимизировал» эту фукнцию ее полным удаление из кода, нужно лишь взять от нее адрес. В общем делать то, что во многих языках с JIT делается постоянно, только в твоем случае это нужно делать вручную, но для очень примитивной задачи (скопировать и переписать адрес).

Вот код для демонстрации идеи.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

// Выделяет RW память заданного размера и возвращает указатель на нее. В случае ошибки
// печатает ошибку и возвращает NULL. В отличие от malloc, память выделяется
// на границе страниц памяти, так что ее можно использовать при вызове mprotect.
void* alloc_writable_memory(size_t size) {
  void* ptr = mmap(0, size,
                   PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (ptr == (void*)-1) {
    perror("mmap");
    return NULL;
  }
  return ptr;
}

void emit_code_into_memory(unsigned char* m) {
  unsigned char code[] = {
    0x48, 0x89, 0xf8,                   // mov %rdi, %rax
    0x48, 0x83, 0xc0, 0x04,             // add $4, %rax
    0xc3                                // ret
  };
  memcpy(m, code, sizeof(code));
}

// Ставит RX права на этот кусок выровненной памяти. Возвращает
// 0 при успехе. При ошибке печатает ошибку и возвращает -1.
int make_memory_executable(void* m, size_t size) {
  if (mprotect(m, size, PROT_READ | PROT_EXEC) == -1) {
    perror("mprotect");
    return -1;
  }
  return 0;
}

const size_t SIZE = 1024;
typedef long (*JittedFunc)(long);

// Выделяет RW память, сохраняет код в нее и меняет права на RX перед
// исполнением.
void emit_to_rw_run_from_rx() {
  void* m = alloc_writable_memory(SIZE);
  emit_code_into_memory(m);
  make_memory_executable(m, SIZE);

  JittedFunc func = m;
  int result = func(2);
  printf("result = %d\n", result);
}

int main()
{
  emit_to_rw_run_from_rx();
  return 0;
}

Попробуй - тебе понравится.

anonymous ()
Ответ на: комментарий от anonymous

Не проще ли libffi если уж захотелось похардкору

anonymous ()
Ответ на: комментарий от anonymous

T * -> void *p -> reinterpret_cast<T *>(p) - это один из немногих правильных кастов через reinterpret_cast.

dzidzitop ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.