IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    动态链接库的编写及使用

    summer发表于 2016-05-30 14:28:52
    love 0

    以前安装程序的时候,在安装目录下总会发现 好多的以.DLL结尾的文件,这些是什么玩意儿?有什么用?而且有时候运行程序的时候还会出现“无法定位程序输入点...与动态链接库....上”这种错误,现在想起来真是可笑。下面就来介绍一下动态链接库以及怎么使用动态链接库(以下简称DLL)。

    当你编写一个程序的时候,为了使用以前写好的库中的函数,很方便的一个解决方法就是使用动态链接库(DLL),下面我们先来编写一个DLL文件,了解一下DLL文件的结构。

    我使用的是RadASM 汇编集成工具,以前编写的都是WIN32汇编程序,现在编写DLL文件需要说明一下具体操作,如下图所示:首先在RadASM中点击新建工程出现以下界面,选中DLL Project一项,在下面书入工程名称,以及工程说明(根据自己的理解写。。)


    下一步:


    下一步:


    该工具已经根据刚才的设定自己默认选择了一些必须要选中的选项如上图所示,如果我们需要在DLL文件中插入其它的一些东西的话,可以自己选择,比如资源(Rc),文本(Txt),注册表(Rgs)等等如上所示,创建目录下面那几个选项也是根据个人情况自己选择,下一步


    在这里注意看一下下面编译资源脚本,编译,连接等后面的文本编辑框中的参数及其格式,编写WIN32APP与编写动态链接库的一些选项是不同的,该工具已经默认定义了必要的选项,如果个人需要修改可以自定义,点击完成


    然后在28.ASM中输入DLL功能代码,与以前编写的WIN32APP不同的是还需要编辑一个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导出库中的函数的声明

    _IncCounter
    proto
    _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
                       invoke   LoadLibrary,addr szDll
                       .if      eax
                       
        mov       hDllInstance,eax
                       
        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
                                mov      eax,wParam
                                .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。


                 



沪ICP备19023445号-2号
友情链接