Можно ли вызвать std:: function из C?
У меня есть код на С++, который возвращает std::function
. Я хотел бы назвать это из некоторого кода C. Это возможно? В качестве примера у меня есть следующий код:
typedef std::function<int(int)> AdderFunction;
AdderFunction makeAdder(int amount) {
return [amount] (int n) {
return n + amount;
};
}
extern "C" {
AdderFunction makeCAdder(int amount) {
return makeAdder(amount);
}
}
с clang++ -std=c++11 test.cpp
он вызывает следующее предупреждение:
'makeCAdder' has C-linkage specified, but returns user-defined type 'AdderFunction' (aka 'function<int (int)>') which is incompatible with C
Я понимаю, почему это происходит, но интересно, есть ли образец, чтобы сделать это возможным?
Ответы
Ответ 1
Самый переносимый метод для взаимодействия между C/С++ будет заключаться в использовании указателей для передачи данных между языками и использования функций, не являющихся членами, для выполнения вызовов функций.
Файл .h:
#ifdef __cplusplus
extern "C" {
#endif
// Declare the struct.
struct Adder;
// Declare functions to work with the struct.
Adder* makeAdder(int amount);
int invokeAdder(Adder* adder, int n);
void deleteAdder(Adder* adder);
#ifdef __cplusplus
}
#endif
Внедрить их в .cpp файл как:
#include <functional>
typedef std::function<int(int)> AdderFunction;
struct Adder
{
AdderFunction f;
};
AdderFunction makeAdderFunction(int amount) {
return [amount] (int n) {
return n + amount;
};
}
Adder* makeAdder(int amount)
{
Adder* adder = new Adder;
adder->f = makeAdderFunction(amount);
return adder;
}
int invokeAdder(Adder* adder, int n)
{
return adder->f(n);
}
void deleteAdder(Adder* adder)
{
delete adder;
}
Ответ 2
Невозможно вызвать a std::function
из C, потому что C не поддерживает требуемые языковые функции. C не имеет шаблонов, модификаторов доступа, вызываемых объектов, виртуальных методов или чего-либо еще, что std::function
может использовать под капотом. Вам нужно придумать стратегию, которую C может понять.
Одна из таких стратегий заключается в том, чтобы скопировать/переместить ваш std::function
в кучу и вернуть ее как непрозрачный указатель. Затем вы предоставили бы другую функцию через свой С++-интерфейс, который принимает этот непрозрачный указатель и вызывает содержащуюся в нем функцию.
// C side
struct function_opaque;
int call_opaque(struct function_opaque*, int param);
// C++ side
extern "C" {
struct function_opaque {
std::function<int(int)> f;
};
int call_opaque(function_opaque* func, int param) {
return func->f(param);
}
};
Конечно, это связано с последствиями управления памятью.
Ответ 3
Вам нужно поместить typedef внутри блока extern "C"
как минимум (чтобы его компилировать как С++). Однако я не уверен, что это сработает с C. Что будет работать с C, это просто использовать простые указатели функций, например.
extern "C" {
using AdderFunction = int(int);
// old-style: typedef int(*AdderFunction)(int);
}
Изменить: если вы используете API, который предоставляет вам объекты std::function
, вы можете использовать метод std::function::target()
для получения (C-callable) raw указатель функции, на который он ссылается.
using AdderFunction = std::function<int(int)>;
extern "C" {
using CAdderFunction = int(int);
CAdderFunction makeCAdder(int amount)
{
return makeAdder(amount).target<CAdderFunction>();
}
}
Ответ 4
Другим решением является разбиение std::function
на указатель на закрытие и указатель на функцию-член и передать три вещи функции C, которая хочет вызвать lambda:
- Адрес функции С++, которая знает, как вызвать функцию в типе закрытия - безопасно
- Указатель на закрытие (несвеже приведённый к
void *
)
- Указатель функции-члена (скрытый внутри структуры-оболочки и добавленный в
void *
)
Вот пример реализации.
#include <functional>
#include <iostream>
template<typename Closure, typename Result, typename... Args>
struct MemberFunctionPointer
{
Result (Closure::*value)(Args...) const;
};
template<typename Closure, typename Result, typename... Args>
MemberFunctionPointer<Closure, Result, Args...>
member_function_pointer(
Result (Closure::*const value)(Args...) const)
{
return MemberFunctionPointer<Closure, Result, Args...>{value};
}
template<typename Closure, typename Result, typename... Args>
Result
call(
const void *const function,
const void *const closure,
Args... args)
{
return
((reinterpret_cast<const Closure *>(closure))
->*(reinterpret_cast<const MemberFunctionPointer<Closure, Result, Args...>*>(function)->value))
(std::forward<Args>(args)...);
}
Пример использования со стороны C:
int
c_call(
int (*const caller)(const void *, const void *, int),
const void *const function,
const void *const closure,
int argument)
{
return caller (function, closure, argument);
}
Пример использования со стороны С++:
int
main()
{
int captured = 5;
auto unwrapped = [captured] (const int argument) {
return captured + argument;
};
std::function<int(int)> wrapped = unwrapped;
auto function = member_function_pointer(&decltype(unwrapped)::operator());
auto closure = wrapped.target<decltype(unwrapped)>();
auto caller = &call<decltype(unwrapped), int, int>;
std::cout
<< c_call(
caller,
reinterpret_cast<const void *>(&function),
reinterpret_cast<const void *>(closure),
10)
<< '\n';
}
Причиной для структуры wrapper является то, что вы не можете использовать указатель функции-члена для void *
или любого другого типа указателя объекта, даже с reinterpret_cast
, поэтому вместо этого мы передаем адрес указателя функции-члена. Вы можете поместить структуру MemberFunctionPointer
в кучу, например. с unique_ptr
, если ему нужно прожить дольше, чем в этом простом примере.
Вы также можете обернуть эти три аргумента в одной структуре на стороне C, а не передавать их отдельно:
struct IntIntFunction
{
int (*caller)(const void *, const void *, int);
const void *function;
const void *closure;
};
#define INVOKE(f, ...) ((f).caller((f).function, (f).closure, __VA_ARGS__))
int
c_call(IntIntFunction function)
{
return INVOKE(function, 10);
}
Ответ 5
Проблема с этим решением заключается в том, что вы вызываете makeAdder
с значениями параметров.. Не удалось его решить, но я отправляю на случай, если кто-то еще может..
template <typename FunctionPointerType, typename Lambda, typename ReturnType, typename ...Args>
inline FunctionPointerType MakeFunc(Lambda&& lambda, ReturnType (*)(Args...))
{
thread_local std::function<ReturnType(Args...)> func(lambda);
struct Dummy
{
static ReturnType CallLambda(Args... args)
{
return func(std::forward<Args>(args)...);
}
};
return &Dummy::CallLambda;
}
template <typename FunctionPointerType, typename Lambda>
FunctionPointerType MakeFunc(Lambda&& lambda)
{
return MakeFunc<FunctionPointerType, Lambda>(std::forward<Lambda>(lambda), FunctionPointerType());
}
typedef int(*AdderFunction)(int);
AdderFunction makeAdder(int amount) {
return MakeFunc<int(*)(int)>([amount] (int n) {
return n + amount;
});
}
extern "C" {
typedef int(*CAdderFunction)(int);
CAdderFunction makeCAdder(int amount)
{
return makeAdder(amount);
}
}
Он работает, сохраняя лямбда в потоке локальным std::function
. Затем верните указатель на статическую функцию, которая вызовет лямбда с переданными параметрами.
Я думал об использовании unordered_map
и отслеживании каждого вызова makeAdder
, но затем вы не можете ссылаться на него из статического контекста..