首页 > 编程语言 >c++类的二进制组件复用

c++类的二进制组件复用

时间:2022-11-23 23:35:29浏览次数:34  
标签:char const 二进制 复用 c++ FastString psz int FastStringItf

本文内容是对 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

相关文章

  • windows--cmake与c++的使用教程(14)
    1概述本文基于前文环境本节目标:target_include_directories用法2作用target_include_directories的作用,用于给固定目标指定头文件搜索路径。moderncmake之......
  • C++零基础入门学习路线图
    C++入门学习路线图分为三阶段:C++基础入门、C++核心编程、C++提高编程。以下学习路线图参考B站黑马程序员《匠心精作C++从0到1入门编程》C++基础入门 1C++初识 ......
  • C++全栈开发学习路线图
    C语言基础与提高 C语言基础 指针、内存管理 变量、条件、字符串、数组、函数、结构体 C语言提高 多级指针的使用 接口的封......
  • 数据结构(二):括号匹配(C++,栈)
    好家伙,写题,题目代码在最后 来吧,  1.栈 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一......
  • C++ 嵌入式实时操作系统调试心得
    1、如果设置了全局vector变量,然后在程序中一直pushback,如果是系统内存较小,运行一段时间后可能会崩溃;2、如果使用C语言编程采用动态内存,一定要在变量生存周期结束时对内存......
  • C++ --- 标准库std::max/std::min和window头文件中宏max/min冲突
    转载:https://blog.twofei.com/668/在包含了Windows.h的C++源代码中使用std::min/std::max会出现错误。intmain(){intx=std::max(0,1);inty=std......
  • C++ 判断闰年简单代码
    闰年闰年分为普通闰年和世纪闰年1582年以来的置闰规则:普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。世纪闰年:公历年份是整百数的......
  • 周六900C++班级-2022-11-19 01背包
    背包问题关系图  问题描述若有N件物品和一个最多能装重量为W的背包,一个物品只有两个属性:重量和价值。第i件物品的重量是weight[i],得到的价值是value[i]。假......
  • 用汇编的眼光看C++(之 总结篇)
       早在八月份的时候,就陆陆续续写了二十多篇用汇编语言看C++的博客内容。在此为了做一个概括,也为了朋友们看起来更方便,我们利用这么一篇博客对所有的文章做一个总结。如......
  • socket通信编程C++实现
    socket提供了套接字,以方便我们想读取文件一样进行网络进程间的数据通信。在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文......