本文内容是对 Essntial COM 一书中第一章内容的总结,该章内容很好的阐述了c++类的二进制复用所需要面对的一些问题及解决方案。
我在使用c++开发模块中,使用了以下几种分发方案:
1)源码分发;
2)使用extern "C"导出函数,所有与类相关的细节均在实现细节中;
3)不考虑升级及编译环境差异,直接导出类,库与调用者同步更新;
当然也面临过一些似乎无法解决的问题,如对MFC的UI模块进行封装,不同编译器之间的协调等问题。当以上问题无法解决时,便会使用源码方式进行协同工作。
本章内容很好的解释了我之前所面对的问题,确实值得记录。
1 源码分发
当c++类库使用了c++编程语言中被广泛支持的子集,则可以通过源码分发来实现复用。
书中给出了一个简单示例代码,快速查找字符串子串,后续所有阐述均是基于该类的适当修改,代码如下:
////////////////////////////////////////////////// // FastString.h class FastString { char* m_psz; public: FastString(const char* psz); ~FastString(); int Length() const; int Find(const char* psz) const; }; ////////////////////////////////////////////////// // FastString.cpp #include "FastString.h" #include <string.h> FastString::FastString(const char* psz) :m_psz(new char[strlen(psz) + 1]){strcpy(m_psz, psz);} FastString::~FastString(){delete[]m_psz;} int FastString::Length() const{return strlen(m_psz);} int FastString::Find(const char* psz) const { // 这里阐述了c++类的复用问题,所以该函数的具体实现并不重要 return 0; }
源码分发方式存在两个缺陷:
1)源码被编译到客户程序中,因此存在多个副本,当库函数较大且被多处使用时,会占用太多空间。
2)当发现现有库存在bug时,需要客户程序重新编译链接新源码才能解决问题。
2 动态链接库分发
使用动态链接库分发可以解决源码分发所面对的问题,具体如下:
使用编译指示符 __declspec(dllexport) 将类 FastString 中所有方法引出,多个客户程序通过链接库 FastString.lib 链接动态库,库实现则生成在 FastString.dll 中。
库文件在系统物理内存中只有一个副本,这样就解决了空间冗余问题。同时当库存在bug时,只有接口文件没有改变,客户程序不需要重新链接库文件。
修改后头文件代码如下:
////////////////////////////////////////////////// // FastString.h class __declspec(dllexport) FastString { char* m_psz; public: FastString(const char* psz); ~FastString(); int Length() const; int Find(const char* psz) const; };
程序调用结构如下:
动态链接库分发同样存在一些问题:
1)当库与客户程序使用相同的编译链接环境,动态链接库可以很好的工作,但当使用不同的编译环境时,则可能无法正确连接到成员函数。
这就是动态库的可移植性问题,主要原因是c++为了支持重载会对成员函数进行重命名,但重命名在不同编译器间却不是一个统一的规则。
extern "C" 可以强制函数不被重命名,但该声明对类的成员函数无效。
2)当为了支持某种更新而不得不修改接口头文件时,即使保持函数接口不变,而是仅仅增加成员变量都需要客户程序重新链接库文件,这是c++类的一个封装问题(仅源码级别)。
如现在Length()函数总是计算字符串的长度,由于字符串为常量,一种改进是在构造函数中计算一次字符串长度,通过增加一个变量来保存该长度,之后的调用直接返回长度即可。
////////////////////////////////////////////////// // FastString.h class __declspec(dllexport) FastString { const int m_cch; char* m_psz; public: FastString(const char* psz); ~FastString(); int Length() const; int Find(const char* psz) const; }; /////////////////////////////////////////////// // FastString.cpp FastString::FastString(const char* psz) :m_cch(strlen(psz)), m_psz(new char[m_cch + 1]){strcpy(m_psz, psz);}
当客户程序不重新编译链接而只是更新了库文件,客户程序在构造类时使用的内存布局为4字节,而新库的内存布局为8字节,这样新库在操作内存时必然出现内存越界问题。
3 将接口从实现中分离
针对动态链接库存在的对类的封装问题,可以采用迂回方式解决,即导出一个接口类,该接口类只包含一些公用的方法函数接口,具体实现则被放在了实现类中。
这样当存在升级库需求时,只有接口没有发生改变,则不需要客户程序重新编译链接。
然而,以上改进仅解决了封装问题,对于不同编译器间的可移植性问题,可以采用一个模块定义文件(Module Definition File),库文件对每个不同编译器生成一个对应的命名文件,
客户程序根据自身的编译环境选择对应的DEF文件即可解决可移植性问题。
将原有实现类保持不变,通过添加一个接口类可以实现分离,代码如下:
/////////////////////////////////////////////// // FastStringItf.h class FastString; // 实现类的声明 class __declspec(dllexport) FastStringItf { FastString* m_pThis; public: FastStringItf(const char* psz); ~FastStringItf(); int Length() const; int Find(const char* psz) const; }; /////////////////////////////////////////////// // FastStringItf.cpp #include "FastStringItf.h" #include "FastString.h" FastStringItf::FastStringItf(const char* psz): m_pThis(new FastString(psz)) {} FastStringItf::~FastStringItf(){delete m_pThis;} int FastStringItf::Length() const{return m_pThis->Length();} int FastStringItf::Find(const char* psz) const{return m_pThis->Find(psz);}
以上代码实现了接口分离,所有的细节均封装到实现类FastString中,当FastString成员变量增加时,始终会得到正确的构造。
接口类的引进间接的在客户程序与实现类之间建立了一道二进制防火墙,当设计好的协议没有改变时,库的升级不要求客户程序重新编译链接。
。。。。。。
。。。。。。
标签:char,const,二进制,复用,c++,FastString,psz,int,FastStringItf From: https://www.cnblogs.com/luofeiju/p/16919771.html