创建动态链接库(Sample.dll)
步骤1:创建导出定义文件 Sample.def
在实验目录下新建文本文件:
EXPORTS _Mod

步骤2:创建DLL源代码 Sample.asm
同一目录下创建Sample.asm:
.386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义(根据实际路径修改) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include D:\\BYQ\\masm32v11r\\masm32\\include\\windows.inc ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? dwCounter dd ? ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; dll 的入口函数 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllEntry proc _hInstance,_dwReason,_dwReserved mov eax,TRUE ret DllEntry 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

步骤3:创建头文件 Sample.inc
创建Sample.inc供调用方使用:
`;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Version 1.0 ; Date: 2004.05.01 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Sample.dll 导出函数: ; ;invoke _Mod,dwNumber1,dwNumber2 ; 输入:dwNumber1 和 dwNumber2 为两个整数 ; 输出:两数的模 dwNumber1 % dwNumber2 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Mod proto dwNumber1:dword,dwNumber2:dword`

步骤4:编译和链接DLL
打开命令提示符,执行:
; 汇编生成目标文件 ml /c /coff Sample.asm

发现环境变量忘记添加了
这里因为个人环境原因我们设置临时环境变量

再次进行汇编

汇编成功 ; 链接生成DLL Link /subsystem:windows /Dll /Def:Sample.def Sample.obj

目录下生成 Sample.dll, Sample.lib, Sample.exp

创建调用程序(UseDll.exe)
步骤5:创建资源文件 UserDll.rc
准备图标文件Main.ico

创建UserDll.rc
#include <resource.h> //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #define ICO_MAIN 1000 #define DLG_MAIN 1000 #define IDC_NUM1 1004 #define IDC_NUM2 1005 #define IDC_MOD 1006 //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 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, "宋体" { GROUPBOX "取模函数测试", -1, 4, 37, 164, 32, BS_GROUPBOX EDITTEXT IDC_NUM1, 11, 50, 43, 12, ES_NUMBER LTEXT "%", -1, 57, 52, 5, 8 EDITTEXT IDC_NUM2, 64, 50, 43, 12, ES_NUMBER LTEXT "=", -1, 110, 52, 5, 8 EDITTEXT IDC_MOD, 117, 50, 43, 12, ES_READONLY }

步骤6:创建调用程序源码 UserDll.asm
创建UserDll.asm:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义(根据实际路径修改) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include D:\\BYQ\\masm32v11r\\masm32\\include\\windows.inc include D:\\BYQ\\masm32v11r\\masm32\\include\\user32.inc includelib D:\\BYQ\\masm32v11r\\masm32\\lib\\user32.lib include D:\\BYQ\\masm32v11r\\masm32\\include\\kernel32.inc includelib D:\\BYQ\\masm32v11r\\masm32\\lib\\kernel32.lib include Sample.inc includelib Sample.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 等值定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_NUM1 equ 1004 IDC_NUM2 equ 1005 IDC_MOD equ 1006 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .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_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,DLG_MAIN,NULL,offset _ProcDlgMain,NULL invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start

编译和运行调用程序
编译资源、汇编、链接
在工作目录下依次执行:
`; 编译资源 rc UserDll.rc
; 汇编 ml /c /coff UserDll.asm
; 链接 Link /subsystem:windows UserDll.obj UserDll.res`

成功生成exe文件

步骤10:运行程序
UserDll.exe

知识梳理
一、Windows DLL 的基本概念与生命周期
-
什么是 DLL(动态链接库):运行时由操作系统装载的可执行模块(PE 格式)。它把函数/资源打包供其它程序(或其它 DLL)在运行时调用。好处:代码复用、节省内存、可替换升级。
-
加载时机:
-
显式加载:
LoadLibrary/FreeLibrary -
隐式加载:程序在链接时引用了 DLL(通过
.lib),进程启动时由 loader 自动加载
-
-
生命周期回调 :系统在装载/卸载或线程创建/退出时会调用 DLL 主入口
DllMain(也叫DllEntry)。-
DllMain(hinst, DLL_PROCESS_ATTACH, NULL):进程装载 -
DLL_PROCESS_DETACH:进程卸载 -
DLL_THREAD_ATTACH/DLL_THREAD_DETACH:线程事件(注意:不一定会收到线程事件)
-
-
DLL 必须返回 TRUE 才能被成功载入 (实验中
mov eax, TRUE即返回 TRUE)。
二、PE/链接器相关(.def、.lib、.exp、导出表)
-
.def(Module-Definition)文件
指定导出的符号(函数名/序号)。示例:
EXPORTS _Mod。它告诉 linker 将_Mod写入导出表(Export Table)。 -
导出方式:
-
使用
.def(显式列出) -
使用编译器关键字
__declspec(dllexport)(C/C++)
-
-
生成的文件:
-
.dll:运行时要的库 -
.lib(import library):链接时使用(目标程序用它生成对 DLL 的引用),并非静态库 -
.exp:导出信息(中间文件)
-
-
Export Table / IAT:
-
Export Table:DLL 中记录可被外部调用的函数(名字/序号/地址)
-
Import Address Table (IAT):可执行文件或另一个 DLL 的表,运行时被 loader 修补为指向目标 DLL 的实际函数地址(thunk)
-
三、Calling Convention(调用约定)
实验用的是 stdcall (MASM option casemap:none + invoke 默认用 stdcall)------必须理解:
-
stdcall 特点:
-
参数由右向左压栈(最后一个参数先压)
-
调用方调用
CALL,被调用方(callee)负责清理栈(ret N) -
返回值放在
EAX(32-bit 整数),但某些操作(如div)余数放EDX
-
-
cdecl(对比):
- 同样右向左压栈,但调用者清理栈 ,用于可变参数函数(
printf)
- 同样右向左压栈,但调用者清理栈 ,用于可变参数函数(
-
fastcall 等 :部分参数传寄存器(MSVC 有
__fastcall,有平台差异) -
在汇编层面要留意
uses子句 :_Mod proc uses ecx edx _dwNumber1,_dwNumber2表示该过程会改写 ecx/edx,MASM 会自动保存/恢复这些寄存器给调用者(自动 push/pop),以防破坏调用者期望的寄存器值(callee-saved vs caller-saved 概念)。