动态链接库的编写
编写DLL时,在需要导出给别的程序用的名称前加上“`__declspec(dllexport),而在别的程序使用dll中名称时,对于普通名称可以直接使用,而对于含有静态成员的类时,则会出现链接器找不到类中的静态成员的错误,这时则需要使用
__declspec(dllimport)` 表明该类中的静态成员定义在DLL中。我们可以用一个宏定义来简化:
#define DLL_EXPORT
#ifdef DLL_EXPORT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif // DLL_EXPORT
在DLL的工程中定义DLL_EXPORT
宏,并且在需要导出的名称前加上DLL_API
,而在提供给用户的头文件中定义DLL_IMPORT
宏
函数
// 头文件 testdll.h
DLL_API std::shared_ptr<InterFace_B> getInstance();
// 实现文件 testdll.cpp
#include "testdll.h"
std::shared_ptr<InterFace_B> getInstance()
{
return std::shared_ptr<InterFace_B>(new B());
}
全局变量
// testdll.h
extern int DLL_API global;
// testdll.cpp
int global;
类
类里面如果有静态成员,则调用时必须使用__declspec(dllimport)
,否则会找不到静态成员
// testdll.h
class DLL_API A
{
public:
static int count;
static int sum(int a, int b);
void print(int x);
};
// testdll.cpp
int A::count = 0;
int A::sum(int a, int b)
{
return a + b;
}
编写DLL中的类时还有一个问题,就是如果其中包含了STL等取决于编译器实现的东西,比如一个string。当编写使用这个DLL的客户端程序时,包含DLL配套头文件后声明这个类的对象,对象中所使用的是当前编译器的string实现,而类的成员函数的实现是在DLL中的,它所使用的string实现是编写DLL时所使用的编译器的string实现。如果两者不同,就会产生错误。
解决方式是使用只包含C++基础类型(不取决与编译器实现的类型)的抽象基类和一个工厂方法, 那些取决于编译器实现的东西都放到抽象基类的派生类中,而这个派生类不导出,而工厂方法则构造一个派生类返回一个基类指针,这样用户可以使用这个基类指针调用派生类中的虚函数(多态特性),这样相当于统一使用DLL编译时编译器的实现:
// testdll.h
class DLL_API InterFace_B
{
public:
std::string name;
virtual const std::string& getName() const = 0;
virtual void setName(const std::string&) = 0;
};
#ifdef DLL_EXPORT
class B :public InterFace_B
{
public:
const std::string& getName() const override
{
return name;
}
void setName(const std::string& name) override
{
this->name = name;
}
};
#endif // DLL_EXPORT
DLL_API std::shared_ptr<InterFace_B> getInstance();
// testdll.cpp
std::shared_ptr<InterFace_B> getInstance()
{
return std::shared_ptr<InterFace_B>(new B());
}
动态库的调用
- 将DLL头文件中的
#define DLL_EXPORT
改为#define DLL_IMPORT
,包含进需要调用DLL的工程中(#include "../include/testdll.h"
) - 将DLL对应的lib文件也包含进来(右键资源文件add,或者
#pragma comment(lib, "../lib/A.lib")
- dll文件放到生成的可执行文件所在的文件夹中