FFI(Foreign Function Interface)允许以一种语言编写的代码调用另一种语言的代码,而libffi库提供了最底层的、与架构相关的、完整的FFI。libffi的作用就相当于编译器,它为多种调用规则提供了一系列高级语言编程接口,然后通过相应接口完成函数调用,底层会根据对应的规则,完成数据准备,生成相应的汇编指令代码。
动态调用C函数,使用libffi提供接口动态调用流程如下:
1. 准备好参数数据及其对应ffi_type数组、返回值内存指针、函数指针
2. 创建与函数特征相匹配的函数原型:ffi_cif
对象
3. 使用“ffi_call”来完成函数调用
补全https://blog.csdn.net/JaimeCool/article/details/76332930中的源码,编译gcc rtl.cpp -fpermissive -w -lffi
结论:
1、指针functionPtr仅仅定义了一个普通数据类型的指针,并没有保证和函数testFunc拥有相同的函数指针类型,这样就可以通用的执行多种类似的函数
可见使用ffi,只要有函数原型cif对象,函数实现指针,返回值内存指针和函数参数数组,我们就可以实现在运行时动态调用任意C函数
#include <stdio.h>
#include <ffi.h>
#include <stdlib.h>
int testFunc(int m, int n) {
printf("params: %d %d \n", m, n);
return m+n;
}
void testCall (void) {
testFunc(1, 2);
//拿函数指针
// int (*funcPointer)(int, int) = &testFunc;
void* functionPtr = &testFunc;
int argCount = 2;
//参数类型数组
ffi_type **ffiArgTypes = alloca(sizeof(ffi_type *) *argCount);
ffiArgTypes[0] = &ffi_type_sint;
ffiArgTypes[1] = &ffi_type_sint;
//参数数据数组
void **ffiArgs = alloca(sizeof(void *) *argCount);
void *ffiArgPtr = alloca(ffiArgTypes[0]->size);
int *argPtr = ffiArgPtr;
*argPtr = 5;
ffiArgs[0] = ffiArgPtr;
void *ffiArgPtr2 = alloca(ffiArgTypes[1]->size);
int *argPtr2 = ffiArgPtr2;
*argPtr2 = 3;
ffiArgs[1] = ffiArgPtr2;
//生成函数原型 ffi_cfi 对象
ffi_cif cif;
ffi_type *returnFfiType = &ffi_type_sint;
ffi_status ffiPrepStatus = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, (unsigned int)argCount, returnFfiType, ffiArgTypes);
if (ffiPrepStatus == FFI_OK) {
//生成用于保存返回值的内存
void *returnPtr = NULL;
if (returnFfiType->size) {
returnPtr = alloca(returnFfiType->size);
}
//根据cif函数原型,函数指针,返回值内存指针,函数参数数据调用这个函数
ffi_call(&cif, functionPtr, returnPtr, ffiArgs);
//拿到返回值
int returnValue = *(int *)returnPtr;
printf("ret: %d \n", returnValue);
}
}
int main ()
{
testCall();
return 0;
}
进阶版本,参考llvm中omp的__tgt_rtl_run_target_region处理方法,限制条件设备函数无返回返回值,处理结果通过参数返回
#include <stdio.h>
#include <ffi.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <cassert>
#include <vector>
#define OFFLOAD_SUCCESS (0)
#define OFFLOAD_FAIL (~0)
#define OFFLOAD_DEVICE_DEFAULT -1
#define DPxMOD "0x%0*" PRIxPTR
#define DPxPTR(ptr) ((int)(2 * sizeof(uintptr_t))), ((uintptr_t)(ptr))
// #define DPxPTR(ptr) ptr
#define PRIxPTR "l" "x"
#define DEBUG_PREFIX "TARGET aarch64 RTL"
#define DEBUGP(prefix, ...) \
{ \
fprintf(stderr, "%s --> ", prefix); \
fprintf(stderr, __VA_ARGS__); \
}
/// Emit a message for debugging
#define DP(...) \
do { \
DEBUGP(DEBUG_PREFIX, __VA_ARGS__); \
} while (false)
#ifdef __cplusplus
extern "C" {
#endif
int32_t __tgt_rtl_run_target_region(int32_t device_id, void *tgt_entry_ptr,
void **tgt_args, ptrdiff_t *tgt_offsets,
int32_t arg_num);
int32_t __tgt_rtl_run_target_team_region(int32_t device_id, void *tgt_entry_ptr,
void **tgt_args,
ptrdiff_t *tgt_offsets,
int32_t arg_num, int32_t team_num,
int32_t thread_limit,
uint64_t loop_tripcount /*not used*/) {
// ignore team num and thread limit.
// Use libffi to launch execution.
ffi_cif cif;
// All args are references.
std::vector<ffi_type *> args_types(arg_num, &ffi_type_pointer);
std::vector<void *> args(arg_num);
std::vector<void *> ptrs(arg_num);
// for (int i=0; i<arg_num; i++) printf("params[%d]: %d + %d\n", i, *(int**)tgt_args[i], tgt_offsets[i]);
for (int32_t i = 0; i < arg_num; ++i) {
ptrs[i] = (void *)((intptr_t)tgt_args[i] + tgt_offsets[i]);
// printf("params[%d]: %d\n", i, *(int*)ptrs[i]);
args[i] = &ptrs[i];
// printf("params[%d]: %d\n", i, *(int*)args[i]);
}
// 函数输入参数个数及数据类型由arg_num及args_types指定,函数返回值类型为void
ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_num,
&ffi_type_void, &args_types[0]);
assert(status == FFI_OK && "Unable to prepare target launch!");
if (status != FFI_OK)
return OFFLOAD_FAIL;
DP("Running entry point at " DPxMOD "...\n", DPxPTR(tgt_entry_ptr));
void (*entry)(void);
*((void**) &entry) = tgt_entry_ptr;
// Set NULL for return value Pointer, so return value must from arguement ?
ffi_call(&cif, entry, NULL, &args[0]);
return OFFLOAD_SUCCESS;
}
// return RTL->run_region(RTLDeviceID, TgtEntryPtr, TgtVarsPtr, TgtOffsets
int32_t __tgt_rtl_run_target_region(int32_t device_id, void *tgt_entry_ptr,
void **tgt_args, ptrdiff_t *tgt_offsets,
int32_t arg_num) {
// for (int i=0; i<arg_num; i++) printf("params[%d]: %d \n", i, *(int**)tgt_args[i]);
// use one team and one thread.
return __tgt_rtl_run_target_team_region(device_id, tgt_entry_ptr, tgt_args,
tgt_offsets, arg_num, 1, 1, 0);
}
// omp 函数的传参类似引用,非值传递
void testFunc(int &m, int &n) {
printf("params: %d %d \n", m, n);
m += n;
return;
}
int main (void) {
// 获取函数指针
void* functionPtr = &testFunc;
int argCount = 2;
int i;
// 参数类型数组, 两个参数的类型为sint
ffi_type **ffiArgTypes = alloca(sizeof(ffi_type *) *argCount);
ffiArgTypes[0] = &ffi_type_sint;
ffiArgTypes[1] = &ffi_type_sint;
//参数数据数组
void **ffiArgs = alloca(sizeof(void *) *argCount);
ptrdiff_t *tgt_offsets = alloca(sizeof(ptrdiff_t) *argCount);
void *ffiArgPtr = alloca(ffiArgTypes[0]->size);
int *argPtr = ffiArgPtr;
*argPtr = 5;
ffiArgs[0] = ffiArgPtr;
void *ffiArgPtr2 = alloca(ffiArgTypes[1]->size);
int *argPtr2 = ffiArgPtr2;
*argPtr2 = 3;
ffiArgs[1] = ffiArgPtr2;
for (i=0; i<argCount; i++)
tgt_offsets[i] = 0; // 参考processDataBefore,只有OMP_TGT_MAPTYPE_PRIVATE需要设置
__tgt_rtl_run_target_region(-1, functionPtr, ffiArgs, tgt_offsets, argCount);
printf("return value from param: %d\n", *(int*)ffiArgs[0]);
return 0;
}
#ifdef __cplusplus
}
#endif