跨模块接口与动态库

模块

首先定义下模块的概念,在C++中可以认为每个二进制文件为一个模块。比如一个exe可执行程序、一个dll或so动态库。通常来说一个exe会依赖于几个dll动态库。
我们写一个带界面的聊天程序,exe为入口主程序 是主模块,它可能依赖于QtWidgets QtCore等Qt模块、libevent第三方模块、我们自己写的基础库xxbase模块。

接口/跨模块接口

模块自己内部调用比较简单,因为编译环境和平台都一致,不存在不兼容的问题。如果想把我们的功能提供给其他人使用,就需要导出接口和dll二进制文件了。每种语言有自己的接口定义形式,接口在C或C++里就是一些.h头文件。头文件里定义了结构体、函数等,供其他模块调用。

也可以认为模块由接口和二进制文件组成。windows编译出来的dll肯定不能在linux上调用、32位编译出的dll又不能被64位程序调用、Debug模式和Release模式也存在很多差异。C++不像Java,Java是编译一次在任意操作系统和平台都能跑起来。C++呢?不同操作系统、不同CPU、不同系统位数、甚至不同优化参数,编译出来的二进制文件都不通用。

动态库

动态库就是编译好的、可供其他模块调用的二进制文件。在windows是dll形式,在类Unix是so形式。

动态库相比源码和静态库有以下优势:

  • 若以源码或静态库方式提供给别人用,如果后期有一个bug需要修改,那么所有调用者都需要重新编译、测试、打包发布,成本很高。
  • 以动态库方式提供,使用者只需要替换dll或so即可,简单高效。

动态库的劣势:

  • 动态库版本维护比较麻烦,得思考下如何避免“dll地狱”。

跨模块接口编写规范

  • 跨平台的接口应该使用C++11新统一的数据类型,比如int32_t int64_t等。因为原始类型在不同平台编译器可能位数不同,比如long有的是32位、有的是64位。
  • 不能使用STL标准库里的vector、list等作为函数参数。因为模块提供者和使用者可能使用不同的C++编译器,STL实现方式不一样。
  • 只能传递裸指针作为函数参数,不能使用智能指针。因为不确定调用者是否支持智能指针、智能指针实现方式是否一样。
  • 接口一经发布,不允许做任何修改(.h不能改、只允许改.cpp发布dll更新版本)。因为修改接口后所有使用者都要重新编译,即使用不到新增的功能。

“接口一经发布,不允许做任何修改”这听上去很难,其实是有办法解决的。我们可以参考COM的思想,提供一个QueryInterface函数,用以查询所有接口。调用者先判断这个接口是否存在,存在则调用、不存在则给出错误提示。如果后期需要增加新接口,只需要更改下QueryInterface内部实现,就能扩展新接口。

即使把现在所有能想到的功能都抽象成接口,也免不了后期增加新功能。可以提前预留一些字段、函数,或者设计成方便扩展的形式。

页面下部广告