动态链接库的编写及使用
Posted 小哈龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态链接库的编写及使用相关的知识,希望对你有一定的参考价值。
以前安装程序的时候,在安装目录下总会发现 好多的以.DLL结尾的文件,这些是什么玩意儿?有什么用?而且有时候运行程序的时候还会出现“无法定位程序输入点...与动态链接库....上”这种错误,现在想起来真是可笑。下面就来介绍一下动态链接库以及怎么使用动态链接库(以下简称DLL)。
当你编写一个程序的时候,为了使用以前写好的库中的函数,很方便的一个解决方法就是使用动态链接库(DLL),下面我们先来编写一个DLL文件,了解一下DLL文件的结构。
我使用的是RadASM 汇编集成工具,以前编写的都是WIN32汇编程序,现在编写DLL文件需要说明一下具体操作,如下图所示:首先在RadASM中点击新建工程出现以下界面,选中DLL Project一项,在下面输入工程名称,以及工程说明(根据自己的理解写。。)
下一步:
下一步:
该工具已经根据刚才的设定自己默认选择了一些必须要选中的选项如上图所示,如果我们需要在DLL文件中插入其它的一些东西的话,可以自己选择,比如资源(Rc),文本(Txt),注册表(Rgs)等等如上所示,创建目录下面那几个选项也是根据个人情况自己选择,下一步
在这里注意看一下下面编译资源脚本,编译,连接等后面的文本编辑框中的参数及其格式,编写WIN32程序与编写动态链接库的一些选项是不同的,该工具已经默认定义了必要的选项,如果个人需要修改可以自定义,点击完成
然后在28.ASM中输入DLL功能代码,与以前编写的WIN32程序不同的是还需要编辑一个28.Def文件,其实这个文件就是DLL的导出表信息,里面需要标明DLL中的每一个函数名称,为了使以后被应用程序使用的时候可以找到某个功能函数。
因为动态链接库的代码非常简单清晰,直接来看一下源代码
.386
.model flat, stdcall
option casemap :none
include windows.inc
.data? ;动态链接库和WIN32APP的编写很像,这里基本 都是一样的
dwCounter dd ?
.code
DllEntry proc _hInstance,_dwReason,_dwReserved ;这里需要注意,这里是动态链接库 的入口函数,就像是一个开始标志, 只有这样才能被系统识别,虽然这 个函数并没有什么功能代码,但是它 决定了DLL是否可以正常装入
mov eax,TRUE
ret
DllEntry Endp
_CheckCounter proc ;功能函数1,确保数字的大小在0--10之间
其实这个函数是个内部函数,因为在导出表 28.Def文件中并没有将它写出来,因此它只 能用在DLL文件内部使用
mov eax,dwCounter
cmp eax,0
jge @F ;大于等于则跳转
xor eax,eax
@@:
cmp eax,10
jle @F
mov eax,10
@@:
mov dwCounter,eax
ret
_CheckCounter endp
_IncCounter proc ;功能函数2,实现将当前的数字加1,该函数 在28.Def中已写出,后来会在应用程序中调用
inc dwCounter
call _CheckCounter
ret
_IncCounter endp
_DecCounter proc ;功能函数3,将当前的数字减1,该函数 在28.Def中已写出,也会在应用程序中调用
dec dwCounter
call _CheckCounter
ret
_DecCounter endp
_Mod proc uses ecx edx _dwNumber1,_dwNumber2 ;功能函数4,求输入的两个数的模,
该函数也会在应用程序中调用
xor edx,edx
mov eax,_dwNumber1
mov ecx,_dwNumber2
.if ecx
div ecx
mov eax,edx
.endif
ret
_Mod endp
End DllEntry ;最后DLL的结尾
除此之外,还需要编写另一个文件那就是上面提到的28.Def文件,如下图所示,只需写上EXPORTS 后面加上在以后的程序中需要调用的函数的名称即可,看似很简单,但是好多错误都是在这里出现的。
然后编译,连接,构建,然后就可以在文件夹RadASM\\Masm\\Projects\\28中找到已经编译好的DLL文件和一堆附属文件如下图:
上面的文件中除了一些我们认识的28.obj,28.rap,28.inc,28.asm还有Bak文件夹剩下的就是以前没见过的了,首先是28.dll文件,这就是我们自己编写的DLL文件(现在系统已经认识它了,而且我们也可以在应用程序中调用它了)。28.Def文件就是上面所说的DLL文件的导出函数说明,这是用来告诉连接器这三个函数需要导出,将来会被其他的的应用程序调用。28.lib文件是导入库文件,这个是为了当程序使用28.dll文件的时候,在源文件的开头 声明includelib 28.lib (看着应该很熟悉这个格式,对,其实就是这样,为了让连接器在连接的时候知道到哪个库中寻找相应的函数,我们平时写的程序中的这样的头文件也是为了使系统的DLL文件只能够的函数可以被应用程序找到) 。 28.exp文件是一个附带的文件,对我们现在还没有用处。28.inc文件中书写的是导出函数的功能,以及其它信息(这些都是自己写的,为了让其他程序员使用的时候方便)。到此DLL文件的编写就结束了。
下面就来看一下,编写好的DLL文件怎样在应用程序中使用,首先编写资源文件,依旧使用ResEdit工具,
资源文件不在多说很熟悉了,直接看一下资源代码:
// Generated by ResEdit 1.6.6
// Copyright (C) 2006-2015
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
//
// Dialog resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_DIALOG1 DIALOG 0, 0, 229, 121
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "DLL例子"
FONT 8, "Ms Shell Dlg"
{
LTEXT "%", 0, 80, 91, 6, 9, SS_LEFT, WS_EX_LEFT
LTEXT "0", IDC_MOD, 183, 90, 30, 13, SS_LEFT, WS_EX_STATICEDGE
LTEXT "=", 0, 165, 92, 5, 9, SS_LEFT, WS_EX_LEFT
EDITTEXT IDC_NUM2, 99, 90, 52, 12, ES_AUTOHSCROLL, WS_EX_LEFT
EDITTEXT IDC_NUM1, 25, 90, 48, 12, ES_AUTOHSCROLL, WS_EX_LEFT
GROUPBOX "取模函数测试", 0, 16, 73, 208, 32, 0, WS_EX_STATICEDGE
LTEXT "", IDC_COUNT, 31, 35, 100, 10, SS_LEFT, WS_EX_STATICEDGE
GROUPBOX "DLL 内部计数器", 0, 16, 19, 206, 34, 0, WS_EX_STATICEDGE
PUSHBUTTON "减少(&D)", IDC_DEC, 181, 32, 34, 14, 0, WS_EX_LEFT
DEFPUSHBUTTON "增加(&A)", IDC_INC, 141, 32, 34, 14, 0, WS_EX_LEFT
}
//
// Icon resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDI_ICON1 ICON "icon1.ico"
资源文件中除了需要输入的两个数字的位编辑文本框,其他的都是静态文本框,一些风格可以自己添加。
下面来看一下怎么来编写实现代码调用28.dll文件实现相关的函数功能。DLL文件的装入方法有两种,一种是直接使用导入库文件(28.lib)和inc文件(28.inc),但是要注意28.inc文件需要自己写入DLL导出库中的函数的声明
_IncCounterproto
_DecCounter proto
_Mod proto dwNumber1:dword,dwNumber2:dword(就是这三个)就是这个格式。
28.lib文件是编写DLL文件时生成的直接复制到使用DLL文件的文件夹下就可以了。
这样其实也就相当于使用的是系统的DLL文件库一样,在头文件中加上包含的信息,在下面的代码中直接使用就可以了,这个程序很简单只是为了实现调用DLL文件,不再多说代码如下:
.386
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include 28.inc
includelib 28.lib
IDI_ICON1 equ 100
IDD_DIALOG1 equ 101
IDC_DEC equ 40000
IDC_COUNT equ 40001
IDC_INC equ 40002
IDC_NUM1 equ 40003
IDC_NUM2 equ 40004
IDC_MOD equ 40005
.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_COMMAND
mov eax,wParam
.if ax ==IDC_INC
invoke _IncCounter ;在处理控件的消息的时候,直接调用DLL库中的函数, 同下面两个函数的调用一样。
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
invoke DialogBoxParam,eax,IDD_DIALOG1,NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
end start
DLL文件的另一种装入方式是动态装入,也就是不像上面那样,在调用程序前系统已经将DLL文件装入,而是自己在程序代码中使用相关的函数装入LoadLibrary(装入DLL文件) GetProcAddress(获取导出函数地址)
在这个程序中遇见如下问题:
首先是自定义类型(typedef)
程序中的定义如下:
_PROCVAR2 typedef proto :dword,:dword ;首先定义一个使用两个参数的函数类型记为1
_PROCVAR0 typedef proto ;定义一个没有参数的函数类型记为2
PROCVAR2 typedef ptr _PROCVAR2 ;定义一个1类型的指针作为一个新的类型
PROCVAR0 typedef ptr _PROCVAR0 ;定义一个2类型的指针作为一个新的类型
这个新的类型的定义以前很少用到,这里需要注意学习使用。
还有一个问题就是在使用GetProcAddress函数获取了函数地址的时候,调用函数的方式是结合上面新的自定义类型来调用的,这种方式需要学习,如下代码:
.if ax == IDC_INC
.if lpIncCounter ;首先判断lpincCounter是否为真,同下
invoke lpIncCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.endif
.elseif ax == IDC_DEC
.if lpDecCounter
invoke lpDecCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.endif
.elseif ax == IDC_NUM1 || ax == IDC_NUM2
.if lpMod
invoke GetDlgItemInt,hWnd,IDC_NUM1,NULL,FALSE
push eax
invoke GetDlgItemInt,hWnd,IDC_NUM2,NULL,FALSE
pop ecx
invoke lpMod,ecx,eax
invoke SetDlgItemInt,hWnd,IDC_MOD,eax,FALSE
下面来看一下程序代码:
.386
.model flat,stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
IDI_ICON1 equ 100
IDD_DIALOG1 equ 101
IDC_DEC equ 40000
IDC_COUNT equ 40001
IDC_INC equ 40002
IDC_NUM1 equ 40003
IDC_NUM2 equ 40004
IDC_MOD equ 40005
_PROCVAR2 typedef proto :dword,:dword
_PROCVAR0 typedef proto
PROCVAR2 typedef ptr _PROCVAR2
PROCVAR0 typedef ptr _PROCVAR0
.data?
hDllInstance dd ?
lpIncCounter PROCVAR0 ?
lpDecCounter PROCVAR0 ?
lpMod PROCVAR2 ?
.const
szError db '28.dll 文件丢失或装载失败,程序功能无法实现',0
szDll db '28.dll',0
szIncCounter db '_IncCounter',0
szDecCounter db '_DecCounter',0
szMod db '_Mod',0
.code
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam ;对话框的消息处理过程
mov eax,wMsg
.if eax == WM_CLOSE
.if hDllInstance
xor eax,eax
mov lpIncCounter,eax
mov lpDecCounter,eax
mov lpMod,eax
invoke FreeLibrary,hDllInstance
.endif
invoke EndDialog,hWnd,NULL
.elseif eax == WM_INITDIALOG ;在初始化消息中装入DLL文件
invoke LoadLibrary,addr szDll
.if eax
mov hDllInstance,eax ;装入后记得保存装入的DLL文件句柄
invoke GetProcAddress,hDllInstance,addr szIncCounter ;获得指定函数名称的的入口地址
mov lpIncCounter,eax
invoke GetProcAddress,hDllInstance,addr szDecCounter
mov lpDecCounter,eax
invoke GetProcAddress,hDllInstance,addr szMod
mov lpMod,eax
.else
invoke MessageBox,hWnd,addr szError,NULL,MB_OK or MB_ICONWARNING
invoke GetDlgItem,hWnd,IDC_INC
invoke EnableWindow,eax,FALSE ;装入失败则灰化所有按钮和两个编辑文本框
invoke GetDlgItem,hWnd,IDC_DEC 也就是两个需要输入数字的编辑控件
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWnd,IDC_NUM1
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWnd,IDC_NUM2
invoke EnableWindow,eax,FALSE
.endif
.elseif eax == WM_COMMAND ;在处理控件的消息中,调用装入的DLL文件的
mov eax,wParam 功能函数,注意调用方式,为了使用invoke
.if ax == IDC_INC 伪指令调用函数(可以检查函数的参数),自
.if lpIncCounter 定义新的类型,以函数的入口地址调用函数
invoke lpIncCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.endif
.elseif ax == IDC_DEC
.if lpDecCounter
invoke lpDecCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.endif
.elseif ax == IDC_NUM1 || ax == IDC_NUM2
.if lpMod
invoke GetDlgItemInt,hWnd,IDC_NUM1,NULL,FALSE
push eax
invoke GetDlgItemInt,hWnd,IDC_NUM2,NULL,FALSE
pop ecx
invoke lpMod,ecx,eax
invoke SetDlgItemInt,hWnd,IDC_MOD,eax,FALSE
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,IDD_DIALOG1,NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
end start
最后来介绍一下新的API函数:
GetProcAddress()
功能:
检索指定的动态链接库(DLL)中的输出库函数地址
以上是关于动态链接库的编写及使用的主要内容,如果未能解决你的问题,请参考以下文章
GCC 编译使用动态链接库和静态链接库--及先后顺序----及环境变量设置总结