死灵术。
我认为迄今为止的答案有点不清楚。
让我们举个例子:
假设您有一个像素数组(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* 输出必须是全局变量。
如果您在多线程环境中(例如 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-instance的地址。
所以当你有一个 writeByte 过程的 typedef 时,它看起来像这样
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++ 中可以做的下一件最好的事情是使用 lambda 函数:
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 的签名。
lambda 相对于手动类的优点是您只需要更改一个 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);
但这在幕后只是创建了一个 lambda 函数,然后还需要更改 typedef。
所以不,没有办法将成员函数传递给需要静态函数指针的方法。
但是 lambda 是一种简单的方法,前提是您可以控制源。
否则,你就不走运了。
你不能用 C++ 做任何事情。
注意:
std::function 需要#include <functional>
但是,由于 C++ 也允许您使用 C ,如果您不介意链接依赖项,则可以使用纯 C 中的libffcall来执行此操作。
从 GNU 下载 libffcall(至少在 ubuntu 上,不要使用发行版提供的包 - 它已损坏),解压缩。
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
主.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 头文件和库,或者使用 libffcall 的内容创建一个 cmake 项目。