为什么不建议库导出c++接口

14 篇文章 0 订阅

ABI

回答这个问题之前,我们需要了解ABI(应用二进制接口),

ABI(Application Binary Interface):应用程序二进制接口,描述了应用程序和操作系统之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。ABI涵盖了各种细节,如:

  • 数据类型的大小、布局和对齐;
  • 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
  • 系统调用的编码和一个应用如何向操作系统进行系统调用;
  • 在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。

API界定源码的调用方式,ABI界定二进制目标代码的调用方式。

ABI更多是(编译器、目标机器架构、操作系统)共同决定的;

C++ 因为引入了name mangling之类的新特性,即使是在相同代码、相同机器、相同架构、相同系统,不同编译器,目前事实上并没有形成统一的ABI接口规范,虽然部分编译器(CLANG、GCC)相同语言版本下据说存在二进制兼容,但这种并语言并没有强规范要求,实际使用中,你不该认定两种编译器之间存在兼容,本质上也是一定未定义行为。

C 程序则因为历史地位,形成了一个事实上的同一操作系统内的统一ABI。

ABI兼容与API兼容

API接口兼容,大家都理解,就是接口及函数参数不变嘛,ABI兼容其实一致,只是区别是二进制接口兼容;

当然我们说的ABI兼容更多是在相同系统相同架构下的兼容.

因为C++的ABI 并未形成事实上的统一ABI,所以在ABI兼容上存在天然弱势。只允许在相同编译器才可以稳定做到ABI兼容。但对于一些进行二进制库文件分发而不是源码共享的工作时,不同编译器之间不同的ABI很有可能导致一些链接、运行错误。

而C因为稳定的ABI,则做到了版本升级只要不改变内存布局导致ABI改变,可以做到相同系统、架构下的库通用。

相同系统下的ABI改变,除开堆栈方向、传参形式等代码无法控制的东西,更多还是我们对象内存布局的变更;
举个简单例子:

一个库定义了struct ,里面有三个int成员,调用该库的调用者使用malloc 一个struct ,分配了(3*sizeof(int))内存,后续库进行了迭代,struct 新增了一个int成员,这种情况下,struct的内存布局做了变更,使用者直接替换库不进行编译,会导致运行内存错位等问题,必须重新编译链接该库。

C的ABI兼容相对简单(相同编译器相同系统),只要注意不要修改对外导出的内存布局即可;

C++ ABI兼容(相同编译器相同系统相同架构),则相比C,虽然管控的还是内存布局,但因为多了类虚函数表、模板之类的东西,会更难以控制,甚至在使用STL时,不同版本的STL的二进制接口也并不稳定,导致相同编译器不同选项仍然会有因为ABI不兼容而运行甚至链接错误(这也是为什么网络上会有禁止使用STL的论调,简单粗暴解决问题);

对于C++保持ABI兼容的可以做与不可以做

当然因为以上繁杂的规则,C++形成了一套约定俗称的编程技巧PIMPL(private implementation),即将类的内部私有成员使用,指向前置声明的私有结构体的指针,保证类内存布局的稳定,内存对外只有一个sizeof(ptr),类私有成员的增加并不会破坏ABI接口。

另外QT的QD指针就是一种PIMPL

// hpp
class Net
{
  public:
    Net();
    ~Net();
    Net(const Net &rhs);
    Net(Net &&rhs);
    Net &operator=(const Net &rhs);
    Net &operator=(Net &&rhs);
  private:
    struct Impl;
    std::unique_ptr<Impl> mPimpl;
}
// cpp
struct Net::Impl
{
    void *nn_context_;
    std::vector<size_t> input_size_;
};
Net::Net() : mPimpl(std::make_unique<Impl>())
{
}
Net::~Net() = default;
Net::Net(const Net &rhs) : mPimpl(std::make_unique<Impl>(*rhs.mPimpl))
{
}
Net::Net(Net &&rhs) = default;
Net &Net::operator=(const Net &rhs)
{
    *mPimpl = *rhs.mPimpl;
    return *this;
}

API兼容 和ABI兼容 换个更简单的说法

  • API兼容就是源码兼容,更新API兼容的库版本时,不需要修改你的代码即可做到更新
  • ABI兼容则是库文件,更新库版本时,不需要重新编译,只需替换动态加载的库即可做到更新

结论

综上,因为C++接口ABI兼容的问题,实际上很多闭源库都是使用C++开发,但是只导出C接口,稳定的ABI可规避一些二进制不兼容导致的问题。当然可以使用HEADER ONLY形势对C接口再次封装成C++接口(可以提供RAII),HEADER ONLY编译在用户端,保证了库C++接口与用户之间的ABI统一。

当然如果你能确保库的使用者与你使用相同的编译器相同系统架构,可以直接导出C++接口,因为这种情况下,ABI是统一的,只要用PIMPL之类的技巧保证库的ABI兼容就好了。

如果是源码分发形式,则不用管那么多,保证代码的跨平台性即可。
但是如果是二进制分发,库尽量导出C接口,再辅助以C++ header only接口形式。

C++不是跨平台的吗

可能一些初学者会有这种疑问,其实上C和C++的跨平台指源码跨平台,源码在不同平台使用对应编译器编译出来的程序,效果一致。并不是编译出来的库在任意平台都可使用。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C动态导出类的具体步骤如下所示: 1. 首先需要定义一个虚类(InterfaceClass),该类包含需要导出的函数,并且需要将该类定义前面增加API,即__declspec(dllexport)。该类的定义中推荐导出C的基础数据类型,而不是标准或标准模板的数据类型,以避免不同版本的可能引发的问题。在该虚类中,使用纯虚函数的原因是为了实现接口的多态性。 2. 接着,定义实际继承自虚类的类(readImg),即我们原本需要导出的类。该类需要实现虚类中的纯虚函数。 3. 在动态的源文件中,通过使用C语言的方式导出动态,并提供一个函数(getInstance),该函数用于获取对应类的对象。在该函数中,通过new关键字创建实际继承类的对象,并返回指向该对象的指针。 4. 最后,在项目属性中,将配置类型设置为dll,以将项目编译为动态。 综上所述,以上是C动态导出类的基本步骤。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【cmake实战十】c++从动态(dll)导出类](https://blog.csdn.net/junxuezheng/article/details/126908851)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [c++导出动态](https://blog.csdn.net/weixin_42295969/article/details/126983694)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值