动态链接库dll详解

2023-05-16

动态链接库概述

    DLL就是整个Windows操作系统的基础。动态链接库不能直接运行,也不能接收消息他们是一些独立的文件。

    Windows API中的所有函数都包含在DLL中。

    其中有三个最重要的DLL

    kernel32.dll,它包含用于管理内存、线程和进程的各个函数;

    User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数

    GDI32.dll,它包含用于画图和显示文本的各个函数

静态库和动态库

    静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LID)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe文件);

    在使用动态库的时候,往往提供两个文件:一个因入库和一个DLL。因入库包含被DLL导出的函数和变量的符合名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。

DLL的创建和使用

动态链接库的创建

新建一个动态链接库

新建一个.cpp文件,在头部添加 #include"pch.h"预编译文件。 应用程序想要访问动态库函数需要先将该函数导出,使用_declspec(dllexports)关键字可以将该函数导出。

#include"pch.h"

_declspec(dllexport) int add(int a, int b) {
	return (a + b);
}


_declspec(dllexport) int substract(int a, int b) {
	return (a - b);
}

通过dumpbin 指令可以查看我们编写的dll有哪些导出函数

去到新建的.dll目录下。

输入

dumpbin -exports lib文件名即可查看该dll文件有哪些函数被导出。

 ordinal表示导出函数的序号

hint表示提示码,意义不大

RVA表示导出函数在dll模块中的位置

name表示导出函数名,编译器会根据自己的规则更改函数名,以保证函数重载。

动态链接库的使用

新建一个基于对话框的MFC应用

添加两个按钮,点击add按钮会调用dll文件中的add函数,点击“substract"按钮会调用dll文件中的substract函数

这里介绍隐式加载,隐式加载需要lib文件

使用动态库前要先将.dll文件加入到程序文件目录下

第一种方法:

在程序里添加#pragma comment(lib,"1124TDll.lib")代码

第二种方法:

将lib文件添加到附加依赖项中。

两种方法选一种即可 

在需要用到的动态库函数用关键字extern声明

extern int add(int a, int b);
extern int substract(int a, int b);
void CDllMFCTestDlg::OnBnClickedAddBtn() {
	CString str;
	str.Format(L"4+3 = %d", add(4, 3));
	MessageBox(str);
}

void CDllMFCTestDlg::OnBnClickedSubstractBtn() {
	CString str;
	str.Format(L"4-3 = %d", substract(4, 3));
	MessageBox(str);
}

我们也可以通过dumpbin命令查看生成的应用程序有哪些导入的dll 

在开发者提示命令里输入 dumpbin -imports 项目名,即可查看.exe添加的所有lib

DLL的导入和导出以及条件编译 

导入和导出

dll的导入和导出需要用到两个关键字_declspec(dllimport)和_declspec(dllexport)

_declspec(dllexport)是在类、函数以及数据声明时候使用。把dll里面的相关代码暴露出来给其他应用程序使用。提供给别的应用程序使用,标识提供者。共dll内部使用。

例如在编写dll库时:

_declspec(dllexport)int add(int a,int b) {
    return a+b;
}

_declspec(dllimport)是在外部程序需要使用DLL内相关内哦让那个使用的标识符。是把dll中的相关代码插入到应用程序中去。表示使用者。不是dll内部使用。

例如某个项目使用dll库中的add函数,先使用_declspec(import)关键字声明。

_declspec(dllimport)int add(int a,int b);

条件编译指令

使用条件编译的目的是使用“dll+头文件”的方式使用动态链接库,这样可以头文件中查看动态库中的函数声明,而不需管函数的具体实现。

在DLL.cpp中

添加 

#define DLL1_API _declspec(dllexport)

Dll1.cpp 

#include"pch.h"
#include"Dll1.h"

#define DLL1_API _declspec(dllexport)
int add(int a, int b) {
	return (a + b);
}


int substract(int a, int b) {
	return (a - b);
}	

在DLL.h中声明函数

#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport) 
#endif
DLL1_API int add(int a, int b);
DLL1_API int substract(int a, int b);

在编译dll文件时,DLL1_API已经在.cpp文件中被定义过而不会在.h中再被定义,因此它的值为_declspec(dllexport),也就是将函数导出。

而当某个项目需要用到dll库时,只需要在程序中田间#include"dll.h的路径”,这样在该项目只会编译dll.h文件,DLL1_API又会被定义为_declspec(dllimport),这样这些函数相对于项目来说又编程导入函数。

从DLL中导出C++类

