DLL教程 - 如何编写动态链接库

本dll教程是自己在实际编程中总结的,作为笔记记录,对应的代码工程下载地址Dll编程demo
若没有积分,可以从github上clone仓库 :git@github.com:FFFlyFish/LibraryTest.git

一、 概述

DLL(Dynamic Linkable Library)它提供一些可以直接使用的变量,类和函数。在经历了“无库—静态链接库—动态链接库”的历程后,dll使用十分广泛。本文主要介绍dll,顺便介绍静态库(.lib)的一些知识。

1、静态链接库和动态链接库的异同点

静态链接库和动态链接库都是共享代码,如果采用静态链链接库(.lib),lib中的指令最终都会编译到链接该静态库的exe(或dll)文件中,发布软件时,只需要发布exe(或dll)文件,不需要.lib文件。但是若使用动态链接库(. dll),dll中的指令不会编译到exe文件中,而是在exe文件执行期间,动态的加载和卸载独立的dll文件,需要和exe文件一起发布。
静态链接库和动态链接库另一个区别是静态链接库不能再包含其他动态链接库或静态链接库,而动态链接库不受此限制,动态链接库中可以再包含其他的动态链接库和静态链接库。

2、Dll的一些概念

(1)只要遵循约定的dll接口规范和调用方式,用各种语言编写的dll可以相互调用。
(2)dll的分类有三种,Non-MFC DLL(非MFC DLL),MFC Regular DLL(MFC规则DLL)和MFC Extension DLL(MFC拓展DLL)。非MFC DLL不采用MFC类库规则,其导出函数为标准C接口,能被非MFC或MFC编写的应用程序调用;MFC规则DLL包含一个CWinApp的类,但其无消息循环;MFC拓展DLL采用MFC的动态链接库版本创建,它只能被用MFC类库编写的应用程序调用。

二、 静态链接库

在介绍动态链接库前先要介绍静态链接库的知识,静态链接库的后缀是.lib。下面的一个例程将介绍如何生成.lib文件和如何调用.Lib

1、如何生成一个.lib文件

1)、建立一个空解决方案,方案名称为StaticLibrary。
这里写图片描述
2)、添加一个win32项目,类型为Static library,工程名称为StaticLib,空项目。
选择win32 project项目类型
这里写图片描述
选择Static library, 取消Precompiled header
这里写图片描述
3)、在该项目中添加lib.h和Lib.cpp文件,两个文件代码如下图所示。
这里写图片描述
lib.h文件代码如下:

#ifndef __LIB_H__
#define __LIB_H__

int add(int a,int b);

#endif

Lib.cpp文件中提供一个函数,实现两个整数的相加,返回两数的和。代码如下:

#include "lib.h"

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

4)、库工程不能单独运行,需要右键点击生成
这里写图片描述
生成成功后,将在解决方案目录的debug文件夹中生成一个StaticLib.lib文件,该文件就是静态库文件。
这里写图片描述

2、如何调用.lib文件

1)、在解决方案里再添加一个项目,项目名称为StaticLibCall,类型为Win32,控制台程序,选择空项目。
这里写图片描述
选择Console application 和Empty project
这里写图片描述
2)、在项目源文件文件中添加main.cpp文件。
这里写图片描述
main.cpp文件中添加如下代码:

#include <stdio.h>
#include "lib.h"
#pragma comment (lib,"StaticLib.lib")//指定与静态库一起连接

int main()
{
	printf("2+3=%d",add(2,3));
}

3)、将刚刚生成的StaticLib.lib文件和lib.h两个文件复制到该项目的目录下。(一般使用静态库时必须提供这两个文件,.h文件提供函数的预定义,而.lib提供函数的实现)
这里写图片描述
4)、生成的exe文件是可以独立运行的运行程序,lib文件中的函数实现被链接到exe文件中,lib不再需要了。运行结果如下
这里写图片描述

三、 动态链接库

本教程只介绍一种DLL(非MFC DLL)的创建与调用方法,本Dll实现的功能与第2节介绍的静态库实现的功能一样。

1、如何生成一个dll文件

1)、创建dll工程的步骤和上面介绍的建立lib的步骤一样,仅仅在选择类型时需要选择dll。建立工程后,添加DLib.h和DLib.cpp文件
这里写图片描述
在项目中添加DLib.h和DLib.cpp文件
这里写图片描述
2)、DLib.h和DLib.cpp代码如下所示:
DLib.h文件

#ifndef __DLIB_H__
#define __DLIB_H__

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


#endif

DLib.cpp文件

#include "Dlib.h"

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

分析该代码,该工程的.cpp文件中代码和第2节的.cpp中代码完全一样。而.h文件不一样,该工程的.h文件中对add函数添加extern “C” 是告诉编译器该函数采用C调用方式进行编译,而__declspec(impoet)是声明函数add为dll的导出函数,dll的函数分两种:
(a) DLL导出函数,可供调用dll的应用程序调用
(b) DLL内部函数,只能在DLL程序使用,调用DLL的应用程序无法调用
3)、右键–生成;生成成功后,将在debug文件夹中生成一个DynamicLib.dll文件,同时,在此路径下也生成DynamicLib.lib文件,该lib文件不同于第一节中的静态库文件,此lib文件只是dll文件中导出函数的声明和定位信息,并不包含函数的实现(而第一节中的静态库文件,包含了函数的实现),因此此lib文件只是在调用对应dll库的工程编译时使用,不需要随exe发布。
这里写图片描述

2、如何调用.dll文件

dll文件的调用方式有两种,一种是动态调用,一种是静态调用。
动态调用是由编程者调用系统API函数加载和卸载dll,程序员可以决定dll文件何时加载,何时卸载,加载哪个dll文件,将dll文件的使用权完全交给程序员。
静态调用是由编译系统完成对dll文件的加载和应用程序结束时完成对dll的卸载,当调用某dll的应用程序结束时,则windows系统对该dll的应用记录减1,直到使用该dll的所有应用程序都结束,即对该dll应用记录为0,操作系统会卸载该dll,静态调用方法简单,但不如动态调用适用。
(1)、动态调用dll
1)、新建控制台项目,添加main.cpp文件,将刚刚生成的dynamicLib.dll文件拷贝到项目目录下,main.cpp代码如下

#include "stdio.h"
#include "windows.h"

typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型

int main()
{
	HINSTANCE hDll;//DLL 句柄
	lpAddFun addFun;//函数指针
	hDll = LoadLibrary(L"DynamicLib.dll");//动态获取dll文件的路径
	if (hDll!=NULL)
	{
		addFun =(lpAddFun)GetProcAddress(hDll,"add");//根据函数名在dll文件中获取该函数的地址	
		if (addFun!=NULL)
		{
			int result =addFun(2,3);
			printf("2+3=%d",result);
		}

	FreeLibrary(hDll);
	}
	return 0;
}

运行结果如下:
这里写图片描述
main.cpp代码分析:
语句typedef int (*lpAddFun)(int ,int )定义了一个与add函数接收参数类型和返回值均相同的函数指针类型,随后在main函数中定义了lpAddFun的实例addFun;
在函数main中定义了一个DLL HISTANCE句柄实例hDll,通过Win32API函数LoadLibrary动态加载DLL模块并将DLL模块句柄赋给hDll;
在main函数中通过Win32API函数GetProcAddress得到所加载的DLL模块中函数add的地址并赋值给addFun,经由函数指针addFun进行了对该DLL中add函数的调用;
在完成对dll的调用后,在main函数中通过Win32API函数FreeLibrary释放已加载的DLL模块。

通过以上的分析可知:
(a) 动态调用只需要dll文件即可,不需要对应的.h头文件和.lib文件,一般情况下,只要有dll,就可以调用此dll中的导出函数。
(b) 在调用dll中的函数时,需要知道导出函数的函数签名,若拥有dll对应的头文件,可以参照头文件即可,若没有头文件,使用特定工具也可以得到dll中导出函数的函数签名
(c) DLL需要已某种特定的方式声明导出函数(或变量,类)
(d) 应用程序需要以特定的方式调用DLL的淡出函数(或变量,类)

(2)、静态调用dll
1)、新建一个工程,命名为DllStaticCall,添加main.cpp文件,并将刚刚生成的DynamicLib.dll和DynamicLib.lib两个文件拷贝到工程目录下。
这里写图片描述
这里写图片描述
main.cpp代码如下

#include "stdio.h"

//.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
#pragma comment(lib,"DynamicLib.lib")

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

