关闭

win32汇编动态链接库的编写及使用

标签: 汇编源代码文本编辑
4222人阅读 评论(1) 收藏 举报
分类:


以前安装程序的时候,在安装目录下总会发现 好多的以.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)中的输出库函数地址

原型:

FARPROC GetProcAddress(

HMODULE hModule, // DLL模块句柄

LPCSTR lpProcName // 函数名

);

参数:

hModule

[in] 包含此函数的DLL模块的句柄。LoadLibrary、AfxLoadLibrary 或者GetModuleHandle函数可以返回此句柄。

lpProcName

[in] 包含函数名的以NULL结尾的字符串,或者指定函数的序数值。如果此参数是一个序数值,它必须在一个字的底字节,高字节必须为0。

返回值:

如果函数调用成功,返回值是DLL中的输出函数地址。

如果函数调用失败,返回值是NULL。得到进一步的错误信息,调用函数GetLastError。




LoadLibrary()

功能:

载入指定的动态链接库,并将它映射到当前进程使用的地址空间。一旦载入,即可访问库内保存的资源


原型:

HMODULE WINAPI LoadLibrary( _In_ LPCTSTR lpFileName);


参数:

lpFileName:  需要载入的动态链接库的名称

返回值:

Long,成功则返回库模块的句柄,零表示失败。会设置GetLastError

一旦不需要,用FreeLibrary函数释放DLL


FreeLibrary()

功能:

释放指定的动态链接库。

原型:

BOOL WINAPI FreeLibrary( _In_ HMODULE hModule);

参数:

hLibModule Long,要释放的一个库句柄

返回值:

long,非零表示成功,零表示失败。会设置GetLastError









































             

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:171817次
    • 积分:3073
    • 等级:
    • 排名:第12818名
    • 原创:90篇
    • 转载:184篇
    • 译文:0篇
    • 评论:20条
    博客专栏
    最新评论