且看一文梳理VS2019中dll的创建使用
动态链接库(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编译器的函数名修饰规则:
- __stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如 _functionname@number。
- __cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。
- __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文件(有两种添加方法)
- 属性页->链接器->常规->附加库目录( 添加.lib文件路径); 属性页->链接器->输入->附加依赖项 (添加.lib文件名)
- 直接用#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函数
LoadLibrary、GetProcAddress和FreeLibrary显式地装载、卸载dll。
显式链接整体思路:
- 声明头文件<windows.h>,说明我想用windows32方法来加载和卸载DLL
- 然后用typedef定义一个指针函数类型(这个指针类型,要和你调用的函数类型和参数保持一致)
- 定义一个句柄实例,用来取DLL的实例地址。(HMODULE hdll;)
- 加载目标DLL,即 LoadLibrary()函数,将DLL加载到句柄实例,若成功则返回该DLL模块的句柄,否则返回NULL
- 获得导出函数的地址,即GetProcAddress()函数,成功时返回函数地址,否则返回NULL
- 调用导出函数
- 卸载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库

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的创建使用的更多相关文章
- Delphi中DLL的创建和使用(转)
Delphi中DLL的创建和使用 1.DLL简介: 2.调用DLL: 3.创建DLL: 4.两个技巧: 5.初始化: 6.例外处理. 1.DLL简介 ...
- 一文梳理JavaScript中的this
最近零零碎碎看了许多关于this的文章,本着"好记性不如烂笔头"的思想,特在这里整理一下this有关的知识点.[长文警告!!!] 接下来,笔者将按照以下目录对this进行阐述: t ...
- Delphi中DLL的创建和使用
参考:http://blog.csdn.net/ninetowns2008/article/details/6311663 结合这篇博客:http://www.cnblogs.com/xumenger ...
- KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态
KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态获取 <寒江独钓>内核学习笔记(5) 继续我们的线程相关的数据结构的学习.接下来我们学习 KTH ...
- SharePoint 2013 文档库中PPT转换PDF
通过使用 PowerPoint Automation Services,可以从 PowerPoint 二进制文件格式 (.ppt) 和 PowerPoint Open XML 文件格式 (.pptx) ...
- delphi中DLL编程详解
10.1 Windows的动态链接库原理 动态链接库(DLLs)是从C语言函数库和Pascal库单元的概念发展而来的.所有的C语言标准库函数都存放在某一函数库中,同时用户也可以用LIB程序创建自己的函 ...
- 解决VS2019中.net core WPF 暂时无法使用 Designer 的临时方法
目录 解决 VS2019 中.net core WPF 暂时无法使用 Designer 的临时方法 安装 vs 2019 professional/enterprise版本 在vs的设置里,勾选.NE ...
- 解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法
目录 解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法 安装 vs 2019 professional/enterprise版本 在vs的设置里,勾选 ...
- css 文档流中块级非替换元素水平区域的计算规则(1)
最近在读<Basic Visual Formatting in CSS>,结合以前看的<css权威指南>和css标准.今天就做个笔记. 以前在遇到一些宽度不明确指明的一些布局的 ...
随机推荐
- 8、oracle密码过期设置
8.1.登录到oracle实例: [oracle@slave-node2 ~]$ echo $ORACLE_SID orcl [oracle@slave-node2 ~]$ sqlplus sys/1 ...
- ps2020 将图片中的字清除 并且不损坏背景图
步骤:1:使用选框工具选中要删除的字:2:选择-->色彩范围,选中字体颜色 :3.选择-->修改-->扩展:4.图片区域,右键填充--内容识别--确定: 1.使用选框工具选中要删除 ...
- 安卓手机改造服务器——基本环境配置(CentOS7 arm32)
安装好CentOS系统之后,我们需要对环境进行一些基本的配置,让Linux更好用 写在前面 注意:本文章是针对arm32的CentOS7进行配置的,其他系统或不同架构不要尝试. 配置yum镜像源 1. ...
- RNA
原始地球 你会想,我们每一个细胞中都有一个遗传分子叫做DNA?那么,DNA之前有没有什么遗传分子呢?我的答案是:"有".在远古地球,那个海底有无数火山,喷发的火山口两侧都是喷涌出的 ...
- UI自动化学习笔记- UnitTest单元测试框架详解
一.UnitTest基本使用 1. UnitTest框架 1.1 什么是框架 说明: 框架英文单词frame 为解决一类事情的功能集合 1.2什么是UnitTest框架 概念:UnitTest是pyt ...
- MyEclipse中,编写properties文件,输入中文显示乱码
我在properties文件中输出中文,结果显示的是乱码,额......好吧,其实不是乱码,哪有这么规范的乱码 其实是在输入中文时发生了转码,就是下面这个样子: 字符集不支持中文,修改方法: 选中你工 ...
- buu pyre
一.下载附件是是pyc的字节码文件,找个在线网站反编译一下 思路还是挺清晰: 先逆着求出code, 这里就是求余,有点麻烦,那个+128%128其实没啥用的,省略就好了 算法里面再处理一下细节,跑一下 ...
- sort,wc,uniq 排序与统计命令
sort命令 语法:sort [parameter] [file or stdin]参数: -f:忽略大小写的差异,例如A和a视为编码相同 -b:忽略最前面的空格符部分 -n:使用"纯数字& ...
- PYTHON找色不变移动
import cv2 import aircv as ac import numpy as np def wmhd(sjh): bzz0=0 bzz1=0 bzz2=0 xxa=0 yya=0 xxb ...
- 【剑指offer】03.数组中重复的数组
剑指 Offer 03. 数组中重复的数字 知识点:数组:哈希表:萝卜占坑思想 题目描述 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些 ...