繁体   English   中英

std::function 的运行时实现

[英]Run-time implementation of std::function

为了安全起见,我已经在我的 DLL 调用中使用了旧式 function 指针,如下所示:

// DLL
typedef int (__stdcall* ty)();
void test(ty t)
{
    if (t)
    {
        int r = t(); 
        ....
    }
}

而我可以使用这个:

void test(std::function<int()> t)
{
}

然而,众所周知,后者使用类型擦除 std::function不能是原始 function 指针(因为它可以传递一个 lambda ,它具有捕获,因此不能是原始指针)。

因此,在 Visual Studio 中,在发布模式下使用 DLL 构建和包含std::function签名的 function 签名会崩溃,反之亦然。 即使它是调试或发布模式,行为也不相同。 有时它会崩溃,有时它会工作。

是否有定义的运行时行为,我们可以依赖它来使用std::function 或者这是编译器专门根据我传递给它的内容而专门编译的东西,因此不能预先假定运行时行为?

在汇编级别,必须知道预编译单元上的 function 签名。 据我所知std::function运行时实现没有很好的定义。

所以,例如,当我编译时,然后

void test(std::function<int()> t)

可以通过类型擦除采用任何参数,如[]() -> int, [&]() -> int, [=]() -> int等。 但是,当这是预编译的,因此只在运行时,可以接受什么? std::function 是如何实现的? 用 class 指针? 有明确定义的方法吗?

我不是在寻找与 VS 相关的解决方案,而是在寻找std::function的标准定义(如果有的话)。

因此,在 Visual Studio 中,在发布模式下使用 DLL 构建和包含std::function签名的 function 签名会崩溃,反之亦然。

这永远不会起作用,因为它会更改 ABI。

即使它是调试或发布模式,行为也不相同。 有时它会崩溃,有时它会工作。

如果您注意编译器标志、依赖 static 与动态库、C 和 C++ 标准库的编译方式、编译器供应商的例外情况和保证是什么等,这可能会起作用。

是否有定义的运行时行为,我们可以依赖它来使用std::function

一般来说,最好的选择(也是为其他语言创建绑定最有用的选择)是避免使用 C++ 接口并使用普通类型的普通 C 接口。

也就是说,如果您想传递 C++ 类型,请将它们作为不透明类型传递,并且只能从一侧操作它们。

我不是在寻找与 VS 相关的解决方案,而是在寻找std::function的标准定义(如果有)。

C++ 标准不强制任何特定的 ABI,也不提供绝大多数类型的数据成员等实现细节。

这就是为什么即使您以完全相同的方式编译所有内容,混合不同的 STL 库也是一个问题。

旧式回调应该是一个 function 指针一个 void 指针。

有了它,您可以发送一个标准 function (但不存储它)。

您可以将这些操作封装在标准布局 class 中,这样可以 99.99% 安全地跨越 DLL 边界。 运行/注入的代码无法提前卸载,但这与 C 样式回调没有什么不同。

为了安全地存储可调用数据,您需要调用状态和销毁状态操作。

以完全 dll 安全的方式做到这一点......

template<class Sig>
struct callback;

template<class R, class...Args>
struct callback<R(Args...)>{
  // standard layout data:
  void* state=0;
  R(*operate)(void*, Args...)=0;
  void(*cleanup)(void*)=0;

  voud clear(){ if(cleanup){cleanup(state);}state=0; operate=0; cleanup=0; }
  callback(callback const&)=delete;
  callback(callback&&o):state(o.state),operate(o.operate),cleanup(o.cleanup){o.cleanup=0;o.clear();}
  callback& operator=(callback&&o){
    if(this==&o) return *this;
    clear();
    state=o.state;
    operate=o.operate;
    cleanup=o.cleanup;
    o.clear();
    return *this;
  }

  ~callback(){ clear(); }
  R operator()(Args...args)const{return operate(state, std::forward<Args>(args)...);}
};

您可以很容易地将 std function 转换为上述内容,反之亦然。

它是标准布局,因此应该安全地穿过 DLL 边界。

但是,如果您非常偏执,您可以将其解压缩并一个一个发送 2 个 function 指针和一个 void 指针。

将有状态的 object 放入上面:

template<class T> void(*deleter)(void*)=+[](void*ptr){delete static_cast<T*>(ptr);};
template<class F, class R, class...Args> R(*caller)(void*,Args...)=
  +[](void* ptr, Args...args)->R{
    return (*(F*)(ptr))(std::forward<Args>(args)...);};

 template<class F, class R, class...Args>
 callback<R(Args...)> make_callback(F&& f){
   callback<R(Args...)> retval;
   retval.state=new std::decay_t<F>(std::forward<F>(f));
   retval.operate=caller<std::decay_t<F>, R, Args...>;
   retval.cleanup=deleter<std::decay_t<F>>;
   return retval;
}

它适用于 lambdas 或 std 函数。

一个原始的 function:

template<class R,class...Args>
callback<R(Args...)> make_callback(R(*pf)(Args...)){
  callback<R(Args...)> retval;
  retval.state=reinterpret_cast<void*>(pf);// note on some obscure platforms this does not work.
  retval.operate=caller<R(Args...), R, Args...>;// ditto
}

无需清理。

callback是一个只移动的精简 std function 实现。 在保持 dll 安全的同时,还可以进行更多改进。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM