Windows程序设计:创建并使用动态链接库(DLL)

0x00前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

0x01动态链接库(Dynamic Link Libraries, DLL)

动态链接库(DLL)为模块化应用程序提供一种方式,使得更新和重用应用程序更加方便。注意只有在其它模块调用动态链接库中的函数时,动态链接库才会发挥作用。

另外,动态链接库是代码重用的绝佳方式,我们可以不必在每个程序中都编写同一功能函数,只要对这个功能函数创建成DLL,然后再从需要该功能函数的程序调用它即可,这样一来减少了工作量。

还要注意的是,动态链接库不能够被直接使用或接收消息,但它是一个独立的文件,可以被可执行程序或其它DLL调用。

动态链接库包含两个文件:引入库文件(.lib)和DLL文件(.dll)。引入库文件(.lib)包含DLL文件中导出函数和API接口,DLL文件(.dll)包含实际的函数和数据。动态链接库在程序编译时并不会被插入可执行文件中,只有在可执行文件运行并需要动态库中的某个函数时,才会加载所需的动态链接库,将该库代码调入内存,这就是所谓的“动态链接”。

在发布产品时,除了发布可执行文件,还要同时发布该程序要调用的动态链接库(若有)。

0x02创建并生成动态链接库(DLL)

1.创建DLL项目

打开文件-》新建项目-》Win32 项目,新建工程“DLLDemo”,然后选择DLL,如下图所示:
在这里插入图片描述
在这里插入图片描述
此时查看左侧列表中的源文件中的dllmain.cpp,为vs2010自动生成,若在创建工程时第2步选择“空项目”则创建空工程,不会生成该例。该文件中的DllMain为动态链接库的入口点函数,windows系统在库的装载、卸载以及进程中创建和结束时调用该入口函数,该函数在vs2010中截图如下:
在这里插入图片描述
具体参数及分支选择释义如下:

BOOL APIENTRY DllMain( HMODULE hModule,				//本模块调用句柄
                       DWORD  ul_reason_for_call,	//调用原因
                       LPVOID lpReserved			//没有被使用
					 )
{
	switch (ul_reason_for_call)
	{
	//动态链接库刚被映射到某个进程的地址空间
	case DLL_PROCESS_ATTACH:
	
	//应用程序创建了一个新线程
	case DLL_THREAD_ATTACH:
	
	//应用程序某个线程正常终止
	case DLL_THREAD_DETACH:

	//动态链接库将被卸载
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

动态链接库DLL可以定义两种函数:导出函数和内部函数。导出函数可以被其它模块调用,也可以被定义这个函数的模块调用,而内部函数只能被定义这个函数的模块调用。

动态链接库中的代码编写和通常的代码编写一样,可以使用头文件,资源以及类。

2.编写DLL实例

现在编写一个DLL实例,功能为在可执行文件运行时,会调用该DLL以弹出一个消息框,该消息框中显示的内容由可执行程序决定,标题栏内容由本DLL模块名决定。

(1)修改dllmain.cpp代码内容:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"

HMODULE g_hModule;
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			g_hModule = (HMODULE)hModule;	//保存模块句柄,为DLL模块句柄实例
			break;
	}
	return TRUE;
}

(2)在项目中新建.h文件
函数定义之后只能在本工程中使用,若要将函数导出供其它模块使用,需要新建头文件,在该头文件进行如下修改:

#include "stdafx.h"

__declspec(dllexport) void ExportFunc(LPCTSTR pszContent);

通常到这一步会发现错误列表中会有 “ “LPCTSTR” 类型的实参与 “LPCSTR” 类型的形参不兼容”报错:
在这里插入图片描述
打开项目-》属性-》配置属性-》常规,将字符集修改为“使用多字节字符集”解决:

在这里插入图片描述(3)修改DLLDemo.cpp(由工程创建时的名称决定):

// DLLDemo.cpp : 定义 DLL 应用程序的导出函数。
#include "stdafx.h"
#include "DllDemo.h"

extern HMODULE g_hModule;

//自定义导出函数
void ExportFunc(LPCTSTR pszContent)
{
	char sz[MAX_PATH];
	::GetModuleFileNameA(g_hModule, sz, MAX_PATH);
	::MessageBoxA(NULL, pszContent, strrchr(sz, '\\') + 1, MB_OK);
}