int main()
{
	int result =add(2,3);
    printf("2+3=%d",result);
	scanf("%d");
	return 0;
}

从上述代码可以看出,静态调用需要完成两个动作:
a)#pragma comment(lib,“DynamicLib.lib”)是告诉编译器与该dll相对应的.lib文件所在的路径和文件名。
在生成dll文件时,链接器会自动为其生成一个对应的.lib文件,该文件包含了dll导出函数的符号名和序号(并没有实际的代码)。在应用程序中,.lib文件将作为dll的替代文件参与编译,编译完成后,.lib文件就不需要了。
b)extern “C” int __declspec(dllimport) add(int a,int b)是声明导入函数,这需要与dll工程中.h文件中的函数声明一致。
c)可以将dll工程中的头文件包含在工程中,这样上述代码中就不需要写extern “C” int __declspec(dllimport) add(int a,int b)声明了,但若没有提供对应的头文件,只有采用本文这种方式声明函数。

静态调用不需要使用Win32API函数来加载和卸载Dll以及获取Dll中导出函数的地址,这是因为当通过静态链接方式编译生成程序时,编译器会将.lib文件中导出函数的函数符号链接到生成的exe文件中,.lib文件中包含的与之对应的dll文件的文件名也被编译存储在exe文件内部,当应用程序运行过程中需要加载dll文件时,windows将根据这些信息查找并加载dll,然后通过符号名实现对dll函数的动态链接,这样,exe将能直接通过函数名调用dll 的输出函数,就像调用程序内部的其他函数一样。

四、 动态链接库的def文件

上节介绍的dll导出函数是前面添加__declspec(impoet)语句,声明该函数为dll的导出函数,还有另一种方式声明函数为导出函数–通过def文件

1、如何使用def文件

(1)新建解决方案,添加两个项目,DllLib是生成dll文件的项目,Dllcall是调用该dll的项目。
这里写图片描述
(2)在dllLib中添加lib.cpp和dlllib.def两个文件(不需要.h头文件),代码如下
Lib.cpp代码:声明两个函数,加法和减法

int __stdcall Add(int numa, int numb)
{
	return (numa + numb);
}

int __stdcall Sub(int numa, int numb)
{
	return (numa - numb);
}

dllLib.def代码如下:

LIBRARY DllLib
EXPORTS
Add @ 1
Sub @ 2

.def文件的规则为:
(1)LIBRARY语句说明该.def文件相对于的dll(不需要后缀dll)
(2)EXPORTS语句后面列出要到处的函数名称,可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n,在进行函数调用时,可以根据这个编号调用该函数(参见下面的调用过程)
(3).def文件中的注释由每行开始处的分号(;)指定,且注释不能与语句共需一行。
由此可以看出,例子中的.def文件的含义是生成名为DllLib的动态链接库,导出其中的add和sub函数,并且指定add函数序号为1,sub序号为2。

2、调用dll

调用的方法与上面介绍的一样,本例使用动态调用。将刚刚生成的dll拷贝到项目目录下。
main.cpp代码如下:

#include <stdio.h>
#include <windows.h>

typedef int (__stdcall *FUN)(int, int);
HINSTANCE hInstance;

FUN   fun;

int main()
{
	hInstance = LoadLibrary(L"DllLib.dll");
	if(hInstance!=NULL)
	{
		fun = (FUN)GetProcAddress(hInstance, "Add");
		//当在Def文件中指定函数序号时,可以通过序号导出,否则只能通过函数名称导出
		//fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));
		if (fun!=NULL)
		{
				printf("1+2=%d",fun(1, 2));
		}
	}
	FreeLibrary(hInstance);
	return 0;
}

注:def文件中定义了函数序号,在动态加载dll时,可以根据这个序号加载函数,这样做的好处时,当dll工程的导出函数的函数名有变化,而功能没有变化时,只要def中定义的函数序号没有变化,则调用dll的代码不需要任何改变。
运行结果如下:
这里写图片描述

五、 DllMain函数

