使用的编译器:vs2017.
注:以下例子的配置都是Debug.
1.1 :新建一个空项目,取名“TestDll";
1.2:在该项目下添加”TestDll“类。
1.3:TestDll.h添加以下代码:
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
#include
// 将此类的符号导出到 DLL中
class DLL_API CDll {
public:
CDll(void);
void Output(void);
// TODO: 在此处添加方法。
};
//导出变量符号到DLL中
extern DLL_API int nDll;
//导出函数符号到DLL中
DLL_API int fnDll(void);
1.4: TestDll.cpp添加以下代码:
#include "TestDll.h"
// 这是导出变量的一个示例
DLL_API int nDll = 0;
// 这是导出函数的一个示例。
DLL_API int fnDll(void)
{
std::cout << "this is fnDll function!"<
1.5:修改项目属性:
属性页->配置属性->常规中修改两项:
目标文件扩展名: .dll
配置类型: 动态库(.dll)
属性页->配置属性->C/C+±>预处理器中,预处理器定义中添加预处理宏:
DLL_EXPORTS;
然后ctrl+shift+b组合键生成dll.
1.6: 在生成的Debug文件中找到TestDll.dll文件和TestDll.lib文件.和头文件TestDll.h三个文件一起保存下来,这三个文件就是使用DLL的时候需要用到的.
头文件中为什么要有下面的代码呢?
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
__declspec(dllexport)意思是导出符号到dll.
__declspec(dllimport)意思是从dll导入符号.
我们生成的时候,在项目属性中定义了DLL_EXPORTS宏,所以生成时将符号导出到DLL中.
我们在使用DLL的时候,没有定义到DLL_EXPORTS宏,所以使用的时候从DLL中导入符号到.exe文件,以供.exe文件使用.
除了这种创建方式,还有一种是使用vs的模板创建的dll项目:
该操作会生成一个dllmain.cpp文件,里面有以下代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
这是dll文件的入口,只有在dll中需要new和dll操作时才需要用到这个,假如是一些单纯的函数库,或者该dll没有涉及资源的管理的话,则无需理会该文件.
如果有,则在case DLL_PROCESS_ATTACH语句后面做初始化操作,在case DLL_PROCESS_DETACH语句后面做反初始化操作.
这个东西应该是和构造函数一样的,假如dll中没有定义应该会自动生成一个默认的.(不确定是否正确).
///
2.1: 创建一个控制台项目,在.cpp文件中添加以下代码:
#include
using namespace std;
#include"DLL/TestDll.h"
#pragma comment(lib,"./DLL/TestDll.lib")
int main(void)
{
CDll TestDll;
cout << "动态库中的成员函数:";
TestDll.Output();
cout << "动态库中的变量:" << nDll<
2.2: 在源代码的文件夹下面新建一个DLL文件夹,然后将1.6步骤中保存的TestDll.lib文件和TestDll.h文件复制到DLL文件夹中,然后运行,这时候会提示错误,然后在生成的Debug文件夹中(也就是.exe文件所在的位置),将1.6步骤中保存的TestDll.dll文件复制到该目录下,再运行,输出:
动态库中的成员函数:this is CDll::Output function!
动态库中的变量:0
动态库中的函数:this is fnDll function!
我曾疑问,不是说.lib是静态库,在编译的时候会将所有的 信息都一起编译到.exe文件中,.dll是动态库,在运行时动态加载吗,那么为何在使用.dll的时候还需要.lib文件呢?
后来才明白,.lib文件有两种,一种是静态库,是静态编译出来的,索引和实现都在其中.另一种是与动态编译出来的dll文件对应的lib文件,一般是一些索引信息,具体的实现在dll文件中.
在我们编译的时候只需要.lib文件即可,即使没有.dll文件也是可以编译成功的.所以在使用的时候我们的源代码是包含头文件和链接.lib文件:
#include"DLL/TestDll.h"
#pragma comment(lib,"./DLL/TestDll.lib")
只有在程序中运行时才需要dll,所以我们的dll文件是和.exe文件同一目录的.
另外调用dll还有另外一种动态调用的方式,需要使用一些api,还需要使用vs中的dumpbin工具,比较麻烦,但好处是更加灵活(动态加载卸载),不用将dll的信息编译到程序中,生成的程序也更小.
代码:
#include
//包含头文件,以使用LoadLibrary,GetProcAddress,FreeLibrary函数.
#include
//只需包含头文件,无需连接.lib.
#include"DLL/TestDll.h"
//#pragma comment(lib,"./DLL/TestDll.lib")
using namespace std;
int main(void)
{
//定义函数指针.该指针必须要和我们接下来要查询的函数的返回值和参数相等.
typedef int (*pFunc)();
//定义句柄.
HINSTANCE hdll = NULL;
//加载dll.需要设置属性->属性页->配置属性->常规->字符集->使用多字节字符集.使用Unicode不行.
//参数是dll的地址.
hdll = LoadLibrary("./TestDll.dll");
//判断是否加载成功.
if (NULL == hdll)
{
return 0;
}
//获得函数地址,将其赋给函数指针.第二个参数是函数名.函数名使用dumpbin工具查询得到.
pFunc func =(pFunc)GetProcAddress(hdll, "?fnDll@@YAHXZ");
//判断是否成功.
if (NULL == func)
{
//如果不成功,需要先卸载该dll再返回.
FreeLibrary(hdll);
cout << "2";
return 0;
}
cout << "动态库中的函数:";
//用函数指针调用该函数.
(*func)();
//使用完成,卸载dll.
FreeLibrary(hdll);
return 0;
}
以我们的TestDll.dll为例:
1.找到dll地址,笔者这里为C:\Users\dai19\source\repos\TestDll\Debug;
2.打开vs的"适用于vs 2017的x64本机工具命令提示"
3.输入:cd C:\Users\dai19\source\repos\TestDll\Debug (即dll地址)
4.输入:dumpbin TestDll.dll -exports TestDll.dll,找到我们需要的函数,在GetProcAddress函数的第二个参数中填入.