在可执行文件加载次DLL时,系统会调用DllMain函数,当ul_reason_for_call的值为DLL_PROCESS_ATTACH时,hModule会保存到全局变量g_hModule中。GetModuleFIleName函数用于取得指定的模块的文件名,strrchr函数会找到文件名最后的“\”字符串位置,即“strrchr(sz,’\’”) + 1”返回的值为本DLL模块不带目录的名称。

(4)代码完成后,便可生成DLL,选择生成-》生成DLLdemo,生成的.dll以及.lib存放在工程文件夹中的Deug文件夹中:

在这里插入图片描述
在这里插入图片描述
生成之后,.dll,.lib以及步骤(2)中新建的.h文件可以被其它工程使用。

0x03在程序中调用动态链接库

调入DLL中的导出函数有两种方式:
(1)装载期间动态链接。模块可以像调用本地函数一样可以从其它模块导出的函数,就好像API函数一样,装载期间必须使用DLL导入库(.lib文件),它为系统提供了加载这个DLL和定位DLL中导出函数所需要的信息。
(2)运行期间动态链接。模块可以使用LoadLibrary或者LoadLibrarayEx函数在运行期间加载这个DLL。DLL加载之后加载模块调用GetProAddress函数取得 DLL导出函数的地址然后通过 函数地址调用DLL中的函数。

使用第一种方法时,需要.dll,.lib以及上一小节步骤(2)中新建的.h,使用第2中方法时,只有.dll会用到。

1.装载期间动态链接

该方法在可执行文件运行时有载入器(加载可执行文件中的组件)载入.dll文件。载入器是通过PE文件中的.idata确定要载入的DLL。

新建一个DLLTest Win32控制台工程,将.dll,.lib以及上一小节步骤(2)中新建的.h赋值到该工程目录下。DLLTest调用导出函数ExportFunc方法如下:

#include"stdafx.h"
#include <windows.h>
#include"DllDemo.h"


// 指明要链接到DLLDemo.lib库
#pragma comment(lib, "DLLDemo")

void main()
{
	// 调用DllDemo.dll库的导出函数
	ExportFunc("调用成功!");
}

编译运行:
在这里插入图片描述
同样在编译的过程中要注意“ “LPCTSTR”: 未声明的标识符”问题,修改方法见上一小节。
注意 ,载入器加载DLL文件时,默认情况下时在应用程序目录下查找,若是找不到就回到系统盘“\windows\system32”文件夹下查找,如果还找不到就按照错误处理。

2.运行期间动态链接

运行期间动态链接库是在程序运行过程中显式加载DLL库,从中导出需要的函数,为了能在运行期间能够动态的导出函数,一般需要在DLLDemo工程中新建一个DEF文件来指定要导出的函数。
右键源文件-》添加-》新建项,选择新建def文件命名为DLLDemo,在其中添加:

EXPORTS
	ExportFunc

在这里插入图片描述
这两行说明DLL库向外导入ExportFunc函数。重新编译该工程。
回到DLLTest工程下,修改程序DLLDemo.cpp(由工程创建时的名称决定):

#include"stdafx.h"
#include <windows.h>

// 声明函数原形
typedef void (*PFNEXPORTFUNC)(LPCTSTR);

int main(int argc, char* argv[])
{
        // 加载DLL库
        HMODULE hModule = ::LoadLibrary("E:\\下载\\01计算机学习\\windows程序设计学习\\自己照写Windows程序设计代码例子\\DLLDemo\\Debug\\DLLDemo.dll");
        if(hModule != NULL)
	{ 
		// 取得ExportFunc函数的地址
		PFNEXPORTFUNC mExportFunc = (PFNEXPORTFUNC)::GetProcAddress(hModule, "ExportFunc");
		if(mExportFunc != NULL)
		{
			mExportFunc("调用成功!");
		}

		// 卸载DLL库
		::FreeLibrary (hModule);	
	}
	
        return 0;
}

编译运行,观察效果同上:
在这里插入图片描述
以上。

参考文档:
1.https://blog.csdn.net/u011642774/article/details/52440187
2.https://blog.csdn.net/u010335911/article/details/23474919?utm_source=blogxgwz3
3.https://blog.csdn.net/bloke_come/article/details/75336990
4.张铮,孙宝山,周立天.Windows程序设计(第3版)[M].北京;人民邮电出版社,2018.7.

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值