Некромантування.
Я думаю, що відповіді на сьогоднішній день трохи незрозумілі.
Давайте зробимо приклад:
Припустимо, у вас є масив пікселів (масив значень ARGB int8_t)
int8_t* pixels = new int8_t[1024*768*4];
Тепер ви хочете створити PNG. Для цього ви викликаєте функцію до JPEG
bool ok = toJpeg(writeByte, pixels, width, height);
де writeByte - це функція зворотного виклику
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
Проблема тут: вихід FILE * повинен бути глобальною змінною.
Дуже погано, якщо ви перебуваєте в багатопотоковому середовищі (наприклад, на 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 #.
Отже
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);
Однак, оскільки лямбда не робить нічого іншого, ніж передача екземпляра прихованому класу (наприклад, класу "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 ::#include <functional>
Однак, оскільки С ++ дозволяє використовувати і С, ви можете зробити це за допомогою libffcall у простому С, якщо ви не проти зв’язати залежність.
Завантажте libffcall з GNU (принаймні на ubuntu, не використовуйте наданий дистрибутив - він зламаний), розпакуйте.
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
main.c:
#include <callback.h>
void function (void* data, va_alist alist)
{
int abc = va_arg_int(alist);
printf("data: %08p\n", data);
printf("abc: %d\n", abc);
}
int main(int argc, char* argv[])
{
int in1 = 10;
void * data = (void*) 20;
void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
incrementer1(123);
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.