自己写动态链接库

动态链接库一些基础的概念:

动态链接库缩写为DLL。动态链接库提供了许多的通用函数,可以被多个程序调用。动态链接库,顾名思义,它只有在程序执行的时候才会装入到程序的地址空间中,程序不执行时就只保留一些库的信息在文件中。了解过PE文件结构的应该知道,PE文件不执行时,动态链接库的信息在PE文件的导入表中,导入表里面包含了这个PE文件要使用的所有的动态链接库的名称以及要从库中导入的函数的一些信息。

了解过Windows的分页内存机制应该知道,如果有多个程序用到同一个动态链接库,Windows在物理空间中只保留一份库的代码,系统通过分页机制将这份代码映射到不同的进程的地址空间中。因为Windows是一个分时系统,所以对于用户DLL,当时间片切换到了要用这个DLL的进程时,该DLL在内存中的代码就会被映射到这个进程的线性地址空间中。而对于系统DLL,因为所有程序都要用到,所以它会在所有时间片中都被映射。所以说,不管有多少程序正在使用DLL,库代码在物理内存中永远只有一份。但是库使用的数据段还是会被映射到不同的物理内存中,多少给程序在使用动态链接库就会有多少份数据段。

注意:动态链接库是被映射到其他应用程序的线性地址空间中执行的,它和应用程序可以看成是一体的,DLL可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它所做的任何动作都是代表应用程序进行的,如果动态链接库进行打开文件,分配资源等操作,这些文件,资源都是归应用程序所拥有。

编写动态链接库:

我们先看一段动态链接库的源码:

		.386
		.model	flat,stdcall
		option	casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include		windows.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.data?
dwCounter	dd	?
		.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的入口函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DllEntry	proc	_hInstance,_dwReason,_dwReserved
			mov		eax,TRUE
			ret
DllEntry	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;私有函数。将数值限制在0到10之间
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_CheckCounter	proc
				mov		eax,dwCounter
				cmp		eax,0
				jge		@F
				xor		eax,eax
				@@:
				cmp		eax,100
				jle		@F
				mov		eax,100
				@@:
				mov		dwCounter,eax
				ret

_CheckCounter	endp				;如果大于等于0,则保持原来的数字不变,如果大于十则将十填入,小于0则将0填入
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的导入函数之一,数据自加一,且范围在0到10以内
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_IncCounter		proc
				inc		dwCounter
				call	_CheckCounter
				ret
_IncCounter		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的导入函数之二,数据自减一,且范围在0到10以内
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_DecCounter		proc
				dec		dwCounter
				call	_CheckCounter
				ret
_DecCounter		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的导入函数之三,取模运算
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Mod			proc	uses ecx edx _dwNumber1,_dwNumber2
				xor		edx,edx
				mov		eax,_dwNumber1
				mov		ecx,_dwNumber2
				.if		ecx
						div		ecx
						mov		eax,edx
				.endif
				ret
				
_Mod			endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
				End	DllEntry

乍一看好像和平常的汇编源代码没有区别,但是细看就会发现,它没有了start和end   start 。而是换成了一个 End   DllEntry,并且还加了一个入口函数。其他三个函数就是DLL的导出函数,导出函数就和我们平常自定义的函数一样写就行。

和可执行文件一样,动态链接库需要一个入口点,但是它的入口点是一个函数,函数的名字可以随意取,只要合法就行,但是函数的格式有规定。

入口函数的结构一般如下所示:

DllEntry    proc    hInstDLL,dwReason,dwReserved
        
            mov     eax,dwReason
            .if     eax == DLL_PROCESS_ATTACH
                    ;保存hInstDll
                    ;初始化库需要的各种资源
                    .if     初始化成功
                            mov    eax,true
                    .else
                            mov    eax,FALSE
                    .endif
            .elseif eax == DLL_THREAD_ATTACH
                    ;为新的线程分配资源
            .elseif eax == DLL_THREAD_DETACH
                    ;为线程释放资源
            .elseif eax == DLL_PROCESS_DETACH
                    ;释放库使用的资源
            .endif
            ret

DllEntry    Endp   

