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 __cplusplus
    extern "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#可以直接调用;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

手写不期而遇

感谢你的打赏,也欢迎一起学习

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值