【问题标题】:Can I call a std::function from C?我可以从 C 调用 std::function 吗?
【发布时间】:2017-11-01 20:57:14
【问题描述】:

我有一些 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

我明白为什么会发生这种情况,但想知道是否有一种模式可以使它成为可能?

【问题讨论】:

  • 为什么需要这个?它可能是XY Problem 的一个实例。
  • 有几个选项。您可以返回void* 并在堆上分配std::function,还可以提供回调到C++、强制转换和对您想要的函数执行任何操作的函数。究竟如何将std::function 转换为与 C 兼容的数据类型取决于您;您甚至可以将其放入原始字符数组(也许包装在结构中)。有很多选择
  • 您可以通过返回原始函数指针(例如int (*AdderFunction)(int)),但不能通过std::function。这基本上就是 std::function::target 所做的
  • @IharobAlAsimi 上下文是我正在尝试使用 Emscripten 为 Javascript 手动定义 C++ 代码的接口。 Embind 可以,但是会创建很多多余的代码并使用eval 来实现。

标签: c++ c c++11


【解决方案1】:

在 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;
}

【讨论】:

  • 这会让你真的打电话 f吗?
  • 感谢您的回答,不幸的是,这会导致类似/相同的警告:'makeCAdder' has C-linkage specified, but returns user-defined type 'AdderWrapper' which is incompatible with C
  • @RSahu 是的,这就是 OP 所说的他真正想做的事情。我认为做到这一点的唯一方法是一个 extern C 包装函数,它实际上进行了调用,但我对自己编写它的能力没有足够的信心来回答这个问题。
  • 很好,扩展的make/invoke/delete 模式有效。谢谢!
  • 如果您打算使用the standard macro__cplusplus 应为小写。
【解决方案2】:

无法从 C 调用 std::function,因为 C 不支持所需的语言功能。 C 没有模板、访问修饰符、可调用对象、虚拟方法或 std::function 可以在后台使用的任何其他内容。你需要想出一个 C 能理解的策略。

一种这样的策略是将您的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);
    }
};

当然,这涉及内存管理。

【讨论】:

    【解决方案3】:

    您至少需要将 typedef 放入 extern "C" 块中(以使其编译为 C++)。但是,我不确定这是否适用于 C。从 C 中起作用的只是使用普通函数指针,例如

    extern "C" {
    using AdderFunction = int(int);
    // old-style: typedef int(*AdderFunction)(int);
    }
    

    编辑:如果您使用的 API 为您提供 std::function 对象,则可以使用 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>();
    }
    }
    

    【讨论】:

    • 是的,我试过了,不幸的是target 返回一个空指针。代码如下所示:CAdderFunction* makeCAdder(int amount) { return makeAdder(amount).target&lt;CAdderFunction&gt;(); }
    • 我相信以上是因为target_type不同,这里提到en.cppreference.com/w/cpp/utility/functional/function/…
    • @StuartK 试试makeAdder的返回类型std::function&lt;CAdderFunction&gt;?
    • 另一种可能性是 return makeAdder(amount).target&lt;int(int)&gt;(); ,我相信这应该隐式转换为 CADderFunction。
    【解决方案4】:

    另一种解决方案是将std::function拆分成一个指向闭包的指针和一个指向成员函数的指针,并将三个东西传递给想要调用lambda的C函数:

    • 知道如何安全地调用闭包上的函数的 C++ 函数的地址
    • 闭包指针(不安全地转换为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,如果它需要比这个简单示例中的寿命更长。

    您还可以将这三个参数包装在 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);
          }
      }
      

      它通过将 lambda 存储在线程本地 std::function 来工作。然后返回一个指向静态函数的指针,该函数将使用传入的参数调用 lambda。

      我考虑过使用unordered_map 并跟踪每个makeAdder 调用,但是您无法从静态上下文中引用它..

      【讨论】:

      • 这是一个无法解决的问题(除非使用运行时代码生成)。如果参数值在编译时已知,则有许多解决方法,例如使用线程局部变量或模板。但是,除了回调函数之外,许多 C API 还允许传递任意 void *data。在简单的情况下,您可以只使用reinterpret_cast&lt;void *&gt;(myInt),或者指向一个数据结构。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多