Necromancing.
Я думаю, что ответы на сегодняшний день немного неясны.
Давайте сделаем пример:
Предположим, у вас есть массив пикселей (массив значений ARGB int8_t)
// A RGB image
int8_t* pixels = new int8_t[1024*768*4];
Теперь вы хотите создать PNG.
Для этого вы вызываете функцию toJpeg
bool ok = toJpeg(writeByte, pixels, width, height);
где writeByte - функция обратного вызова
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
Проблема здесь: FILE * output должен быть глобальной переменной.
Очень плохо, если вы находитесь в многопоточной среде (например, http-сервер).
Так что вам нужен какой-то способ сделать вывод неглобальной переменной, сохранив при этом сигнатуру обратного вызова.
Непосредственное решение, которое приходит на ум, - это замыкание, которое мы можем эмулировать, используя класс с функцией-членом.
class BadIdea {
private:
FILE* m_stream;
public:
BadIdea(FILE* stream) {
this->m_stream = stream;
}
void writeByte(unsigned char oneByte){
fputc(oneByte, this->m_stream);
}
};
А потом сделай
FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);
bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);
Однако, вопреки ожиданиям, это не работает.
Причина в том, что функции-члены C ++ реализованы подобно функциям расширения C #.
Итак, у вас есть
class/struct BadIdea
{
FILE* m_stream;
}
и
static class BadIdeaExtensions
{
public static writeByte(this BadIdea instance, unsigned char oneByte)
{
fputc(oneByte, instance->m_stream);
}
}
Поэтому, когда вы хотите вызвать writeByte, вам нужно передать не только адрес writeByte, но и адрес экземпляра BadIdea.
Итак, когда у вас есть typedef для процедуры writeByte, и это выглядит так
typedef void (*WRITE_ONE_BYTE)(unsigned char);
И у вас есть подпись writeJpeg, которая выглядит следующим образом
bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t
width, uint32_t height))
{ ... }
принципиально невозможно передать двухадресную функцию-член указателю на одноадресную функцию (без изменения writeJpeg), и нет никакого способа обойти это.
Следующая лучшая вещь, которую вы можете сделать в C ++, это использовать лямбда-функцию:
FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp); };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
Однако, поскольку lambda не делает ничего иного, чем передача экземпляра в скрытый класс (такой как класс «BadIdea»), вам необходимо изменить сигнатуру writeJpeg.
Преимущество лямбды перед ручным классом в том, что вам просто нужно изменить один typedef
typedef void (*WRITE_ONE_BYTE)(unsigned char);
до
using WRITE_ONE_BYTE = std::function<void(unsigned char)>;
И тогда вы можете оставить все остальное нетронутым.
Вы также можете использовать std :: bind
auto f = std::bind(&BadIdea::writeByte, &foobar);
Но это, за сценой, просто создает лямбда-функцию, которая затем также нуждается в изменении в typedef.
Так что нет, нет способа передать функцию-член методу, который требует статического указателя на функцию.
Но лямбды - это легкий путь, если у вас есть контроль над источником.
В противном случае вам не повезло.
Вы ничего не можете сделать с C ++.
Примечание:
std :: function требует #include <functional>
Однако, поскольку C ++ также позволяет вам использовать C, вы можете сделать это с помощью libffcall в простом C, если вы не против связать зависимость.
Загрузите libffcall из GNU (по крайней мере, в Ubuntu, не используйте пакет с дистрибутивом - он не работает), разархивируйте.
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
main.c:
#include <callback.h>
// this is the closure function to be allocated
void function (void* data, va_alist alist)
{
int abc = va_arg_int(alist);
printf("data: %08p\n", data); // hex 0x14 = 20
printf("abc: %d\n", abc);
// va_start_type(alist[, return_type]);
// arg = va_arg_type(alist[, arg_type]);
// va_return_type(alist[[, return_type], return_value]);
// va_start_int(alist);
// int r = 666;
// va_return_int(alist, r);
}
int main(int argc, char* argv[])
{
int in1 = 10;
void * data = (void*) 20;
void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
// void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
// void(*incrementer1)(int abc) starts to throw errors...
incrementer1(123);
// free_callback(callback);
return EXIT_SUCCESS;
}
А если вы используете CMake, добавьте библиотеку компоновщика после add_executable
add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)
или вы можете просто динамически связать libffcall
target_link_libraries(BitmapLion ffcall)
Примечание:
Возможно, вы захотите включить заголовки и библиотеки libffcall или создать проект cmake с содержимым libffcall.