包装函数指针

7

我正在尝试为移植目的封装一个库。该库公开了一个名为say的函数 -

fooLib(int , char , function pointer A);

函数指针A的签名为:

void handler(DataFormat);

其中DataFormat是一个结构体。

我不希望我的封装暴露这个库的回调函数。我想要创建一个不同的函数,供封装的使用者使用,比如说

int handlerNew(NewDataFormat);

这里,NewDataFormat 是我的结构体。

现在的问题是如何链接这两个函数?每当库调用 handler,我希望它在从 DataFormat 填充 NewDataFormat 结构之后调用我的回调函数 handlerNew


1
有时,回调函数注册除了函数指针外,还会接受一个 void*,并将该 void* 传递给回调函数。这样可以将自定义数据“附加”到函数指针上。如果没有此功能,这将非常困难。 - John Kugelman
这是正确的,但是这个库不接受任何void*指针。 - Saaras
所有的细节还不是很清晰。fooLib 库怎么用?库什么时候调用 handler 函数?客户端代码如何使用你的包装器 - 你只说了包装器有一个 handlerNew 函数,但暗示这个函数是由库而不是客户端代码调用的。那么客户端代码调用什么 - 原始库还是你的包装器? - kaylum
下面@Gene的优秀答案就是你要找的。你的新处理程序原型返回int没有意义,因为封装库不会使用它,因为它期望回调不返回任何内容。还要记住,如果你正在封装这个库以便从不同的语言中调用它,请记住你提供的处理程序必须具有C调用约定。如果该语言是C ++(extern“C”{void realHandler(NewDataFOrmat);}),那么问题不大,但在其他语言中可能会更加棘手。 - bazza
我同意没有必要返回一个整数。我只会在C中使用它。 - Saaras
1个回答

5
只要不需要线程安全,这并不难。您只需提供一个私有(静态)处理程序,用于将库数据结构转换为您的包装版本,然后将其作为参数调用您的回调函数。您的接口应如下所示:
// wrapped_foo_lib.h
typedef struct { ... } NewDataFormat;
typedef void (*WRAPPED_CALLBACK)(NewDataFormat);
void wrappedFooLibCall(int x, char c, WRAPPED_CALLBACK cb);

你的实现,客户端永远无法看到:

// wrapped_foo_lib.c
// This static var makes this module _not_ thread safe.
static WRAPPED_CALLBACK wrapped_callback;

static void private_handler(DataFormat data) {
  NewDataFormat new_data = ...; // extract new_data from data
  wrapped_callback(new_data);
}

void wrappedFooLibCall(int x, char c, WRAPPED_CALLBACK cb) {
  wrapped_callback = cb;      
  foo_lib(x, c, private_handler);
}

非线程安全性是为什么每个API回调都应该包括一个由您定义且传递给回调的void *的原因。也就是说,你提供的库应该定义为:
fooLib(int, char, void (*)(DataFormat, void *env));
void handler(DataFormat, void *env);

现在当您调用fooLib时,您可以提供任何结构作为env,它将传递回给您。这样您就可以省去包装器中的静态变量:
// wrapped_foo_lib.c
typedef struct { WRAPPED_CALLBACK wrapped_callback; } ENV;

static void private_handler(DataFormat data, void *void_env) {
  ENV *env = (ENV*)void_env;
  NewDataFormat new_data = ...; // extract new_data from data
  env->wrapped_callback(new_data);
}

void wrappedFooLibCall(int x, char c, WRAPPED_CALLBACK cb) {
  ENV env[1] = {{ cb }};      
  foo_lib(x, c, env);
}

这是线程安全的,因为ENV是堆栈分配的。好的例子就是libpng。
可以随意将C90更新为更现代的语法。

1
在GCC中,至少可以通过将 static WRAPPED_CALLBACK wrapped_callback; 更改为 __thread WRAPPED_CALLBACK wrapped_callback; 来解决线程安全问题。然后,wrapped_callback变量是线程全局变量,因此可以安全地针对每个线程进行设置。 - bazza
这个会起作用,唯一的问题是如果我的客户想要注册多个回调函数,那么只有最后一个注册的回调函数会被静态变量记住。有没有什么解决方法? - Saaras
@Saaras,wrappedFooLibCall可以接受一个回调函数指针的列表或数组,而不是单个指针。静态变量可以是该列表的副本,私有处理程序只需迭代列表并依次调用每个处理程序。 - bazza
@bazza,请您详细说明迭代部分。我已经卡在这里两天了。private_handler 如何知道要调用哪个 wrapped_callback - Saaras
@Saaras,只需创建并传递一个存储回调列表的东西,例如函数指针数组。 private_handler 将能够在 for 循环内调用每个回调函数。如果您想要更加选择性,请再创建一个标记数组来标记哪些回调应该被调用,例如 if (flag[i]) callback[i](NewDataFormat); - bazza
@Saaras 我不确定你所说的“register”是什么意思。你没有提到回调注册表。用户可以随心所欲地使用多个不同的回调来调用入口点wrappedFooLibCall(int x, char c, WRAPPED_CALLBACK cb)。静态变量仅在调用进行中用于捕获回调指针,以便私有处理程序可以看到它。就像 bazza 所说的,您可以使用线程本地存储来解决线程安全性问题,但这就像用棒球棒来搔头。 - Gene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接