前言
本篇博客主要是记录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#可以直接调用;