动态链接库(dll)

Windows下有静态链接(lib)库和动态链接库(dll)两种共享代码的方式。

本文将介绍dll的应用场景,以及在vs2019平台下的生成和使用。

今天的笔记内容说的是平时经常能看见的,运行 VS 项目的时候老在下方加载的 .dll 。包括一小部分的理论和超大部分的实操。


 [What] dll是什么

动态链接库(Dynamic Link Library)又称为“应用程序扩展”,在windows系统中,大多数应用程序并非仅有一个可执行文件exe,同时也包含一些相对独立(模块化)的dll文件。dll中存放函数代码实现,exe中存放dll中相应函数代码的地址,而且dll中的代码可以被多个exe调用而在内存中仅保留一份拷贝,从而节省了内存空间。

[How] 如何生成dll

步骤<1>:创建新项目

步骤<2>:配置新项目

输入“项目名称”,然后选择工程“位置”,“解决方案名称”与“项目名称”相同,是自动生成的,如果没有特殊需求建议不要修改,不要勾选“将解决方案和项目放在同一目录中”,最后点击“创建”按钮。

步骤<3>:导出DLL

vs官方文档中提供了两种方式可以导出dll中的函数:

  • 关键字__declspec(dllexport):操作简单,但通用性较差。可见,vs创建dll项目时默认使用了该方式
  • 模块定义文件(.def):通用性(指给其他语言eg. Java、C#调用)好,但操作相对复杂

使用关键字__declspec(dllexport)


(1)首先新创建头文件“CreateDll.h”,它的作用是用来声明需要导出的函数接口。

#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif //导出类
class MYDLL_API Rectangle
{
public:
double getarea(double w, double h);
void print(); }; //导出函数
extern"C" MYDLL_API int __stdcall mysum(int a, int b);

(2)然后我们需要在‘CreateDll.cpp’中实现在‘CreateDll.h’中被声明的函数,代码如下:

#include "pch.h"
#include "CreateDll.h"
#include<iostream>
double Rectangle::getarea(double w, double h)
{
return w * h;
}
void Rectangle::print()
{
std::cout << "已被打印";
}
int __stdcall mysum(int a, int b)
{
return a + b;
}

(3)点击重新生成解决方案,即在debug目录下生成MyDll.lib和MyDll.dll

代码分析:

  • __declspec(dllexport)此修饰符告诉编译器和链接器被它修饰的函数或变量需要从DLL导出,以供其他应用程序使用;

与其相对的还有一句代码是__declspec(dllimport),此修饰符的作用是告诉编译器和链接器被它修饰的函数或变量需要从DLL导入

  • extern "C"的作用是告诉编译器将被它修饰的代码按C语言的方式进行编译

这是由于C语言没有重载,不会改变函数名。而C++中有重载,在编译过程中会根据返回值和参数修改函数名。

  • __stdcall定义导出函数入口点调用约定为_stdcall

C编译器的函数名修饰规则:

  1. __stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如 _functionname@number。
  2. __cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。
  3. __fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number

模块定义文件(.def)


(1)新建.def文件

VS会自动添加.def文件为链接器输入:

 (2)实现一个dll函数

(3)编写.def文件如下

[How] 如何调用dll

新建一个控制台应用,在其中调用上述生成的dll。

调用dll有两种链接方式:隐式链接显式链接无论哪种方式都要求将dll和exe放在同一目录下


隐式链接


  • 隐式链接需要三个文件:.h文件、.lib文件 和 .dll文件。
  • 对于.h文件: 属性页->C/C++->附加包含目录 添加路径并引用。(或者直接引用绝对路径)
  • 对于.lib文件(有两种添加方法)
  1. 属性页->链接器->常规->附加库目录( 添加.lib文件路径);  属性页->链接器->输入->附加依赖项 (添加.lib文件名)
  2. 直接用#pragma comment(lib,"MyDll.lib) (需要将该lib文件放到与exe同目录下)

在配置好文件后编写代码,调用dll:

#include"CreateDll.h"
#include<iostream>
#pragma comment(lib,"MyDll.lib")
int main()
{
Rectangle rect;
std::cout << "矩形面积:" << rect.getarea(3, 2)<<std::endl;
rect.print();
std::cout << "二数相加" << mysum(3, 2);
return 0;
}


显式链接


  • 显式链接只需要一个文件:.dll文件。
  • 所谓显式链接,就是直接调用WIN32 API函数LoadLibraryGetProcAddressFreeLibrary显式地装载、卸载dll。

显式链接整体思路:

  1. 声明头文件<windows.h>,说明我想用windows32方法来加载和卸载DLL
  2. 然后用typedef定义一个指针函数类型(这个指针类型,要和你调用的函数类型和参数保持一致)
  3. 定义一个句柄实例,用来取DLL的实例地址。(HMODULE hdll;)
  4. 加载目标DLL,即 LoadLibrary()函数,将DLL加载到句柄实例,若成功则返回该DLL模块的句柄,否则返回NULL
  5. 获得导出函数的地址,即GetProcAddress()函数,成功时返回函数地址,否则返回NULL
  6. 调用导出函数
  7. 卸载dll
#include<iostream>
#include<Windows.h>
typedef int(*Pmysum)(int a, int b);//定义一个指针函数类型
int main()
{
HMODULE Hdll = LoadLibrary(L"MyDll.dll");//获取dll地址
if (Hdll!=NULL)
{
Pmysum mysunm = (Pmysum)GetProcAddress(Hdll, "mysum");//获取dll中的函数地址
if (mysunm !=NULL)
{
std::cout << "调用两变量相加函数:"<<mysunm(3, 2);
}
}
FreeLibrary(Hdll);//卸载dll
return 0;
}

这也暴露出显式链接的一个弊端:要求开发人员必须清楚地知道调用函数的导出名称和传参格式。extern "C"和def文件相当于给函数重命名,如果想调用默认c++方式导出的函数,就要用那一长串修饰后的函数名。


实例:用显式调用dll中的类

首先需要强调,当使用某个类时一般目的有二:实例化成对象或者继承它产生新类。对于前者,我们可以构造一个抽象类来连接调用方和DLL。

一,创建dll库

1️⃣创建动态链接库项目,新建一个接口类Interface

2️⃣在Interface.h

#ifdef INTERFACE_EXPORTS
#define INTERFACE_API __declspec(dllexport)
#else
#define INTERFACE_API __declspec(dllimport)
#endif #pragma once class Interface
{
public:
virtual void ShowMsg() = 0; // 将调用方需要调用的成员函数声明成纯虚函数
virtual ~Interface() {};// 抽象类的虚析构函数
};
extern "C" INTERFACE_API Interface * Export(void); //外部接口

3️⃣Interface.cpp( 通过导出外部接口向调用方提供指向派生类Test对象的基类指针)

#include "pch.h"
#include "Interface.h"
#include"Test.h"
// 通过导出函数形式向调用方提供指向派生类对象的基类指针
Interface* Export(void)
{
return (Interface*)new Test();
}

4️⃣将真正要调用的类Test声明成抽象类 Interface 的派生类

Test.h

#pragma once
#include "Interface.h"
#include <string>
class Test:public Interface
{
public:
Test();
virtual ~Test();
virtual void ShowMsg(void);//重写虚函数
private:
std::string s;
};

Test.cpp

#include "pch.h"
#include "Test.h"
#include<iostream>
Test::Test()
{
s = "hello form dll";
} Test::~Test()
{
std::cout << "destroy";
} void Test::ShowMsg()
{
std::cout << s << std::endl;
}

二,显式调用dll

创建一个空项目testdll,将生成的Mydll.dll和Interface.h放入testdll的目录下

在testdll项目中新建rundll.cpp。动态调用dll

#include <Windows.h>
#include"Interface.h" // 包含抽象类从而使用接口
#include<iostream> using pExport = Interface * (*)(void); // 定义指向导出函数的指针类型
int main()
{
HINSTANCE hDll = LoadLibrary(L"Mydll.dll");// 加载DLL库文件,DLL名称和路径用自己的
if (hDll !=NULL)
{
pExport Get = (pExport)GetProcAddress(hDll, "Export");// 将指针指向函数首地址
if (Get == NULL)
{
std::cout << "load address fail \n";
return -1;
}
Interface* t = Get();// 调用导出函数获得抽象类指针
t->ShowMsg();// 通过该指针调用类成员函数
delete t; // 释放DLL中生成的对象
FreeLibrary(hDll); //释放库句柄
}
system("pause");
return 0;
}

此时需要注意两点:

  • 我们需要把Interface.h放在UseDLL工程目录下
  • 如果编译时出现:无法将参数 1 从“const char [14]”转换为“LPCWSTR”的错误,则我们需要点击项目属性,常规-》字符集-》改为“未设置”即可

实际上整个项目的方法是Interface完成了接口的设置,而具体的实现在test中进行,真正使用了类的抽象性和多态性,封闭性。

且看一文梳理VS2019中dll的创建使用的更多相关文章

  1. Delphi中DLL的创建和使用(转)

    Delphi中DLL的创建和使用     1.DLL简介:   2.调用DLL:   3.创建DLL:   4.两个技巧:   5.初始化:   6.例外处理.            1.DLL简介  ...

  2. 一文梳理JavaScript中的this

    最近零零碎碎看了许多关于this的文章,本着"好记性不如烂笔头"的思想,特在这里整理一下this有关的知识点.[长文警告!!!] 接下来,笔者将按照以下目录对this进行阐述: t ...

  3. Delphi中DLL的创建和使用

    参考:http://blog.csdn.net/ninetowns2008/article/details/6311663 结合这篇博客:http://www.cnblogs.com/xumenger ...

  4. KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态

    KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态获取 <寒江独钓>内核学习笔记(5)   继续我们的线程相关的数据结构的学习.接下来我们学习 KTH ...

  5. SharePoint 2013 文档库中PPT转换PDF

    通过使用 PowerPoint Automation Services,可以从 PowerPoint 二进制文件格式 (.ppt) 和 PowerPoint Open XML 文件格式 (.pptx) ...

  6. delphi中DLL编程详解

    10.1 Windows的动态链接库原理 动态链接库(DLLs)是从C语言函数库和Pascal库单元的概念发展而来的.所有的C语言标准库函数都存放在某一函数库中,同时用户也可以用LIB程序创建自己的函 ...

  7. 解决VS2019中.net core WPF 暂时无法使用 Designer 的临时方法

    目录 解决 VS2019 中.net core WPF 暂时无法使用 Designer 的临时方法 安装 vs 2019 professional/enterprise版本 在vs的设置里,勾选.NE ...

  8. 解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法

    目录 解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法 安装 vs 2019 professional/enterprise版本 在vs的设置里,勾选 ...

  9. css 文档流中块级非替换元素水平区域的计算规则(1)

    最近在读<Basic Visual Formatting in CSS>,结合以前看的<css权威指南>和css标准.今天就做个笔记. 以前在遇到一些宽度不明确指明的一些布局的 ...

随机推荐

  1. ESP32-性能监控笔记

    基于ESP-IDF4.1 1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #i ...

  2. Java | 循环的控制语句

    循环的控制语句 循环的控制语句有两种:break.continue 两种. braak可以用于强制限出循环. continue可以用于强制结束本次循环. break braak可以用于强制限出循环. ...

  3. Windows内核开发-2-开始内核开发-2-内核开发入门

    Windows内核开发-2-开始内核开发-2- 第一个驱动程序: 直接采用vs2019中的Empty WDM Driver 模块创建: 初始的项目文件夹中有一个Driver Files里面会有一个.i ...

  4. 「AGC027D」Modulo Matrix

    「AGC027D」Modulo Matrix 传送门 神仙构造题. 首先考虑一个非常自然的思路,我们把棋盘黑白染色后会变成一个二分图,黑色棋子只会与白色棋子相邻. 也就是说,我们可以将二分图的一部随便 ...

  5. Django基础-03篇 操作Django自带的admin后台

    1.使用model.py来定义表结构,使用命令同步到数据库 python manage.py makemigrations #生成表结构(py) python manage.py makemigrat ...

  6. 一文搞懂一致性hash的原理和实现

    在 go-zero 的分布式缓存系统分享里,Kevin 重点讲到过一致性hash的原理和分布式缓存中的实践.本文来详细讲讲一致性hash的原理和在 go-zero 中的实现. 以存储为例,在整个微服务 ...

  7. Tomcat网站根目录设置

    直接将war放入到webapps目录下 修改server.xml文件,在Host节点下添加如下代码 <Context path="/" docBase="web&q ...

  8. 【16位RAW图像处理三】直方图均衡化及局部直方图均衡用于16位图像的细节增强。

    通常我们生活中遇到的图像,无论是jpg.还是png或者bmp格式,一般都是8位的(每个通道的像素值范围是0-255),但是随着一些硬件的发展,在很多行业比如医疗.红外.航拍等一些场景下,拥有更宽的量化 ...

  9. Python+Requests+Xpath(解析)爬取某站点简历图片(数据分析三)

    1.环境安装 pip install lxml 2.解析原理 使用通用爬虫爬取网页数据 实例化etree对象,且将页面数据加载到该对象中 使用xpath函数结合xpath表达式进行标签定位和指定数据提 ...

  10. Leetcode:230. 二叉搜索树中第K小的元素

    Leetcode:230. 二叉搜索树中第K小的元素 Leetcode:230. 二叉搜索树中第K小的元素 思路: 利用BST的中序历遍的结果为其排序后的结果,我们可以利用其特性直接找到第k个中序遍历 ...