如何在C语言中实现接收任意函数指针与可变参数的通用caller函数
C语言实现通用函数转发的解决方案
纯C语言没有C那种编译期类型推导、可变参数完美转发的语法能力,无法实现和C模板完全等价的类型安全、无额外依赖的通用caller_imp函数,可根据场景选择以下两种近似方案:
方案1:宏直接展开(推荐,无依赖、类型安全、零开销)
如果不需要在函数调用前后添加统一的封装逻辑(比如日志打印、耗时统计、错误处理等),完全可以去掉caller_imp层,直接用宏完成调用,原有代码几乎不用修改:
// 直接用宏完成转发,无需额外实现函数 #define caller(function, ...) function(__VA_ARGS__) // 原有测试代码完全兼容 int test (int a) { return a; } int test2(int a, int b) { return a+b; } int test3(int a,char *str) { return printf("%d: %s\n", a, str); } int main(int argc, char **argv) { caller(test,33); caller(test2,44,55); caller(test3, 10, "hello world"); return 0; }
- 优点:完全符合示例的调用方式,类型由编译器自动检查,没有任何运行时开销,兼容所有C标准
- 缺点:如果要加统一的调用前后逻辑,只能写在宏里,会在每个调用点展开多份代码,无法封装到独立函数中。
方案2:依赖libffi实现运行时动态调用
如果你必须把通用逻辑封装到独立的caller_imp函数中,可使用跨平台的libffi库,它提供了运行时构造任意函数调用的能力,你需要手动传入函数的返回值类型、参数个数、每个参数的类型信息(C没有运行时类型信息,必须手动提供):
#include <stdio.h> #include <ffi.h> #include <stdarg.h> #include <stdlib.h> #define MAX_ARG_CNT 16 void caller_imp(ffi_type *ret_type, void (*func)(), int arg_cnt, ...) { ffi_cif cif; ffi_type *arg_types[MAX_ARG_CNT]; void *arg_values[MAX_ARG_CNT]; va_list ap; va_start(ap, arg_cnt); for (int i = 0; i < arg_cnt; i++) { arg_types[i] = va_arg(ap, ffi_type*); arg_values[i] = malloc(arg_types[i]->size); // 按需补充更多类型的处理逻辑 if (arg_types[i] == &ffi_type_sint) { *(int*)arg_values[i] = va_arg(ap, int); } else if (arg_types[i] == &ffi_type_pointer) { *(void**)arg_values[i] = va_arg(ap, void*); } } va_end(ap); // 构造调用上下文后执行函数 if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_cnt, ret_type, arg_types) == FFI_OK) { int ret; // 非int返回值可改为通用内存存储 ffi_call(&cif, func, &ret, arg_values); // 此处可添加统一的后置处理逻辑 printf("函数调用完成,返回值:%d\n", ret); } for (int i = 0; i < arg_cnt; i++) { free(arg_values[i]); } } // 宏定义简化调用 #define caller(func, ret_type, arg_cnt, ...) caller_imp(ret_type, func, arg_cnt, __VA_ARGS__) // 测试函数定义同上文 int test (int a) {return a;} int test2(int a, int b) {return a+b;} int main(int argc, char **argv) { // 调用时需手动指定返回值、参数个数、每个参数的类型和值 caller(test, &ffi_type_sint, 1, &ffi_type_sint, 33); caller(test2, &ffi_type_sint, 2, &ffi_type_sint, 44, &ffi_type_sint, 55); return 0; }
- 优点:可以把通用逻辑完全封装到
caller_imp函数中,支持任意签名的函数调用 - 缺点:需要依赖第三方libffi库,调用时需要手动传入类型描述,写起来繁琐,且有一定运行时开销。
补充说明
如果既不能用第三方库,又必须把逻辑封装到函数中,标准C没有可移植的实现方案,只能针对特定平台的调用约定手写汇编实现,这种方式兼容性极差,不推荐。
内容的提问来源于stack exchange,提问作者Honneamise
相关产品推荐
相关产品推荐

