Lib和DLL调用与实现

DLL:Dynamic Link Library 动态链接库 是一个被其他应用程序调用的程序模块,其中封装了可以被调用的资源或函数。DLL 文件属于可执行文件,它符合Windows系统的PE文件格式,不过它是依附于EXE文件创建的的进程来执行的,不能单独运行。

LIB与DLL

静态链接库和动态链接库都是共享代码的方式。使用静态链接库时,LIB中的指令最终都被包含到生成的exe中。动态链接库可以在工程中动态的引用和卸载。另外静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。

静态库:在链接步骤中,连接器将库文件取得所需的代码,复制到生成的可执行文件中,这种库叫做静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。即静态库中的指令全部被直接包含在最终生成的exe文件中。在vs中新建生成静态库的工程,编译生成成功后,只产生一个.lib文件。

动态库:动态库链接是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行的文件,动态库提供一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL中包含一个或者多个已经被编译,链接并使用它们的进程分开存储的函数,在vs中新建生成动态库的工程,编译成功后,产生一个.lib和一个.dll文件。

静态库的lib:该lib包含函数的代码本身(包括函数的索引,也包括实现),在编译 的时候将代码加入程序当中

动态库的lib:该lib包含了函数所在的DLL文件和文件中函数位置的信息,函数实现代码由运行的时候加载在进程空间中DLL提供,总之,lib是编译的时候用到的,如果完成源代码的编译,只需要lib,如果要使得动态库的程序运行起来,只需要dll。

静态库特点:
静态库对函数库的链接是放在编译期完成的,程序在运行的时候与函数库再无瓜葛,移植方便。
缺点:浪费空间与资源,因为所有相关的目标文件与牵扯到的函数库会被合成一个可执行的文件。
是静态库对程序的更新,部署和发布会带来麻烦,如果静态库更新,所有使用它的应用程序程序都需要重新编译,发布给用户。

动态库特点:
动态库把一些库函数的链接载入推迟到程序运行的时期,可以实现进程之间的资源共享。
将实现进程之间的资源共享,将一些程序升级变得简单。甚至可以真正做到链接载入完全由程序代码中控制(显示调用)

何时调用DllMain

跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。
系统是在什么时候调用DllMain函数的呢?静态链接时,或动态链接时调用LoadLibrary和FreeLibrary都会调用DllMain函数。DllMain的第三个参数fdwReason指明了系统调用Dll的原因,该参数有4个可能值: DLL_PROCESS_ATTACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH。

其中,DLL_PROCESS_ATTACH是在一个DLL首次被映射到进程的地址空间时,系统调用它的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH。这只有在首次映射时发生。如果一个线程后来为已经映射进来的DLL调用LoadLibrary或LoadLibraryEx,操作系统只会增加DLL的计数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

而DLL_PROCESS_DETACH是在DLL被从进程的地址空间解除映射时,系统调用它的DllMain函数,传递的fdwReason值为DLL_PROCESS_DETACH。我们需要注意的是,当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明初始化不成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain。因此,必须确保没有清理那些没有成功初始化的东西。

DLL_THREAD_ATTACH:当进程中创建一个线程时,系统察看当前映射到进程的地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用所有的这些DLL的DllMain函数。该通知告诉所有的DLL去执行线程级的初始化。注意,当映射一个新的DLL时,进程中已有的几个线程在运行,系统不会为已经运行的线程用值DLL_THREAD_ATTACH调用DLL的DllMain函数。

而DLL_THREAD_DETACH,如果线程调用ExitThread来终结(如果让线程函数返回而不是调用ExitThread,系统会自动调用ExitThread),系统察看当前映射到进程空间的所有DLL文件映像,并用值DLL_THREAD_DETACH来调用所有的DLL的DllMain函数。该通知告诉所有的DLL去执行线程级的清理工作。
这里,我们需要注意的是,如果线程的终结是因为系统中的一个线程调用了TerminateThread,系统就不会再使用DLL_THREAD_DETACH来调用DLL和DllMain函数。这与TerminateProcess一样,不再万不得已时,不要使用。

在项目中需要这样一个场景:
当一个dll被加载时候,在这个dll中需要加载另一个dll。
尝试在DLL_PROCESS_ATTACH调用LoadLibrary发生程序卡死。
得出结论:“不能在DllMain中调用LoadLibrary,FreeLibrary以及Shell,ODBC,COM,RPC和socket函数与CreateThread”