函数的第一个参数是该动态链接库的句柄,因为这个入口函数对调用该DLL的程序时不可见的,入口函数是供系统调用的,系统在装载,卸载动态链接库,以及进程中线程的创建和结束时都会调用这个入口函数。所以要在动态链接库里面获得它自己的DLL的话,就只能在这里获得了。

第二个参数是调用的原因,从上面代码可以看到原因有4种。

  • DLL_PROCESS_ATTACH:这个原因表示动态链接库刚刚被映射到进程的地址空间,这里可以做一些初始化的工作。返回TRUE表示初始化成功,返回FALSE表示初始化失败,这样库的装入也会失败。
  • DLL_THREAD_ATTACH:这个原因表示进程创建了一个新的线程,这样的话,就要为那个线程分配资源。
  • DLL_THREAD_DETACH:这个原因表示要结束一个线程,所以要释放资源
  • DLL_PROCESS_DETACH:这个原因表示动态链接库要被卸载了。

最后一个参数是系统保留参数,可以不加理会。

但是在我们的例子程序中,只返回应该一个true表示装入成功就可以了,因为我们也没有什么好初始化的。

编写动态链接库光有这个源文件还不够,我们还需要一个指出导出函数的文件,这个就是定义文件。比如,如果我们上面编写的那个DLL里面最后三个函数都导出的话,定义文件可以这样写:

EXPORTS    _IncCounter
           _DecCounter
           _Mod

没错,就是这么一点,需要导出什么函数就把函数名写在EXPORTS后面就行了。而我们在库的代码中有写的函数但是没有在这里定义的的话,那么这个函数就是DLL的私有函数,对应用程序是不可见的。

生成应该动态链接库需要就是这两个文件,然后和之前编译链接汇编源代码成EXE文件不同的是连接选项,编译选项还是一样的。链接DLL的完整的链接选项:

Link    /DLL /subsystem:windows /Def:filename.def filename.obj filename.res

DLL文件中也可以包含资源。

然后为了使用NMAKE,这里还是给出完整的makefile文件:

DLL = Sample
LINK_FLAG =/DLL /subsystem:windows
ML_FLAG = /c /coff

$(DLL).dll: $(DLL).obj $(DLL).def
			LINK $(LINK_FLAG) /Def:$(DLL).def $(DLL).obj
.asm.obj:
		ml $(ML_FLAG) $<
.rc.res:
		rc $<
clean:
		del*.obj
		del*.exp

好,有了这三个文件,我们就可以直接用NAMKE编译链接了。

 编译链接完了之后会发现文件夹下多了好几个文件:

 原来文件中就只有Makefile,Sample.asm,Sample.def这三个文件。那么多出来的那几个文件是什么呢?

首先多出来的DLL文件就不用说什么了,这个就是我们最后要得到的动态链接库文件,然后下面还有个EXP文件是什么?这个其实就是一个连接过程中的副产品,叫输出库文件,没什么用,可以删掉。再下面是一个LIB文件,这个文件就很熟悉了,这个是一个导入库文件,我们编写程序的时候要导入什么DLL的话一般都是  :includelib     xxxx.lib   。只有当我们指定了这个导入库文件,程序才知道到哪个库中寻找特定的函数。这个导入库文件是链接过程中自动帮我们生成的。最下面那个就不用多说了,就是编译后产生的OBJ文件。

DLL文件已经生成了,但是我们要在程序里面去调用的话这还不够。我们还需要编写一个INC文件,这个就是供程序使用的头文件,在程序中包含这个文件后我们就不需要再写函数声明了。

;***********************************************************************************
;作者:		XXX
;E-mail:	XXXXXXXXXX@qq.com
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Version	1.0
;			Data:	2020.01.31
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Sample.dll	导出函数
;
;1、invoke	_IncCounter
;		增加 DLL 内部计数器的值(最大到10)并返回计数
;2、invoke	_DecCounter
;		减少DLL内部计数器的值(最小到0)并返回计数
;3、invoke	_Mod,dwNumber1,dwNumber2
;		输入:dwNumber1 和 dwNumber2 为两个整数
;		输出: 两个数的模 dwNumber1 % dwNumber2
;**********************************************************************************
_IncCounter		proto
_DecCounter		proto
_Mod			proto	_dwNumber1:dword,_dwNumber2:dword

