前一篇文章中讲过C++中如何正确的定义接口类,那定义好的接口类如何正确使用?本篇将细细说说。
提供接口与实现
首先,声明一个接口:
// circle.h
// 圆的接口类
class Circle {
public:
virtual ~Circle() {};
// 接口方法:面积
virtual double area() = 0;
};
通过继承的方式实现这个接口:
// circle_impl.h
#include "circle.h"
// 圆的具体实现类
class CircleImpl : public Circle {
private:
double radius;
public:
CircleImpl(double radius);
double area() override;
};
// circle_impl.cpp
#include <cmath>
#include "circle_impl.h"
inline double pi() {
return std::atan(1) * 4;
};
CircleImpl::CircleImpl(double _radius) : radius(_radius) {
};
double CircleImpl::area() {
return pi() * radius * radius;
};
最后,通过管理类创建接口派生类的实例,或者销毁接口派生类的实例:
// circle_manager.h
#include "circle.h"
// 圆的创建工厂类
class CircleManager {
public:
static Circle* create(double radius); // 创建circle实例
static void destroy(Circle* circlePtr); // 销毁circle实例
};
// circle_manager.cpp
#include "circle_manager.h"
#include "circle_impl.h"
Circle* CircleManager::create(double radius) {
Circle* circlePtr = new CircleImpl(radius);
return circlePtr;
};
void CircleManager::destroy(Circle* circlePtr) {
delete circlePtr;
};
代码目录结构:
proj-+
|-inc-+
| |-circle.h
| |-circle_manager.h
|
|-src-+
|-circle_impl.h
|-circle_impl.cpp
|-circle_manager.cpp
其中inc目录用于存放Circle接口类和Circle管理类的声明,src目录中存放Circle实现类CircleImpl的声明和定义、Circle管理类CircleManager的定义。
然后,可以将以上代码编译成静态库circle.lib,并和inc目录中的头文件一起提供给外部调用:
如何使用静态库?
外部使用者编译时,需要做如下配置:
1). 把inc目录添加到“附加包含目录”中。
2). “附加依赖项”中添加circle.lib。
3). 把circle.lib所在目录的路径添加到“附加库目录”中。
外部使用者的代码如下:
// main.cpp
#include <iostream>
#include "circle_manager.h"
#include "circle.h"
int main()
{
Circle* circlePtr = CircleManager::create(3);
cout << circlePtr->area() <<endl;
CircleManager::destroy(circlePtr);
system("pause");
return 0;
}
以上代码只提供给外部circle的接口,circle的实现完全被隐藏了起来,外部将无从知晓,外部使用者只能通过circle管理类生成circle的派生类的实例。外部使用者得到circle派生类的实例后,除了能调用接口暴露的方法area()外,其它什么也做不了,这样就完全达到了使用接口的最终目标。
如何编译成动态库?
首先,添加一个新的头文件:
// dll_export.h
// if windows .dll
#ifdef _WINDLL
#ifdef DLL_API_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
// else if Linux or macOS .so
#else
#define DLL_API
#endif
添加此头文件后,代码可以在windows、Linux下都可编译生成动态库,只需在编译时设置不同参数就行了。
windows: /D "DLL_API_EXPORTS" /D "_WINDLL"
Linux: 不用配置额外参数
circle.h和circle_manager.h也要做相应改动:
// circle.h
#pragma once
#include "dll_export.h"
// 圆的接口类
class DLL_API Circle
{
public:
virtual ~Circle() {};
// 接口方法:面积
virtual double area() = 0;
};
// circle_manager.h
#pragma once
#include "circle.h"
#include "dll_export.h"
// 圆的创建工厂类
class DLL_API CircleManager
{
public:
static Circle* create(double radius);
static void destroy(Circle* circlePtr);
};
编译完成后将生成”circle.lib“和”circle.dll“文件:
proj-+
|-inc-+
| |-circle.h
| |-circle_manager.h
|
|-src-+
| |-circle_impl.h
| |-circle_impl.cpp
| |-circle_manager.cpp
|
|-bin-+
|-circle.lib
|-circlr.dll
如何使用动态库?
外部使用者编译时,需要做如下配置:
1). 代码中添加#pragma comment(lib,"circle.lib"), 这里是circle.lib,不是circle.dll。
2). 把inc目录添加到“附加包含目录”中。
3). “附加依赖项”中添加circle.lib,这里也是circle.lib,不是circle.dll。
4). 把bin目录所在路径添加到”附加库目录“中。
新的外部使用者的代码如下:
#include <iostream>
#include "circle_manager.h"
#include "circle.h"
#pragma comment(lib,"circle.lib")
int main()
{
Circle* circlePtr = CircleManager::create(3);
cout << circlePtr->area() << endl;
CircleManager::destroy(circlePtr);
system("pause");
return 0;
}
总结
这里有几点需要说明一下:
1、为什么CircleManager类即在提供创建实例的方法又要提供销毁实例的方法?
由于编译器的实现方式不同,dll的堆空间可能跟调用方的堆空间不同,它可能是由dll自己单独管理的,所以从dll中创建的实例,最好还是在dll中销毁。
2、对动态库的调用本文是通过隐式调用的方式完成的,对动态库的调用也可以使用显式调用的方式,但由于windows和Linux在使用显式调用时的API是不同的,不好提供统一的代码,所以本文没有举例,以后有机会再单独行文介绍。
参考文档
HowTo: Export C++ classes from a DLL
Exporting C++ classes from a DLL
Microsoft Visual Studio .NET 2003 Warning C4251
Exporting classes containing std:: objects (vector, map, etc) from a dll