因为DllMain在这些场合会被重新调用(例外是LOAD_LIBRARY_AS_DATAFILE和DisableThreadLibraryCalls这样跳过DllMain的场合)。为了避免造成死循环问题,操作系统给调用DllMain的代码加锁以确保DllMain会不被干涉地执行完毕而不会重入你自己这个DllMain。另外操作系统也在获取加载的DLL列表的时候加锁(比如LoadLibrary、FreeLibrary或者创建线程都会枚举当前进程的所有DLL来调用这些DLL的DllMain)来等待你的DllMain结束以避免获得不完整的DLL列表。但是这需要程序员的合作,不在DllMain中编写可能造成重新获取DLL加载锁的代码。
程序卡死不算什么严重后果,而且也很容易重现(加载其他的DLL,创建线程等等,在全局堆上分配内存都可能造成这个问题,如果内存分配器的线程同步锁没写好的话)。如果你在这里创建一个全局的钩子,这甚至会造成系统卡死。
这个问题无法调试,因为调试器需要在被调试的进程创建一个远程线程之后才能中断。最好的办法是不要在DllMain中放那些代码。
简单的说,DllMain的调用前后系统加了一把loader-lock,导致很多API都不能调用,在DllMain里做的事情越少越好,否则容易死锁。解决你的问题,可以把dll 2静态链接到dll1,只要你在dll 1中随便什么函数调用一下dll2里导出的函数就行。或者你也可以用微软的Detours库里的setdll工具,让dll1强行依赖dll2,这样系统加载dll1时就会加载dll2。

DLL接口定义:

DLL中导出函数的声明有两种方式:
一种为在函数声明中加上__declspec(dllexport);另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

#pragma once
#ifndef DLL_IMPLEMENT
#define DLL_API _declspec(dllexport)
#else
#define DLL_API _declspec(dllexport)
#endif
BOOL DLL_API StartHookEv(HWND hWnd);
VOID DLL_API StopHookEv(void) ;

LIBRARY mousehook_dll
EXPORTS
StartHookEv
StopHookEv
这里对模块定义做一些说明(最后一个文件),LIBRARY后跟的是项目名,EXPORTS后是这个dll中需要导出的函数名。

