Windows编程dll基本知识点

前言

本篇博客主要是记录windows系统下dll开发的相关基本知识点,并使用相关分析工具分析,有利于初学者学习,更是为开发者查缺补漏;

使用dumpbin查看dll,lib,exe相关信息

VS编译器提供了查看链接库相关的工具,安装后的VS编译器的安装目录内可以找到dumpbin.exe,也可以在工具里直接打开dumpbin
在这里插入图片描述

打开VS2015 x86 x64兼容工具命令提示符,输入dumpbin指令,/exports是显示所有函数的指令,后面是要查看的dll文件

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC>dumpbin /exports G:\bin\CustomWidget.dll

执行之后便可以看到dll内相关函数信息,红色框内便是函数名
在这里插入图片描述
也可以用dumpbin查看lib和exe信息,只不过指令不是/exports,具体的指令有:

查看a.dll库中包含哪些函数,可以使用:dumpbin /exports a.dll 
查看b.exe中加载了哪些动态库,可以使用:dumpbin /imports b.exe
查看c.lib中包含哪些函数,可以使用:dumpbin /all /rawdata:none c.lib 
查看d.obj中包含哪些函数,可以使用:dumpbin /all /rawdata:none d.obj 

dll输出调试信息与debugview调试

在编写dll程序的过程中,不像exe可以输出信息到控制台,可以将dll中信息输出到调试信息窗口内;只需要调用OutputDebugString函数;别人调用此函数时,调试信息也会显示在调试窗口内;

int __stdcall fun1(int a, int b)
{OutputDebugString(L"Camera::Run Fun");return a + b;
}

调用dll时,可以在调试窗口内看到:
在这里插入图片描述

dll发布后,三方调用时,如何查看dll内输出的信息,微软提供了debugview软件,可以跟踪dll内信息;
在这里插入图片描述
在这里插入图片描述
点击漏斗型按钮,打开过滤器设置,在Include中输入Camera::,在Exclude中输入WAIT_TIMEOUT;这样就只显示带字符串“TRACE”的debug信息,不显示带“WAIT_TIMEOUT”的调试信息;不设置过滤器的话,会捕获所有dll的调试信息,我们只关注所监控的信息,在编写dll时,设置好唯一的标识符字符;
在这里插入图片描述

在这里插入图片描述

exe是如何找到dll的?

.dll 是动态链接库文件,里面存储着函数和数据;
.lib是静态数据连接库文件,存储着函数名和文件位置;
也就是说在执行程序时,exe文件可通过lib文件找到dll文件,并执行在程序中调用的函数。
Windows在查找dll文件会按照以下几种方式顺序查找:
1.exe文件所在的目录下;
2.进程当前的工作目录;
3.Windows系统目录;
4.Windows目录;
5.环境变量Path下的一系列目录

C语言编译成dll,lib和C++编译成dll,lib的区别

C语言编译动态链接库

首先使用VS建立一个win32的dll空项目,再添加SelfDll.h文件和SelfDll.c文件(编译器会根据.c文件格式默认为C的编译器)
SelfDll.h文件代码如下:

/**
@file       SelfDll.h
@brief      C语言导出dll
*/
#pragma once 
__declspec(dllexport) int fun1(int a,int b);
__declspec(dllexport) double fun2(double a, double b);

这里注意的是

_declspec(dllexport) int fun1(int a,int b);    //加上_declspec(dllexport)是导出lib文件,如果不加上,则只有dll文件
int fun1(int a,int b);     //只导出dll文件

SelfDll.c文件代码如下:

#include "SelfDll.h"
int fun1(int a, int b) 
{return a + b;
}double fun2(double a, double b)
{return a + b;
}

最终编译出来的文件如图
在这里插入图片描述
使用dumpbin查看c编译的dll
在这里插入图片描述

C++编译动态链接库

VS建立一个win32的dll空项目,添加SelfDll.h文件和SelfDll.cpp文件(注意不是.c文件)
SelfDll.h文件代码如下:

/**
@file       SelfDll.h
@brief      C++导出dll
*/
#pragma once 
__declspec(dllexport) int fun1(int a,int b);
__declspec(dllexport) double fun2(double a, double b);

SelfDll.cpp文件代码如下:

#include "SelfDll.h"
int fun1(int a, int b) 
{return a + b;
}double fun2(double a, double b)
{return a + b;
}

