Windows所支持的动态链接库(dll),对软件的开发和维护提供了极大的便利。dll把程序分成若干个模块,某个模块更新的时候,只需要更新对应的dll即可。编译器按照一定调用约定,调用dll中的函数,并返回值。dll的使用请参阅:DLL教程 - 如何编写动态链接库_Fisher-优快云博客_dll教程
1、C++中dll的局限性
为了说明这个问题,请看下面的一个例程。使用C++开发了一个字符串处理的类,这个字符串类的功能十分强大,现在将这个类封装成了dll来向外发布。字符串处理类为FastString,其代码如下:
// FastString.h
class __declspec(dllexport) FastString
{
public:
FastString(const char* ch);
~FastString();
int Length() const;
private:
char* m_char;
};
// FastString.cpp
#include "FastString.h"
#include <cstring>
#include <new>
FastString::FastString(const char* ch)
: m_char(new char[strlen(ch) + 1])
{
strcpy(m_char, ch);
}
FastString::~FastString()
{
delete[] m_char;
}
int FastString::Length() const
{
return strlen(m_char);
}
某个用户A使用了这个类,并编写的代码如下:
#include <iostream>
#include "FastString.h"
int main()
{
FastString fs("Hello Word");
std::cout << "Length:" << fs.Length() << "\n";
}
编译运行后,效果正确,运行如下:
一切皆大欢喜。
但是, FastString类的实现者发现,Length函数可以优化,其实在构造的时候,已经求过一次字符串长度,可以定义一个成员变量存储这个长度,于是,Length函数只需要直接返回这个成员变量即可。优化后的代码如下:
// FastString.h
class __declspec(dllexport) FastString
{
public:
FastString(const char* ch);
~FastString();
int Length() const;
private:
int m_len;
char* m_char;
};
// FastString.cpp
#include "FastString.h"
#include <cstring>
#include <new>
FastString::FastString(const char* ch)
: m_len(strlen(ch))
{
m_char = new char[m_len + 1];
strcpy(m_char, ch);
}
FastString::~FastString()
{
delete[] m_char;
}
int FastString::Length() const
{
return m_len;
}
对于刚刚的程序A,必须重新编译,才能正确运行。这是因为,在程序A中,要构造一个FastString对象,编译程序A的编译器需要知道FastString类占用的字节数,才能正确构造对象。上面的例程中,因为FastString类新增了成员变量,于是占用的字节会增加,因此,使用了FastString这个字符串类的应用程序必须重新编译。
对于这种发布给其他用户使用的库,假如每一次升级,依赖它的其他应用都需要重新编译,这是不可能的、也是不能被接受了。
造成这种问题的原因是因为dll模块的FastString类,直接暴露给了应用程序,紧接着FastString类改变,造成了二进制层面的不兼容。为了解决这个问题,必须要想出一种方法,使得暴露给用户的代码,在二进制层面上不会再改变。
C语言之所以不会出现这样的问题,是因为它的模型简单得多——清晰的调用约定,清晰的函数名匹配方式。C++则会复杂很多,如构造函数、析构函数、虚基类、虚表,C++规范只在行为上进行了约束,并没有在实现上或二进制上进行约束。为了能跨越编译器起到一致的效果,我们必须要隔离编译器相关的实现部分,不得出现跨边界的编译器相关行为。
基于以上2个原因(当然还有更多原因),微软在1993年发布了COM模型,之后又发布了一系列支持的接口(OLE, ActiveX, ATL等)。通过COM模型,我们可以编写出可分发的、跨编译器的、二进制兼容的C++组件。