且看一文梳理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标准.今天就做个笔记. 以前在遇到一些宽度不明确指明的一些布局的 ...
随机推荐
- CentOS-yum安装Nginx
查看系统版本 $ cat /etc/redhat-release Nginx 不在默认的 yum 源中,使用官网的 yum 源 $ rpm -ivh http://nginx.org/packages ...
- 升级IDEA后Lombok不能用了,如何解决?
今天到工作室比较晚,在电脑前吃着早饭,看到提示IDEA提示升级,寻思已经有好久没有升过级了.一样等着,就升级下吧. 升级完毕重启之后,突然发现好多错误,原来的应用也没法启动了.仔细一看报错信息,是由于 ...
- PHP严格类型检查模式
前言 PHP默认情况下是弱类型校验模式,在php7下declare新增了strict_types指令,通过设置strict_types的值(1或者0),1表示严格类型校验模式,作用于函数调用和返回语句 ...
- ADO.NET整理 [转]
虽然我们都知道ADO.NET是对数据库的操作,但是要真的说出ADO.NET的具体含义还不是很容易. ADO.NET是ActiveX Data Objects的缩写,它是一个COM组件库,用于在micr ...
- pxe+kickstart部署多个版本的Linux操作系统(上)---原理篇
PXE概述: 1.PXE(Pre-bootExecution Environment),预启动执行环境2.通过网络接口启动计算机3.支持工作站通过网络从远端服务器下载映像,并由此支持通过网络启动操作系 ...
- C语言:地址
一切都是地址 C语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用.数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是 ...
- Java基础00-数据输入5
1. 数据输入 1.1 数据输入概述 我们需要的数据(比如账号密码)并不是一开是就有的,而是要输入的. 1.2 Scanner使用的基本步骤 1.3 案例
- Python基础之动态添加属性,方法,动态类,静态类
## 动态添加属性class Person: def __init__(self,name): self.name = name# 1.通过对象.属性名称来操作p = Person('KTModel' ...
- springMVC-12-整合spring和springmvc
问题1: 好像我们只需要使用springmvc的配置文件作为IOC容器就可以了 --> 需要进行Spring 整合SpringMVC吗? ----> 还是否需要再加入Spring的IOC容 ...
- 给你的Mac 整个好用的命令行iTerm2 + zsh + oh-my-zsh + powerlevel10k
给你的Mac 整个好用的命令行iTerm2 + zsh + oh-my-zsh + powerlevel10k 介绍 iTerm2 是一个MacOS 下的终端模拟器,和其他的终端本质上没啥大不同.但相 ...