![](/img/trans.png)
[英]create std::tuple using compile-time types and a run-time 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.