如果在声明类的时,指定了导出标志,则该类中所有函数都被导出;

#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport) 
#endif


class  DLL1_API Point {
public:
	void outPut(int x, int y);
	void test();
};

 如果只给某个成员函数指定导出标志,则只导出指定标志的函数。

#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport) 
#endif


class   Point {
public:
	void DLL1_API outPut(int x, int y);
	void test();
};

解决名字改编的问题 

假人我们使用C++写了一个dll,而使用C语言去调用该函数。因为编译器已经将函数名字改编(名字粉碎),因此在调用时会报出”找不到标识符“的错误(除非调用时使用的是粉碎后的函数名)。

因此我们希望dll在编译时,导出函数的名称不要改编

导出时添加extern "C"

#define DLL1_API extern "C" _desclpec(dllimport)

Dll.h文件 

#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport) 
#endif

DLL1_API int  add(int a, int b);
DLL1_API int  substract(int a, int b);

Dll.cpp文件 

#define DLL1_API extern "C" _declspec(dllexport)
#include"Dll1.h"

int add(int a, int b) {
	return (a + b);
}

int substract(int a, int b) {
	return (a - b);
}

编译器就不会进行名字改编,一个用C语言编写的客户端程序可以调用C++的Dll。其缺点是不能导出类的成员函数,只能用于导出全局函数这种情况。 

使用_stdcall 标准的调用约定


  Dll.h文件 

#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport) 
#endif

DLL1_API int  _stdcall add(int a, int b);
DLL1_API int  _stdcall substract(int a, int b);

Dll.cpp文件 

#define DLL1_API extern "C" _declspec(dllexport)
#include"Dll1.h"

int _stdcall add(int a, int b) {
	return (a + b);
}

int -stdcall substract(int a, int b) {
	return (a - b);
}

 导出效果一样嘎嘎棒

如果导出函数采用了是标准调用约定_stdcall,访问这个dll的调用方也要采用该约定类型来访问响应的导出函数

使用def模块文件

新建一个.def文件

编写代码 

EXPORTS即调用_stdcall约定,也不会发送改编,只会调用这里显示的 

EXPORTS语句引入一个由多个definitions组成的节。每个定义必须在单独一行。EXPORTS关键字需要放在第一个定义所在的同一行或前一行 。.def文件可以包含一个或多个EXPORTS语句

导出的definitions的语法为: 

entryname[=internalname][@ordinal][NONAME][PRIVATE][DATA]
  • entryname是导出的函数名,这是必选的。如果导出的名称与DLL中的名称不同,则通过internalname指定DLL中导出的名称。例如,如果DLL导出的函数fun1(),要将它用作fun2(),则应指定
  • @ordinal允许指定是序号而不是函数名将进入DLL的导出表,这有助于最小化DLL的大小。LIB文件将包含序号与函数之间的映射,这使得可以像在使用DLL的项目中那样使用函数名。
  • 可选的NONAME关键字允许只按序号导出,并减小DLL中到处白哦的大小。但是,如果要在DLL上是使用GetProcAddress,则必须直到序号,因为名称将无效 

Dll动态加载 

使用LoadLibrary函数 加载动态库

HINSTANCE hInst = LoadLibraryA("1124TDll.dll");

函数执行成功,则返回该模块的句柄;函数执行失败,返回NULL。 

定义一个函数指针,并从dll中获取函数地址

因为dll中导出函数采用的是标准调用约定,访问这个dll的调用方也要采用该约定类型来访问响应的导出函数。

typedef int(_stdcall *ADDPROC)(int a, int b);//定义一个函数指针
ADDPROC Add = (ADDPROC)GetProcAddress(hInst, "add");//从dll中获取函数地址

GetProcAddress函数执行成功返回函数地址,失败返回NULL

if (!Add) {
	MessageBox(L"获取函数地址失败");
	TRACE("GetProcAddress errorcode :%d", GetLastError());
	return;
}

我们就可以通过这个函数指针使用这个函数了

CString str;
str.Format(L"4+3 = %d", Add(4, 3));
MessageBox(str);

用完之后通过FreeLibray函数释放

FreeLibrary(hInst);

GetProcAddress函数不仅可以通过函数名称导出,也可以通过序号导出。

如果导出函数名称例如下面这种已经被名称粉碎了,那么也必须使用这种名称

 也必须跟着用这个名称

typedef int(_stdcall *ADDPROC)(int a, int b);
ADDPROC Add = (ADDPROC)GetProcAddress(hInst, "?Add@@YAHHH@Z");