Windows在加载dll时候,需要一个入口函数,就如同控制台需要main函数,win32程序需要WinMain函数一样,在前面的例子中dll并没有提供DllMain函数,应用程序也能成功的调用dll,这是因为Windows在找不到DllMain函数时,系统会从其他运行库中引用一个不做任何操作的缺省DllMain函数版本,并不是意味着dll可以放弃DllMain函数,
根据编写规范,Windows必须查找并执行dll里面的DllMain函数作为加载dll的依据,它使得dll能够驻留在内存里,DllMain函数不属于导出函数,而是dll的内部函数,这意味着不能直接在应用程序中引用DllMain函数,DllMain函数是自动被调用的。DLLMain函数的作用是可以做一下初始化操作,相当于类的构造函数,关于DLLMain函数和导出类,在这里不做过多的讲解,后续需要时,再深入研究。

1、为dll添加DllMain函数

(1)新建解决方案,添加两个工程。一个是生成dll,一个是调用dll。
这里写图片描述
Lib.h代码

#ifndef __LIB_H__
#define __LIB_H__
	extern "C" int __declspec(dllexport) add(int a,int b); 
#endif

Lib.cpp代码

#include "lib.h"
#include "stdio.h"
#include "windows.h"

BOOL APIENTRY DllMain(HANDLE hModule,
	DWORD ul_reason_for_call,
	LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		printf("\nprocess attach of dll");
		break;
	case DLL_THREAD_ATTACH:
		printf("\nthread attach of dll");
		break;
	case DLL_THREAD_DETACH:
		printf("\nprocess detach of dll");
		break;
	case DLL_PROCESS_DETACH:
		printf("\nprocess detach of dll");
		break;
	}
	return TRUE;
}

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

Main.cpp代码

#include "stdio.h"
#include "windows.h"

typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型

int main()
{
	HINSTANCE hDll;//DLL 句柄
	lpAddFun addFun;//函数指针
	hDll = LoadLibrary(L"DllLib.dll");//动态获取dll文件的路径
	if (hDll!=NULL)
	{
		addFun =(lpAddFun)GetProcAddress(hDll,"add");
		if (addFun!=NULL)
		{
			int result =addFun(2,3);
			printf("\ncall add in dll %d",result);
		}

		FreeLibrary(hDll);
	}
	return 0;
}

六、 导出类

建立工程如下:

这里写图片描述

工程中的几个类代码如下:

point.h

#ifndef __POINT_H__
#define __POINT_H__

#ifdef DLL_FILE
class _declspec (dllexport) point //导出类point
#else 
class _declspec(dllimport) point //导入类point
#endif
{
public: 
	float y;
	float x;
	point();
	point(float x_coordinate,float y_coordinate);
};

#endif

point.cpp

#ifndef DLL_FILE
#define DLL_FILE
#endif
#include"point.h"
//类point的缺省构造函数
point::point()
	  {
		  x=0.0;
		  y=0.0;
	  }
point::point(float x_coordinate,float y_coordinate)
	  {
		  x=x_coordinate ;
		  y=y_coordinate;
	  }

circle.h

#ifndef __CIRCLE_H__
#define __CIRCLE_H__
#include "point.h"
#ifdef DLL_FILE
class _declspec (dllexport) circle //导出类circle
#else 
class _declspec(dllimport) circle //导入类circle
#endif
{
public: 
	void SetCenter(const point crePoint);
	void SetRadius(float r);
	float GetGirth();
	float GetArea();
	circle();
private:
	float radius;
	point center;
};

#endif

circle.cpp

#ifndef DLL_FILE
#define DLL_FILE
#endif
#include"circle.h"
#define  PI 3.1415926
//类circle的缺省构造函数
circle::circle()
{
	center =point(0,0);
	radius =0;
}
//得到圆的面积
float circle::GetArea()
{
	return PI*radius*radius;
}
//得到圆从周长
float circle::GetGirth()
{
	return 2*PI*radius;
}
//设置圆心坐标
void circle::SetCenter(const point crePoint)
{
	center =crePoint;
}
//设置圆的半径
void circle::SetRadius(float r)
{
	radius	 =r;
}

将circle.h,point.h和生成的.lib文件放在同级目录
这里写图片描述

main.cpp

#include "circle.h"
#include "stdio.h"

//.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
#pragma comment(lib,"DllExportClass.lib")

int main()
{
    circle c;
	point p(2.0,2.0);
	c.SetCenter(p);
	c.SetRadius(1.0);
	printf("area:%f,girth:%f",c.GetArea(),c.GetGirth());
	scanf("%d");
	return 0;
}


结果为
这里写图片描述

  • 45
    点赞
  • 173
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值