编译出的dll,lib与C语言相同名称,使用dumpbin查看c++编译的dll
在这里插入图片描述

C++如何使用C语言编译的链接库

从上面的图可以看出,虽然函数声明与定义一样,但是编译出来的结果不一样。
一个是_fun1,另一个是?fun1@@YAHHH@Z,可以看出存在差异。
如果C++程序链接C编译的动态链接库,则会报错:
在这里插入图片描述
如果C++可以使用C编译的链接库,需要在原先C的头文件内增加extern "C"关键字,这样编译器便会将此处函数按照C链接编译;
C++项目中重新修改原先C的头文件

/**
@file       SelfDll.h
@brief      C语言导出dll
*/
#pragma once extern "C"
{__declspec(dllexport) int fun1(int a, int b);__declspec(dllexport) double fun2(double a, double b);
}

当C++编译到C这段代码时,会将这段按照C编译;

C++导出类的动态链接库

同样的,需要增加__declspec(dllexport),在.h文件内代码如下:

__cdecl与__stdcall的区别

Visual Studio默认是__cdecl,如果使用这个关键字,以后栈的销毁是调用者来做,VS自己编译的dll,再用VS调用会自动销毁栈,但是给其他编译器使用时,就会出问题。
因此标准调用,最好是自己编写__stdcall,其他编译器和其他语言,比如C#,VB都会识别出这个dll,并自动清理栈;

__cdecl是VS默认加上的,因此VS不需要添加写,__stdcall这个关键字需要在函数声明和定义的时候都带上,不然编译器会认为是两个函数;

分别定义一个带_stdcall的dll文件和不带_stdcall的dll文件,用dumpbin查看下区别:
带_stdcall的.h源码如下:

/**
@file       SelfDll.h
@brief      __cdecl与__stdcall的区别
*/#pragma once extern "C" {
__declspec(dllexport) int __stdcall fun1(int a, int b);
__declspec(dllexport) double __stdcall fun2(double a, double b);
}

带_stdcall的.cpp源码如下:

#include "SelfDll.h"int __stdcall fun1(int a, int b)
{return a + b;
}double __stdcall fun2(double a, double b)
{return a + b;
}

用dumpbin查看下区别
带__stdcall的
在这里插入图片描述
不带__stdcall的
在这里插入图片描述
带__stdcall的函数_fun1@8,这里8代表8个字节,源代码fun1的形参是两个int,占8个字节,带__stdcall的函数编译出的dll附带形参栈信息,因此可以被其他语言或者编译器调用。

综上所述,对于dll开发者而言,应该考虑到二次开发者是使用C++开发还是C开发,最好添加一个判断C++的宏,因此标准的dll文件一般都有这样开头的宏定义

#ifndef SDK_API
#if (defined(_WIN32) || defined(_WIN64))
#if  defined(SDK_API)
#define SDK_API __declspec(dllexport)
#else
#define SDK_API __declspec(dllimport)
#endif
#else
#ifndef __stdcall
#define __stdcall
#endif
#ifndef SDK_API
#define SDK_API
#endif
#endif
#endif#ifdef __cplusplusextern "C"  // 使用extern "C"{
#endif// 设置业务消息回调接口SDK_API void __stdcall SetMsgCallBack(int a);// 初始化SDK库SDK_API void __stdcall InitNetSDK();// 登录SDK_API void __stdcall LoginServer(const int a);#ifdef __cplusplus}
#endif

对于二次开发者来说,如果加载dll进项目中后发现编译错误。在发现配置文件没有出错的情况下,很可能开发者忘记了添加extern “C”,这时候二次开发者就需要手动在原来的.h文件内添加extern "C"了;

def文件规范导出符号

如果为了其他语言或者编译器能够使用dll,我们就需要在每一个函数签名加上"extern “C” _declspec(dllexport)"这一长串声明。如果需要导出的函数较多则显得非常繁琐,也非常难看。为了简化这一过程,VS引入了 def文件方便我们操作。
头文件便可以简化为:

/**
@file       SelfDll.h
@brief      使用def文件导出dll
*/#pragma once int __stdcall fun1(int a, int b);
double __stdcall fun2(double a, double b);

在项目中加上一个.def文件,内容如图所示。
在这里插入图片描述

在VS编译器内添加上:
在这里插入图片描述