或者通过序号获取导出函数

ADDPROC Add = (ADDPROC)GetProcAddress(hInst, MAKEINTRESOURCEA(1));

DllMain函数

对可执行模块而言,入口时winmain;对dll而言,入口时Dllmain。但对dll来说,Dllmain是可选的,如果提供了Dllmain,系统加载时会调用Dllmain。

当我们在编写Dll时,如果某些函数需要用到当前Dll模块句柄,那么就可以为该Dll提供dllmain函数,然后头通过参数hMoudule,保存在一个全局变量里面,以供其他函数使用。

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:   //进程第一次加载Dll,并调用Dllmain函数
    case DLL_THREAD_ATTACH:    //当前进程正在创建一个线程
    case DLL_THREAD_DETACH:    //线程结束
    case DLL_PROCESS_DETACH:   //进程结束
        break;
    }
    return TRUE;
}

Dll有三个参数,

参数hMoudle表示动态链接库的模块句柄,当dll初次被加载时,句柄可以通过这个参数传递过来

参数ur_reason_for_call表示加载原因

参数lpReserved用处不大,保留参数。为NULL表示动态加载,非NULL表示静态加载

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态链接库dll详解 的相关文章

  • 解决:‘config.status: error: Something went wrong bootstrapping makefile fragments......’问题

    解决 xff1a config status error Something went wrong bootstrapping makefile fragments 问题 一 问题二 解决方法 一 问题 首先我们来看安装sqlite时报的这
  • TFTP服务器搭建与使用

    文章目录 一 TFTP协议二 TFTP服务器搭建1 安装TFTP服务器2 创建TFTP服务文件夹3 配置tftp文件4 配置tftpd hpa文件 三 TFTP服务器使用 一 TFTP协议 TFTP xff08 Trivial File T
  • 深入探讨Linux驱动开发:Linux设备树

    文章目录 一 设备树介绍二 设备树框架1 设备树框架2 节点基本格式3 节点部分属性简介 总结 一 设备树介绍 设备树 xff08 Device Tree xff0c 简称 DT xff09 是一种在嵌入式系统中描述硬件设备的一种数据结构和
  • 深入探讨Linux驱动开发:驱动介绍与hello驱动实例

    文章目录 前言一 Linux驱动介绍1 用户态和内核态2 内核功能介绍3 驱动程序介绍 二 驱动程序分类与注意事项1 驱动程序分类2 内核驱动开发注意事项 三 hello驱动开发1 驱动模块2 模块加载和卸载函数3 编写hello模块4 M
  • ROS中使用乐视 奥比中光(Astra Pro)深度相机显示彩色和深度图像

    环境 UbuntuROS Kinect or Melodic 奥比中光ROS驱动包安装地址 xff1a https github com orbbec ros astra camera 1 安装ROS 2 安装依赖 span class t
  • 深入探讨Linux驱动开发:字符设备驱动开发与测试

    文章目录 一 字符设备驱动介绍1 设备驱动介绍 二 设备号1 设备号介绍2 分配与释放设备编号 dev t类型 静态分配设备号 动态分配设备号 释放主次设备号 手动创建设备节点 自动创建设备节点 删除设备节点 三 字符设备注册1 cdev结
  • ZED-深度感知使用

    文章目录 1 深度感知配置2 得到深度数据2 1 得到深度值 3 展示深度图4 获取点云数据4 1 从点云数据中计算距离 5 得到法线图像6 调整深度分辨率 1 深度感知配置 可以在初始化时使用InitParameters xff0c 在运
  • Linux系统之 开机自启动程序脚本 编写

    Linux系统启动加载程序 最近完成了项目 xff0c 来个开机自启运行 找到已编译好的程序 xff08 以下是我编译的house xff09 span class token function ls span l span class t
  • 算法时间复杂度、空间复杂度分析

    算法时间复杂度 在计算机程序编写前 xff0c 依据统计方法对算法进行估算 xff0c 经过总结 xff0c 我们发现一个高级语言编写的程序程序在计算机上运行所消耗的时间取决于下列因素 1 算法采用的策略和方案 编译产生的代码质量 3 问题
  • Linux 选择题一百道

    cron 后台常驻程序 daemon 用于 xff1a A 负责文件在网络中的共享 B 管理打印子系统 C 跟踪管理系统信息和错误 D 管理系统日常任务的调度 在大多数Linux发行版本中 xff0c 以下哪个属于块设备 block dev
  • 程序返回return与系统退出exit

    程序返回return与系统退出exit return是语言级别的 xff0c 它表示了调用堆栈的返回 xff1b exit则是系统调用级别的 xff0c 它表示了一个进程的结束 return是返回函数调用 xff0c 如果返回的是main函
  • 结构体、结构体变量、结构体指针、字符串

    结构体 数组是用于保存一组相同类型数据的 而结构体是用于保存一组不同类型数组的 在使用结构体之前必须先定义结构体类型 因为C语言不知道你的结构体中需要存储哪些类型数据 我们必须通过定义结构体类型来告诉C语言 我们的结构体中需要存储哪些类型的
  • linux下脚本实现 切换root用户并执行

    借助一个工具 expect sudo apt span class token operator span get install expect 编写脚本 xff1a vim root sh span class token operato
  • csdn极客江南

    零基础学会 C 语言课程学习突破 1500 人 xff1a 学习地址 xff1a https edu csdn net course detail 31452 spm 61 1001 2014 3001 5507TS TS 专栏文章更新至第
  • 音视频编码格式认知

    编码格式对应的就是音频编码和视频编码 xff0c 音频编码标准和视频编码标准 xff0c 每种编码标准都对应的编码算法 xff0c 其目的是通过一定编码算法实现数据的压缩 减少数据的冗余 视频编码指的是通过特定的压缩技术 xff0c 将某个
  • TensorFlow、PyTorch各版本对应的CUDA、cuDNN关系

    TensorFlow PyTorch各版本对应的CUDA cuDNN关系 xff08 截止2021年4月7日 xff09 TensorFowLinuxCPUGPU macOSCPUGPU WindowsCPUGPU PyTorchCPUGP
  • Android源码新大陆

    vold amp av http aospxref com android 13 0 0 r3 xref system vold model http aospxref com android 13 0 0 r3 xref framewor
  • QT读取GPS模块时,显示不完全,一条指令只能ReadAll32个字节,数据被分开

    在用QT读取GPS模块的时候 xff0c 发现读取到的数据总是显示不出来经度 xff0c 用debug调试 xff0c 发现数据被分开 xff0c 一条指令只能读到N那里 xff0c GNRMC 064401 65 A 3110 47069
  • STM32CubeMX配置串口DMA传输实现不定长数据收发

    串口简介 串口是全双工的串行通信协议 串口通信指串口按位 xff08 bit xff09 发送和接收字节 xff08 一个字节有8位 xff09 尽管比特字节 xff08 byte xff09 的串行通信慢 xff0c 但是串口可以在使用一
  • 单片机串口不够用怎么办?

    扩展串口 一 为什么要扩展串口 xff1f 一块单片机的串口是有限的 xff0c 一般2 4个 当我们做一个项目时需要连接多个外设时跟单片机通讯时 xff0c 且通讯都是以串口形式 那么我们只能去扩展串口来满足我们的应用需求 二 解决方法

