且看一文梳理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标准.今天就做个笔记. 以前在遇到一些宽度不明确指明的一些布局的 ...
随机推荐
- JavaScript编写计算器的发展史
JavaScript编写计算器的发展史: 编写一个普通的四则运算: <!DOCTYPE html> <html lang="en"> <head> ...
- 资源:Postgresql数据库下载路径
postgresql下载路径: https://www.enterprisedb.com/downloads/postgres-postgresql-downloads
- Maven:Maven的project标签报错红线
作者在外网完成demo项目,把Maven的本地库打成压缩包放进内网时,Maven的project标签报错红线,且别的依赖不报错,同时Maven不引入本地仓库的依赖包. 解决方法: 进入自己的Maven ...
- Python+unittest+excel
接口测试设计思想: 框架结构如下: 目录如下: readme: config下的run_case_config.ini 文件说明: run_mode: 0:获取所有sheet页 1: if case_ ...
- 《基于JQuery和CSS的特效整理》系列分享专栏
<基于JQuery和CSS的特效整理>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/201724.html 文章 一款基于jQue ...
- C语言:进制表示
二进制由 0 和 1 两个数字组成,使用时必须以0b或0B(不区分大小写)开头 八进制由 0~7 八个数字组成,使用时必须以0开头(注意是数字 0,不是字母 o) 十六进制由数字 0~9.字母 A~F ...
- viewport深入理解和使用
什么是viewport ? viewport是用户网页的可视区域,也可叫做视区.手机浏览器是把页面放在一个虚拟的窗口(viewport)中,通常这个虚拟的窗口比屏幕宽,这样就不用把网页挤到很小的窗口中 ...
- Spring框架中一个有用的小组件:Spring Retry
1.概述 Spring Retry 是Spring框架中的一个组件, 它提供了自动重新调用失败操作的能力.这在错误可能是暂时发生的(如瞬时网络故障)的情况下很有帮助. 在本文中,我们将看到使用Spri ...
- ubuntu平台下,字符集的转换命令iconv
iconv命令格式 iconv -f 源字符集(要转换文件的字符集) -t 目标字符集 file iconv -f gb18030 -t utf-8 file 默认情况下,不改变原文件,输出到屏幕. ...
- windows程序快速启动的方式:WIN键+R
WIN键+R是windows快速启动程序的一种方式,一般能独立运行的程序都能以这种方式启动.如notepad.calc.explorer等程序. 在命令行方式下explorer加上不同的参数,会得到不 ...