std::function
を返すC++コードがあります。これを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
なぜこれが起こっているのか理解していますが、それを可能にするパターンがあるかどうか疑問に思いますか?
C/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;
}
Cは必要な言語機能をサポートしていないため、Cからstd::function
を呼び出すことはできません。 Cには、テンプレート、アクセス修飾子、呼び出し可能なオブジェクト、仮想メソッドなど、std::function
が内部で使用できるものはありません。 Cが理解できる戦略を考え出す必要があります。
そのような戦略の1つは、std::function
をヒープにコピー/移動し、不透明なポインターとして返すことです。次に、C++インターフェースを介して、不透明なポインターを受け取り、そこに含まれる関数を呼び出す別の関数を提供します。
// 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);
}
};
もちろん、これにはメモリ管理の影響が伴います。
Typedefは、最低でも_extern "C"
_ブロック内に配置する必要があります(C++としてコンパイルするには)。ただし、Cで動作するかどうかはわかりません。 Cで機能するのは、単純な関数ポインタを使用することだけです。
_extern "C" {
using AdderFunction = int(int);
// old-style: typedef int(*AdderFunction)(int);
}
_
編集:_std::function
_オブジェクトを提供するAPIを使用している場合は、 std::function::target()
メソッドを使用して、(C呼び出し可能な)生の関数ポインターを取得できます。を参照してください。
_using AdderFunction = std::function<int(int)>;
extern "C" {
using CAdderFunction = int(int);
CAdderFunction makeCAdder(int amount)
{
return makeAdder(amount).target<CAdderFunction>();
}
}
_
別の解決策は、std::function
をクロージャーへのポインターとメンバー関数へのポインターに分割し、ラムダを呼び出したいC関数に次の3つを渡すことです。
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);
}
C++側からの使用例:
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';
}
ラッパー構造体の理由は、void *
を使用したとしても、メンバー関数ポインターをreinterpret_cast
または他のオブジェクトポインター型にキャストできないためです。代わりにメンバー関数ポインターのアドレス。 MemberFunctionPointer
構造体をヒープに配置することを選択できます。 unique_ptr
を使用します。この単純な例よりも長くする必要がある場合。
これらの3つの引数を個別に渡すのではなく、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);
}
この解決策の問題は、パラメーター値を指定して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
呼び出しを追跡することを考えましたが、静的コンテキストから参照することはできません。