模块定义文件是用来描述 dll 文件的文本格式的文件,其格式如下:

LIBRARY libdll.dll      ;dll 文件的文件名
DESCRIPTION "描述信息"  ;描述信息,此行可以不要
EXPORTS
lib_add @1        ;函数描述
lib_sub @2        ;函数描述

第一行:在 LIBRARY 后面填 dll 文件的名字,分号后面是注释。
第二行:DESCRIPTION,描述信息,此行可以忽略
第三行:EXPORTS
第四行开始,是 dll 文件中函数的描述,可以使用 dumpbin /EXPORTS libdll.dll 命令查看,(其中,libdll.dll 是目标 dll 的文件路径)
注意,def只生成dll文件,是没有lib文件的,如果想要生成对应的lib文件,需要在dumpbin那里使用以下命令:

lib /out:F:\sqlite3.lib /MACHINE:X64 /def:F:\sqlite3.def

F:\sqlite3.lib是要生成的lib文件
F:\sqlite3.def是项目生成dll的def文件

__declspec(dllimport)和__declspec(dllexport)的区别

这篇博主详细分析了之间的区别,结论就是:
dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。
链接如下:
dllimport与dllexport作用与区别
因此,很多三方库的.h文件内都是这样定义

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif

C#使用C++导出的dll

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;  //引入三方库namespace LearnDll
{class Program{[DllImport("E:/14_learnC++/LearnDll/SelfDll(C++)/SelfDll/Debug/SelfDll.dll", CharSet = CharSet.Ansi)]//声明函数static extern int fun1(int a, int b);static void Main(string[] args){int a = 1;int b = 2;int c = fun1(a,b);Console.WriteLine("结果是:"+ c );Console.ReadKey();}}
}

C++编译的dll库通过def文件导出,C#可以直接调用;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/81336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c++GDAL】IHS融合

【c&GDAL】IHS融合 基于IHS变换融合,实现多光谱和全色影像之间的融合。IHS分别指亮度(I)、色度(H)、饱和度(S)。IHS变换融合基于亮度I进行变换,色度和饱和度空间保持不变。 IHS融合步骤: (1)将多光谱RGB影像变换到…

网络安全:保护你的系统

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

地牢大师问题(bfs提高训练 + 免去边界处理的特殊方法)

地牢大师问题 文章目录 地牢大师问题前言题目描述题目分析输入处理移动方式【和二维的对比】边界判断问题的解决 代码总结 前言 在之前的博客里面,我们介绍了bfs 基础算法的模版和应用,这里我们再挑战一下自己,尝试一个更高水平的题目,加深一…

Docker部署单点Elasticsearch与Kibana

一 、 创建网络 因为需要部署kibana容器,因此需要让es和kibana容器互联。这里创建一个网络: docker network create es-net # 创建一个网络名称为:es-net 二 、拉取并加载镜像 方式一 docker pull elasticsearch:7.12.1 版本为elasticsearch的7…

列属性与数据完整性

1.2 数据类型——值类型 1.2.1 整型 类型字节范围tinyint1-128~127smallint2-32768~32767mediumint3-8388608~8388607int4-231~231-1bigint8-263~263-1 1、无符号整数(unsigned):无符号数没有负数,正数部分是有符号的两倍。 例…

Linux驱动之INPUT子系统框架

目录 一、input 子系统简介 二、input 驱动编写流程 1、注册 input_dev 2、上报输入事件 三、input_event 结构体 按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设…

Go语言开发环境搭建指南:快速上手构建高效的Go开发环境

Go 官网:https://go.dev/dl/ Go 语言中文网:https://studygolang.com/dl 下载 Go 的语言包 进入官方网站 Go 官网 或 Go 语言中文网: 选择下载对应操作系统的安装包: 等待下载完成: 安装 Go 的语言包 双击运行上…

udp的简单整理

最近思考udp处理的一些细节,根据公开课,反复思考,终于有所理解,做整理备用。 0:简单汇总 1:udp是基于报文传输的,接收方收取数据时要一次性读完。 2:借助udp进行发包,…

C++数据结构 -- 哈希表

目录 一、哈希概念二、 哈希冲突三、 哈希函数四、 减少哈希冲突常用的方法4.1 闭散列4.1.1 闭散列的开放定址法的增容4.1.2 闭散列的开放定址法的哈希结构的实现 4.3 开散列4.3.1 开散列概念4.3.2 插入元素4.3.2 删除元素4.3.3 开散列的哈希桶的增容4.3.4 开散列的哈希桶(拉链…

快速搭建SpringBoot3.x项目

快速搭建SpringBoot3.x项目 写在前面一、创建项目二、配置多环境三、连接数据库查询数据3.1 新建数据库mybatisdemo并且创建sys_user表3.2 创建实体类3.2 创建Mapper接口3.3 添加mybatis.xml文件3.4 新建service 接口及实现类3.5 创建Controller 四、封装统一结果返回4.1 定义 …

Attention is all you need 论文笔记

该论文引入Transformer,主要核心是自注意力机制,自注意力(Self-Attention)机制是一种可以考虑输入序列中所有位置信息的机制。 RNN介绍 引入RNN为了更好的处理序列信息,比如我 吃 苹果,前后的输入之间是有…

【问题记录】解决Git上传文件到GitHub时收到 “GH001: Large files detected” 错误信息!

环境 Windows 11 家庭中文版git version 2.41.0.windows.1GitHub 问题情况 在命令行中使用git上传pdf文件到GitHub服务器时,提示了如下警告信息: 原因是 GitHub 有一个文件大小限制,通常为 100 MB。 如果尝试上传大于此限制的文件&#xff0c…

Long类型雪花算法ID返回前端后三位精度缺失问题解决

目录 一、问题描述二、问题复现1.Maven依赖2.application.yml 配置3.DemoController.java4.snowflakePage.html 页面5.DemoControllerAdvice.java 监听6.问题复现 三、原因分析四、问题解决方案一方案二 一、问题描述 Java 后端使用雪花算法生成 Long 类型的主键 ID&#xff0…

快速学会搭建微信小程序的基础架构

(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 基础架构 构建界面 引入 uni-ui 组件库 组件自动引入 配置TS类型 状态管理 持久化 数据交互 请…

最小二乘法

Least Square Method 1、相关的矩阵公式2、线性回归3、最小二乘法3.1、损失函数(Loss Function)3.2、多维空间的损失函数3.3、解析法求解3.4、梯度下降法求解 1、相关的矩阵公式 P r e c o n d i t i o n : ξ ∈ R n , A ∈ R n ∗ n i : σ A ξ σ ξ…

leetcode 332. Reconstruct Itinerary(重构行程)

有一些票tickets, tickets[ i ] [from, to], 每个出发到达城市名字都是3个大写英文字母, 同一个出发城市时,优先去字母顺序较小的到达城市。 必须先从“JFK”出发。 每个ticket必须用且只用一次,所有ticket一定会形成至少一个有效的行程&…

【JAVA-Day21】序列化和反序列化,学会Java的编解码方法

标题序列化和反序列化,学会Java的编解码方法 序列化和反序列化,学会Java的编解码方法摘要引言一、什么是序列化1.1 序列化的过程 二、什么是反序列化2.1 反序列化的过程 三、为什么要进行序列化和反序列化3.1 主要目的3.2 应用场景 四、总结参考资料 博主…

Springboot 实践(18)Nacos配置中心参数自动刷新测试

前文讲解了Nacos 2.2.3配置中心的服务端的下载安装,和springboot整合nacos的客户端。Springboot整合nacos关键在于使用的jar版本要匹配,文中使用版本如下: ☆ springboot版本: 2.1.5.RELEASE ☆ spring cloud版本 Greenwich.RELEASE ☆ sp…

辅助驾驶功能开发-功能规范篇(21)-4-XP行泊一体方案功能规范

XPilot Parking 自动泊车系统 • 超级自动泊车辅助(Super AutoParking Assist)、语音控制泊车辅助(Autoparking with Speech) - 产品定义 超级自动泊车辅助是⼀个增强的自动泊车辅助系统。在超级自动泊车辅助系统中,识别车位将会变得实时可见, 并且不可泊入的⻋位也将…

如何在 Excel 中计算日期之间的天数

计算两个日期之间的天数是 Excel中的常见操作。无论您是规划项目时间表、跟踪时间还是分析一段时间内的趋势,了解如何在 Excel 中查找日期之间的天数都可以提供强大的日期计算功能。 幸运的是,Excel 提供了多种简单的方法来获取两个日期之间的天数。继续…