随机推荐

  • C语言:自定义实现strcat函数

    include lt stdio h gt include lt assert h gt char My Strcat char str1 const char str2 assert str1 amp amp str2 指针不为空 cha
  • 了解串口协议,及完成STM32的USART串口通讯程序,并用keil观察波形

    文章目录 前言一 串口协议1 RS 2322 485标准 二 RS 232 485和TTL电平1 RS 232电平2 4853 TTL电平4 区别 三 USB TTL转2321 CH3402 发送接收3 USB转RS 232 四 完成一个S
  • UART RS232 RS485协议原理及应用

    一 URAT UART Universal Asynchronous Receiver Transmitter 通用异步收发传输器 xff0c 简称串口 xff0c 是设备间进行异步通信的模块 UART负责处理数据总线和串行口之间的串 并
  • 2022年电赛E题声源定位跟踪系统

    我们组本来是奔着视觉题去的 xff0c 可是到比赛的时候突然发现好像就无人机比较合适 xff0c 但是我们都没玩过无人机 xff0c 本想转战小车 xff0c 可是材料突然发现要两辆小车 xff0c 材料也不够来不及买 xff0c 于是我们
  • Python中requests库使用方法详解

    python中requests库使用方法详解 前言 xff1a 一 什么是Requests二 安装Requests库三 各种请求方式1 基本的GET请求2 带参数的GET请求3 解析json4 获取二进制数据5 添加headers6 基本P
  • File Browser的安装(适用于Kali/Ubuntu/Debian)

    filebrowser是一个基于Go开发的开源轻量级免费的文件浏览器 xff0c 可以把电脑上的文件快速传到其他设备上 1 下载 下载地址 xff1a https github com filebrowser filebrowser rel
  • Ubuntu 挂载硬盘到 /home 目录下扩容

    实验室提供的 NVIDIA Jetson NX 内存容量太小 xff0c 只有16G xff0c 刷完机装上系统和 CUDA 等安装包后 xff0c 系统只剩下一点多个G xff0c 后面完全是不够用的 xff0c 因此需要加硬盘并挂载才能
  • vue-element-admin 二次开发 报错修改

    安装 GitHub git clone https github com PanJiaChen vue element admin git Gitee https gitee com panjiachen vue element admin
  • 常用字符串函数

    1 strlen 函数 strlen 函数用来计算字符串长度 用法 xff1a 结果 xff1a 2 strcpy 函数 用来将一个数组的内容拷贝到另一个数组 用法 xff1a 结果 xff1a 注意 xff1a 如果a1中的比a2中的长
  • C++ 学习日志——STL中的容器 Vector

    本文简单的介绍下STL下的容器 vector的简单的存放和遍历方法 目录 一 vector存放和访问方式 二 三种遍历方式 三 vector存放对象 四 vector存放指针 难点 一 vector存放和访问方式 迭代器 xff1a vec
  • ARM汇编程序入门实践

    一 stm32程序 1 新建工程 1 xff09 双击打开keil xff0c 点击菜单栏Project gt New Vision Project xff0c 新建项目 xff0c 在弹窗中设置工程项目的名称和路径 xff0c 在这里 x
  • 【STM32学习笔记】(5)—— STM32工程添加源文件和头文件

    向工程目录里创建头文件和源文件 STM32的工程文件构成较为复杂 xff0c 同时为STM32工程文件添加源文件和头文件也是较为复杂的 xff0c 下面就由此文章来介绍怎么给STM32工程添加源文件 xff08 c xff09 与头文件 x
  • 在STM32中使用printf函数

    一 目的 利用printf函数使得输出打印到串口中 二 工作原理 我们在C语言中为什么能够调用printf函数打印数据到控制台中 xff0c 那是因为printf函数又调用了fputc 函数 那么我们我们可不可以说printf函数之所以能够
  • 电赛备赛日记(一):K210与STM32串口通信

    拖更了n久的备赛日记终于来啦 xff0c 最近实现了关于K210图像识别并将所需数据 xff08 即目标类别 xff0c 目标在图像中的加权坐标 xff09 其中 xff0c 加权坐标指K210识别到的目标并框出的框的宽和高与框左上顶点的坐
  • 实验作业2(数组)

    64 TOC实验题目2 xff1a 某公司生产5种产品 xff0c 每周记录生产的每种产品数量和销售数量 每个月月末 xff0c 公司将对其生产规划进行评估 该评估需要以下一个或多个信息 每周生成和销售的数量 xff1b 所有生产产品的总量
  • 函数作业

    仅供交流 xff0c 禁止抄袭 实验一 求序列的和 问题描述 求Y 61 1 1 2 43 1 3 1 4 43 1 2 n 的前n项之和 输入形式 从键盘输入n 输出形式 输出n项和 按四舍五入的方式精确到小数点后第二位 样例输入 30
  • Windows下MySQL安装及配置

    目录 1 MySQL下载2 MySQL配置 mysql版本 xff1a 5 7 注 xff1a 1 现在最新的mysql版本是8 0 xff0c 但是一般安装软件都不推荐安装最新版本的 xff0c 因为可能会有一些错误无法预见 xff0c
  • SLAM遇到的问题及技巧

    文章目录 1 rosbag太大了 xff0c 打不开2 机器人的三维变换roslaunch的node强制关闭了 xff0c exit code 11 1 rosbag太大了 xff0c 打不开 以LZ4格式压缩 rosbag compres
  • 关于螺旋数组的讨论

    下面收集了几种可能的螺旋数组形式以及相应的主代码 供大家学习参考 给定N的值 xff0c 从矩阵的左上角输出顺时针螺旋矩阵 例如N 61 4时 xff0c 输出 xff1a 1 2 3 4 12 13 14 5 11 16 15 6 10
  • 动态链接库dll详解

    动态链接库概述 DLL就是整个Windows操作系统的基础 动态链接库不能直接运行 xff0c 也不能接收消息他们是一些独立的文件 Windows API中的所有函数都包含在DLL中 其中有三个最重要的DLL kernel32 dll xf