里面有用的部分也就是最后面三行,就是声明一下函数。如果只是自己用的话,前面那些东西就不用写了。

最后我们写个程序来调用这个DLL看看:

下面是资源文件:

#include	<resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define		ICO_MAIN	0x100
#define		DLG_MAIN	0x1000
#define		IDC_COUNT	0x1001
#define		IDC_INC		0x1002
#define		IDC_DEC		0x1003
#define		IDC_NUM1	0x1004
#define		IDC_NUM2	0x1005
#define		IDC_MOD		0x1006
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN	ICON	"Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN	DIALOG	186,132,173,79
STYLE	DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "自己写的DLL"
FONT 9,"宋体"
BEGIN
	GROUPBOX "Dll内部计数器",-1,4,2,164,32,BS_GROUPBOX
	EDITTEXT IDC_COUNT,10,15,73,12,ES_READONLY
	PUSHBUTTON "增加(&A)",IDC_INC,86,13,36,14
	PUSHBUTTON "减少(&D)",IDC_DEC,123,13,37,14
	GROUPBOX "取模函数测试",-1,4,37,164,32,BS_GROUPBOX
	EDITTEXT IDC_NUM1,11,50,43,12,ES_NUMBER
	LTEXT "%",-1,57,52,8,8
	EDITTEXT IDC_NUM2,64,50,43,12,ES_NUMBER
	LTEXT "=",-1,110,52,8,8
	EDITTEXT IDC_MOD,117,50,43,12,ES_READONLY
END

 下面是汇编源文件:

		.386
		.model flat,stdcall
		option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include文件
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include		windows.inc
include		user32.inc
includelib	user32.lib
include		kernel32.inc
includelib	kernel32.lib
include		Sample.inc
includelib	Sample.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN	equ		100h
DLG_MAIN	equ		1000h
IDC_COUNT	equ		1001h
IDC_INC		equ		1002h
IDC_DEC		equ		1003h
IDC_NUM1	equ		1004h
IDC_NUM2	equ		1005h
IDC_MOD		equ		1006h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
			.data?
hInstance	dd		?
			.code
_ProcDlgMain	proc	uses ebx edi esi hWnd,wMsg,wParam,lParam
		mov		eax,wMsg
		.if		eax == WM_CLOSE
				invoke	EndDialog,hWnd,NULL
		.elseif	eax == WM_INITDIALOG
				invoke	LoadIcon,hInstance,ICO_MAIN
				invoke	SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
		.elseif	eax == WM_COMMAND
			mov	eax,wParam
			.if	ax == IDC_INC
				invoke	_IncCounter
				invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
			.elseif	ax == IDC_DEC
				invoke	_DecCounter
				invoke	SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
			.elseif	ax == IDC_NUM1 || ax == IDC_NUM2
				invoke	GetDlgItemInt,hWnd,IDC_NUM1,NULL,FALSE
				push	eax
				invoke	GetDlgItemInt,hWnd,IDC_NUM2,NULL,FALSE
				pop		ecx
				invoke	_Mod,ecx,eax
				invoke	SetDlgItemInt,hWnd,IDC_MOD,eax,FALSE
			.endif
		.else
			mov	eax,FALSE
			ret
		.endif
		mov	eax,TRUE
		ret
_ProcDlgMain	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
			invoke	GetModuleHandle,NULL
			mov		hInstance,eax
			invoke	DialogBoxParam,eax,DLG_MAIN,\
					NULL,offset _ProcDlgMain,NULL
			invoke	ExitProcess,NULL
end			start
			

程序很简单,就是分别调用了DLL里面的三个函数,然后把结果显示再对话框中。来看看程序运行的结果:

当然要注意要把DLL文件与我们的程序放在一个目录下面,不然就会像这样:

系统查找DLL的顺序:程序所在目录,Windows系统目录,PATH环境变量指定目录。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值