1、DLL 动态链接库基础知识

所谓的库,就是程序员把一些常用的函数写在一个文件内,以方便其它程序调用。库分为两种,静态链接库和动态链接库。
静态链接库,就是在链接时把库文件链到自己的程序内,也就是整合成一个exe文件。但是这样有一个缺点,就是我们把所有内容都加在这个程序中,使得程序体积增大。如果每个应用程序都用静态链接库的话,那么每个程序的体积都将很大。这是非常浪费硬盘空间的。

动态链接库就可以解决这样的问题。他是在程序运行的时候动态地把这个库加载到应用程序的地址空间中,而无需在程序链接时加入程序中,这样程序和dll文件实际上是分离的,程序的体积不会增大,同时又可以使用dll中的函数。

一、编写一个简单的DLL。

我们用VC新建一个DLL项目,名字就叫dll吧。我们只打算在其中写一个简单的函数,如下

//dll.cpp中
#include "dll.h"
int add(int a,int b)
{
	return a+b;
}

这是一个简单的求和函数。我们想要在应用程序中使用这个函数,必须在dll中将它声明为导出函数。

//dll.h中
extern "C" __declspec(dllexport) int add(int,int);

__declspec(dllexport)表示了后面的函数是要导出的。

如果dll程序是用c++写的,c++编译器会对函数名和变量名进行改编。而如果一个exe文件是用c写的,编译器不会对函数名改编(事实上也会也一些改变,但是和c++的改变方式不同)。这样,在链接时,连接器会发现exe文件引用了一个不存在的符号。由此出错。加上extern “C”,则告诉编译器按照c的方式编译函数名。

我们还可以用另一中方式来导出函数,就是使用def文件。

当用def文件导出函数时,函数的名字就不会发生任何改变。def文件的使用非常简单,只要在def文件的导出段中写上函数名就可以了。就像这样:

; dll.def  在def文件中,注释用分号“;”标志
EXPORTS
    ; 此处可以是显式导出
	add

就是这么简单!
现在我们的dll文件已经写好了,我们可以直接生成这个dll了。

我们发现在生成的文件中,除了一个dll.dll之外,还有一个dll.lib文件。

只要我们在dll中导出了一个函数、变量或者类,生成dll的同时会自动生成一个lib文件。lib文件里面就包含了导出的符号。

二、在应用程序中使用DLL。

应用程序中使用DLL有两种方法,隐式链接和显示链接。我这里只讲一下隐式链接。

我们新建一个exe的项目,首先我们需要把上面生成的dll.dll和dll.lib两个文件复制到应用程序的工程目录内(也就是主函数所在的文件)

第一步我们需要告诉链接器,这个应用程序的运行依赖一个dll文件,我们在项目属性中,找到配置属性->链接器->输入,在附加依赖项中编辑,输入dll.lib.

或者 我们可以用一行代码来完成上述动作:

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

第二步我们必须声明这个函数是从外部文件导入的,有两种方式可以声明,第一种比较简单,直接用extern关键字:

extern "C" extern int add(int,int);	//推荐这种方法,下面的方法有时候编译不过去

当然这种方法不正规,我们不推荐。推荐使用第二种方法:

__declspec(dllimport) int add(int,int);

__declspec(dllimport)直接告诉编译器,add函数是从一个dll文件中导入的。

好了,以上工作都做完了,我们就可以在程序中正常使用add函数了。

三、DllMain函数

dll中也可以有一个入口点函数,为DllMain函数。注意这个函数的大小写,不能随意改动。在DllMain函数中,我们可以做一些初始化工作。

该函数原型如下:

BOOL WINAPI DllMain(
					HINSTANCE hInstDll,	//该dll的实例句柄
					DWORD fdwReason,		//DllMain被调用的原因
					PVOID fImpLoad			//如果dll是显示载入的,该值为0;否则,该值不为0
)

那么这个DllMain函数在什么时候被调用呢,我们来看一个例子:

BOOL WINAPI DllMain(HINSTANCE hInstDll,DWORD fdwReason,PVOID fImpLoad)
{
	switch(fdwReason)
	{
		case DLL_PROCESS_ATTACH:	//第一次将一个dll映射到进程的地址空间时,调用DllMain函数。
                        //做任何初始化的事情
			break;
		case DLL_THREAD_ATTACH:	//创建一个新线程时,系统会检查该进程地址空间的所有dll,并用该通知调用所有dll的DllMain。如果系统将一个dll映射到进程地址空间时,进程已有多个线程在运行,系统不会让任何线程用这个通知调用DllMain。只有在创建一个新线程时,dll已经被映射到进程地址空间,才用该通知调用DllMain.当然,主线程也不会用该通知调用DllMain,进程创建时是第一个通知。
			break;
		case DLL_THREAD_DETACH:	//系统将一个dll从进程的地址空间撤销时,调用DllMain。
 
			break;
		case DLL_PROCESS_DETACH:	//线程即将终止时
			break;
	}
	return TRUE;	//该返回值仅供第一个case使用。比如初始化成功,返回TRUE,失败返回FALSE。如果返回失败,在隐式链接dll的情况下,应用程序会启动失败。其它case下忽略返回值。
}

四、例子

静态链接库

*.cpp

#include <iostream>

#pragma comment(lib,"erya")

extern "C" extern void ccc();

int main()
{
	ccc();

	system("pause");
	return 0;
}

*.dll

//这样就是声明了一个导出函数
extern "C" __declspec(dllexport) void ccc();

//用作导出的函数
void ccc()
{
    std::cout << "123" << std::endl;
}

动态调用

#include <iostream>
#include <Windows.h>

//定义一个函数指针
typedef void (*pCCC)();


int main()
{
	//获取dll的句柄
	HMODULE hModule = LoadLibrary(L"erya.dll");

	//从指定的dll中获取函数的地址
	pCCC pCcc = (pCCC)GetProcAddress(hModule, "ccc");
	
	pCcc();

	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值