说明:
.def文件的规则为:
  (1)LIBRARY语句说明.def文件相应的DLL;
  (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
  (3).def 文件中的注释由每个注释行开始处的分号指定,且注释不能与语句共享一行。

动态库中的全局变量:

动态库中的全局变量,在被其他进程使用时,会拷贝一份,所以多个进程使用同一个动态库中的全局变量也不会相互影响。也不会改变动态库中该变量的值。类似于fork的COW技术。
静态变量将最终出现在dll文件的.bss或.data节中。链接到dll的可执行文件可能甚至不知道它存在。当exe加载dll时,系统为其设置dll的数据部分,然后调用dllmain()。这就是dlls静态值出现并初始化的时候。

静态库的生成与调用:

1、我们先新建一个项目,选择win32项目
2、在接下来的应用程序向导中选择静态库并去掉预编译头的勾。
3、创建头文件(.h)以及源文件(.cpp),它们的格式如下:

//MyLib.h
#pragma once
#ifndef _STATIC_LIB_H_
#define _STATIC_LIB_H_
 
#include <iostream>
 
namespace STATIC_LIB
{
	void PrintHello();  //测试函数
}
 
#endif

//MyLib.cpp
#include "MyLib.h"
 
void STATIC_LIB::PrintHello()
{
	std::cout << "Hello world!" << std::endl;
}

4、然后,我们选择release,生成解决方案
5、随后,我们可以在自定的项目路径下的release文件夹下看到生成的lib文件。

静态库的第一种使用方法
1、我们先创建个普通的win32控制台程序,选择空项目即可。
2、然后我们把上面创建lib时的头文件包含到新创建的控制台程序中:
3、然后在源程序中写如下格式的代码调用即可:

#include <iostream>
#include "MyLib.h"
 
#pragma comment(lib,"MyLib.lib")
 
int main()
{
	STATIC_LIB::PrintHello();
 
	system("pause");
	return 0;
}

静态库的第二种使用方法
1、我们创建一个include文件夹和lib文件夹用来存放头文件和lib文件:
2、项目名那右击,选择属性,在弹出的属性页窗口中选择VC++目录,然后添加进这两个文件夹
3、将先前创建lib时的头文件和lib文件分别复制到这两个文件夹中:
4、接着,再在属性页面中选择链接器–输入–附加依赖项中添加我们的lib
5、准备工作完成,在我们调用的程序中使用如下格式的代码即可调用:

#include <iostream>
#include "MyLib.h"
 
int main()
{
	STATIC_LIB::PrintHello();
 
	system("pause");
	return 0;
}

DLL的生成与调用方式:
1. IDE新建工程MyDll,选择空项目,创建完毕 右击项目 -> 属性 -> 常规 -> 配置类型 选择 动态库.dll
各个文件中写相应的代码实现:

//MyDll.h
#pragma once
#ifndef _DLL_TEST_H_
#define _DLL_TEST_H_
 
#include <iostream>
 
namespace DLL_TEST
{
	void PrintHello();
}
 
#endif // !_DLL_TEST_H_

//MyDll.cpp
#include "MyDll.h"
 
void DLL_TEST::PrintHello()
{
	std::cout << "Hello world!" << std::endl;
}

//MyDll.def
LIBRARY MyDll
 
EXPORTS 
PrintHello

这里对模块定义做一些说明(最后一个文件),LIBRARY后跟的是项目名,EXPORTS后是这个dll中需要导出的函数名。

隐式调用
隐式加载就是在程序编译的时候就将dll编译到可执行文件中。这种加载方式调用方便,程序发布的时候可以不用讲dll带着.缺点是,这样编译出来后,程序会很大。
新建一个工程,将所需的*.dll ,* .lib ,* .h放到工程目录下

//隐式加载dll
#pragma comment(lib, “dllDemo.lib”)

或者在项目->属性->链接器->输入中添加附加依赖项 dllDemo.lib。
编译运行,该DLL会直接执行其中的可执行代码

显式调用
DLL显式加载时指在程序运行过程中,需要用到dll里的函数时,再动态加载dll到内存中,这种加载方式因为是在程序运行后再加载的,所以可以让程序启动更快,而且dll的维护更容易,使得程序如果需要更新,很多时候直接更新dll,而不用重新安装程序.只是这种加载方式,函数调用稍微复杂一点。
需要注意的时这种方式下调用相比较为复杂。比如之前文章中的调用带界面的DLL时

    //1.加载动态库
HINSTANCE  m_hDll = LoadLibrary(_T("MFCDLL1.dll"));

//2.根据函数名获取函数地址
typedef IHpDllWin* (*hpDllFun)();
hpDllFun pShowDlg = (hpDllFun)GetProcAddress(m_hDll, "ShowDialog");


    //3.获取导出类对象指针,调用导出函数
IHpDllWin* m_hpwin = pShowDlg();

    //4.卸载dll
    FreeLibrary(hDll);

例子:

#pragma once
extern "C" __declspec(dllexport) int add(int a, int b);

#include "Calc.h"

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

方式一:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

#pragma comment(lib,"DLLTest1.lib")

extern "C" int add(int a, int b);

// 静态调用DLL库
void StaticUse()
{
	int sum = add(10, 20);
	printf("静态调用,sum = %d\n", sum);
}

方式二:

// 动态调用DLL库
void DynamicUse()
{
    // 运行时加载DLL库
	HMODULE module = LoadLibrary("DLLTest1.dll");
	if (module == NULL)
	{
		printf("加载DLLTest1.dll动态库失败\n");
		return;
	}
	typedef int(*AddFunc)(int, int); // 定义函数指针类型
	AddFunc add; 
    // 导出函数地址
	add = (AddFunc)GetProcAddress(module, "add");

	int sum  = add(100, 200);
	printf("动态调用,sum = %d\n",sum);
}

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

#pragma comment(lib,"DLLTest1.lib")

extern "C" int add(int a, int b);

// 静态调用DLL库
void StaticUse()
{
	int sum = add(10, 20);
	printf("静态调用,sum = %d\n", sum);
}

// 动态调用DLL库
void DynamicUse()
{
	HMODULE module = LoadLibrary("DLLTest1.dll");
	if (module == NULL)
	{
		printf("加载DLLTest1.dll动态库失败\n");
		return;
	}
	typedef int(*AddFunc)(int, int); // 定义函数指针类型
	AddFunc add;
	add = (AddFunc)GetProcAddress(module, "add");

	int sum = add(100, 200);
	printf("动态调用,sum = %d\n", sum);
}

int main(char argc, char* argv[])
{
	StaticUse();
	DynamicUse();
	system("pause");
	return 0;
}

转载与参考:https://blog.csdn.net/wzxq123/article/details/51406832
https://blog.csdn.net/qq_29542611/article/details/86618902
https://blog.csdn.net/m0_37251750/article/details/81280016
https://blog.csdn.net/benkaoya/article/details/2504781
https://blog.csdn.net/googler_offer/article/details/88526962
https://blog.csdn.net/sj2050/article/details/81700183

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值