相信每一个玩过电脑的人都知道杀毒软件这个东西的存在(如果你不知道。。。那么我也没啥说的了汗。。),每当我们的电脑出现卡顿或者中毒等情况的的时候,我们往往会进行一个全盘的文件扫描,对每个文件进行处理,包括校验该文件的各种属性,来确定该文件是否是垃圾文件或者是不安全文件,以前没有接触计算机内存管理的时候感觉当杀毒软件对全盘的文件进行扫描的时候,那种刷刷刷扫描文件的状态感觉很是强大(那时候还小对什么都好奇。。。),当学习完计算机内存管理的时候才算是有所了解,这个功能完全可以用很短的代码实现此功能,只不过对每个文件的判断.筛选.校验需要其它的额外操作(虽然现在不知道怎么写,但是我知道那是在处理某个被扫描出来的文件的时候调用另一个子函数实现对该文件的判断.筛选.校验等,也有可能是多个子函数,实现的功能都一样),对内存目录.文件的读写,需要遍历一个文件夹中的各个文件夹,然后对每个文件夹及文件进行处理计数,如此循环(递归实现,这也是在学习的时候学习罗云斌老师的方法),下面来看一下实现的步骤:
对话框 | IDD_DIALOG1 |
图标 | IDI_ICON1 |
编辑控件 | IDC_PATH |
静态文本框 | IDC_NOWFILE |
按键 | IDC_BROWSE IDOK |
对文件的处理 | _ProcessFile() |
查找文件功能实现(循环,递归) | _FindFile() |
实现y与查找相关的一些列操作(初始化,显示查找的内容等) | _ProcThread() |
对话框消息处理过程 | _ProcDlgMain() |
创建对话框 | DialogBoxParam() |
包含浏览文件模块 | include _BrowseFolder.asm |
上面介绍了程序实现的大致流程,下面来看一下遇见的问题,首先是资源文件:
本次依然是使用ResEdit工具编写,对资源文件的编写这次只有一个问题,那就是在你编辑静态文本框IDC_NOWFILE的时候由于静态文本框需要标题是空白的,我当时写的时候就把静态文本框右边属性栏那一行的标题设为空,这时候可以发现,在对话框中以前编辑静态文本框的地方很难找到那个静态文本框控件,其实这个时候用鼠标左键按着不放在那个控件的的大概位置选中一片区域,然后放开鼠标左键,那个很难选中的静态文本框控件就可以选出来了,但是这个时候你如果去看一下,现在关于这个控件的属性的时候你会发现,他的宽和高都是0,如果就按照这个大小去编译资源文件的时候,当编译好的资源文件与其他文件链接构建生成可执行文件的时候,就会发现这个静态文本框并没有显示出来需要显示出来的当前浏览到的文件的路径和名字,当时没有注意那个控件的大小,当程序运行之后才发现无法显示,当时也是一头雾水,我以为是在程序代码中对那个控件发送的消息失败得原因,或者是对这个控件发送的消息没有处理,查看源程序代码如下:
invoke
wsprintf,addr @szBuffer,addr szSearchInfo,dwFolderCount,dwFileCount,eax ;后面需要三 个参数分别对应 szSearchInfodb‘共找到 %d 个文件夹,%d
个文件,共 %luK 字节‘,0 这个变量中的3个需要显示的变量。
invoke SetDlgItemText,hWinMain,IDC_NOWFILE,addr @szBuffer ;设置IDC_NOWFILE这个控件的文本内容
上面这段代码将查找到的文件夹 及文件的个数显示出来,还有共多少个字节,
这一部分的代码没有什么错误
再看一下另一个与此控件相关的代码:
ProcessFileproc_lpszFile
local @hFile
inc dwFileCount
invoke
SetDlgItemText,hWinMain,IDC_NOWFILE,_lpszFile ;此处将文本框的文本内容设置 为查找到的文件夹或文件名(显示的是路径加名
称) 参数等并没有什么错误之处
invokeCreateFile,_lpszFile,GENERIC_READ,FILE_SHARE_READ,0,\
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
.if eax !=INVALID_HANDLE_VALUE
mov @hFile,eax
invoke
GetFileSize,eax,NULL
add dwFileSizeLow,eax
adc dwFileSizeHigh,0
invoke
CloseHandle,@hFile
.endif
ret
_ProcessFile endp
此处的代码也没有什么错误之处,由此看出,并不是,程序代码的错误,应该是资源文件的问题,最后我又把资源文件拿出来,将那个静态文本框重新编辑,将大小位置设置为 9, 49, 203, 17 从新编辑资源文件,连接构建运行可执行文件,OK程序功能可以实现了,对比两次对静态文本控件的书写格式,才发现是文本框的大小设置错误,以前的宽与高设置为0,0了, 这个我也很无语,因为使用ResEdit工具插入静态文本框时,把标题设置为空的时候,它的大小也会有所变化,应该先将文本内容(也就是标题)设为空(为了在程序中接受其他的文本信息)然后再改变它的位置,大小,如果次序颠倒的话,文本框的宽和高就是0,0
,当在程序代码中向这个控件发出修改这个控件的消息时,它并不能得到需要的文本信息,小小的错误,导致整个程序不能运行,关键是这种问题也不好查找,还是细节问题,引以为戒。下面来看一下完整的资源代码:
// 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, 219, 95
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_THICKFRAME | WS_SYSMENU
EXSTYLE WS_EX_STATICEDGE
CAPTION "全盘文件搜索"
FONT 8, "Ms Shell Dlg"
{
EDITTEXT IDC_PATH, 48, 26, 160, 17, ES_AUTOHSCROLL, WS_EX_LEFT
LTEXT "", IDC_NOWFILE, 9, 49, 203, 17, NOT WS_GROUP | SS_LEFT, WS_EX_LEFT
LTEXT "开始目录", 0, 11, 28, 33, 10, SS_LEFT, WS_EX_LEFT
PUSHBUTTON "浏览", IDC_BROWSE, 107, 76, 34, 14, NOT WS_TABSTOP | BS_VCENTER, WS_EX_LEFT
DEFPUSHBUTTON "开始", IDOK, 39, 76, 36, 14, 0, WS_EX_LEFT
}
//
// Icon resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDI_ICON1 ICON "icon2.ico"
上面介绍了资源文件的问题,下面来看一下程序源代码中遇见的问题,看一下这段代码浓缩的都是精华:
invoke
lstrlen,addr @szPath ;首先测量@szPath的长度,结果返回到eax中,备用。
lea esi,@szPath ;将变量@szPath的地址传递给esi
addesi,eax ;在esi基地址的基础上,加上@szPath变量的长度也就是
到了文件的末尾
xor eax,eax ;将eax清0
mov al,‘\‘ ;将‘\‘ 放入eax的低8位中,那么高8位就全是0了
.if byte ptr [esi-1] != al ;判断文件的倒数第二位是否是‘\‘
mov word ptr [esi],ax ; 如果不是‘\‘,那么将 ax放到esi中,将16位的ax放入 esi中,正好低8位是‘\‘,高8位是0,正好以0结尾。
.endif
invoke
lstrcpy,addr @szSearch,addr @szPath
invoke
lstrcat,addr @szSearch,addr szFilter
invoke
FindFirstFile,addr @szSearch,addr @stFindFile ;将完整的路径加文件名作为 新 的文件名进行查找
.if eax !=INVALID_HANDLE_VALUE ;上面函数调用不出错的话执行
下面语句
mov @hFindFile,eax
.repeat ;注意.repead循环的使用
invoke
lstrcpy,addr @szFindFile,addr @szPath
invoke
lstrcat,addr @szFindFile,addr @stFindFile.cFileName
.if @stFindFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
.if @stFindFile.cFileName != ‘.‘ ;判断是否为本目录,‘..‘则表
示上层目录
inc dwFolderCount
invoke
_FindFile,addr @szFindFile ;在循环中使用递归调用处理 目录的深度问题,实现将每个文件夹及子文件夹都读取
.endif
.else
invoke
_ProcessFile,addr @szFindFile ;对查找到的文件进行处理
.endif
invoke
FindNextFile,@hFindFile,addr @stFindFile ;在指定的文件下,对其进 行遍历查找(一个文件夹中
有多个子文件夹)
.until
(eax == FALSE) || (dwOption & F_STOP) ;如果想终止的话,提供一 个可以随时停止查找的按钮
invoke
FindClose,@hFindFile
.endif
我感觉上面子程序模块是本程序的精华之处,可以学习一下。
下面就是建立一个新的线程来进行与查找相关的一些操作(也就是子程序_ProcThread),因为查找全盘的文件有点耗时间,试想一下,如果当程序处理一个消息的时候,一直停在查找全盘文件的任务上面,下面的其他消息不能及时处理,是不是很不合适,因此,当接到查找文件的消息时,我们可以新建一个线程来完成这个任务,其他的消息继续处理,这也是多线程的一个应用(就像是在进程范围内的多任务一样,系统为他们轮流分配时间片,当轮到哪一个线程执行的时候,就执行,不然则"挂起"),如下代码创建一个新的线程:
.elseif
ax == IDOK
.if dwOption & F_SEARCHING
or dwOption,F_STOP
.else
invoke
GetDlgItemText,hWnd,IDC_PATH,addr szPath,MAX_PATH
invoke
CreateThread,NULL,0,offset _ProcThread,NULL,\ ;创建新线程完 成查找工作
NULL,addr @dwTemp
invoke
CloseHandle,eax
在对话框的过程中在对IDOK进行处理的时候,创建一个新的线程执行子程序_ProcThread,来实现查找功能,注意使用这种方法。
接下来看一下完整的程序代码:
.386
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include ole32.inc
includelib ole32.lib
include shell32.inc
includelib shell32.lib
include _BrowseFolder.asm
IDI_ICON1 equ 100
IDD_DIALOG1 equ 101
IDC_NOWFILE equ 40000
IDC_PATH equ 40001
IDC_BROWSE equ 40003
.data?
hInstance dd
?
hWinMain dd
?
dwFileSizeHigh dd?
dwFileSizeLow dd?
dwFileCount dd?
dwFolderCount dd?
szPath db
MAX_PATH dup (?)
dwOption db
?
F_SEARCHING equ0001h
F_STOP equ
0002h
.const
szStart db
‘开始(&S)‘,0
szStop db
‘停止(&S)‘,0
szFilter db
‘*.*‘,0
szSearchInfo db‘共找到 %d 个文件夹,%d 个文件,共 %luK 字节‘,0
.code
_ProcessFile proc_lpszFile
local @hFile
inc dwFileCount
invoke
SetDlgItemText,hWinMain,IDC_NOWFILE,_lpszFile
invoke
CreateFile,_lpszFile,GENERIC_READ,FILE_SHARE_READ,0,\
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
.if eax !=INVALID_HANDLE_VALUE
mov @hFile,eax
invoke
GetFileSize,eax,NULL
add dwFileSizeLow,eax
adc dwFileSizeHigh,0
invoke
CloseHandle,@hFile
.endif
ret
_ProcessFile endp
_FindFile proc_lpszPath
local @stFindFile:WIN32_FIND_DATA
local @hFindFile
local @szPath[MAX_PATH]:byte;用来存放“路径\”
local @szSearch[MAX_PATH]:byte;用来存放“路径\*.*”
local @szFindFile[MAX_PATH]:byte;用来存放“路径\找到的文件”
pushad
invoke
lstrcpy,addr @szPath,_lpszPath
@@:
invoke
lstrlen,addr @szPath
lea esi,@szPath
add esi,eax
xor eax,eax
mov al,‘\‘
.if byte ptr [esi-1] != al
mov word ptr [esi],ax
.endif
invoke
lstrcpy,addr @szSearch,addr @szPath
invoke
lstrcat,addr @szSearch,addr szFilter
invoke
FindFirstFile,addr @szSearch,addr @stFindFile
.if eax !=INVALID_HANDLE_VALUE
mov @hFindFile,eax
.repeat
invoke
lstrcpy,addr @szFindFile,addr @szPath
invoke
lstrcat,addr @szFindFile,addr @stFindFile.cFileName
.if @stFindFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
.if @stFindFile.cFileName != ‘.‘
inc dwFolderCount
invoke
_FindFile,addr @szFindFile
.endif
.else
invoke
_ProcessFile,addr @szFindFile
.endif
invoke
FindNextFile,@hFindFile,addr @stFindFile
.until
(eax == FALSE) || (dwOption & F_STOP)
invoke
FindClose,@hFindFile
.endif
popad
ret
_FindFile endp
_ProcThread procuses ebx ecx edx esi edi,lParam
local @szBuffer[256]:byte
and dwOption,not F_STOP
or dwOption,F_SEARCHING
invoke
GetDlgItem,hWinMain,IDC_PATH
invoke
EnableWindow,eax,FALSE
invoke
GetDlgItem,hWinMain,IDC_BROWSE
invoke
EnableWindow,eax,FALSE
invoke
SetDlgItemText,hWinMain,IDOK,addr szStop
xor eax,eax
mov dwFileSizeHigh,eax
mov dwFileSizeLow,eax
mov dwFileCount,eax
mov dwFolderCount,eax
invoke
_FindFile,addr szPath
mov edx,dwFileSizeHigh
mov eax,dwFileSizeLow
mov ecx,1000
div ecx
invoke
wsprintf,addr @szBuffer,addr szSearchInfo,dwFolderCount,dwFileCount,eax
invoke
SetDlgItemText,hWinMain,IDC_NOWFILE,addr @szBuffer
invoke
GetDlgItem,hWinMain,IDC_BROWSE
invoke
EnableWindow,eax,TRUE
invoke
GetDlgItem,hWinMain,IDC_PATH
invoke
EnableWindow,eax,TRUE
invoke
SetDlgItemText,hWinMain,IDOK,addr szStart
invoke
SetDlgItemText,hWinMain,IDC_PATH,addr szPath
and dwOption,not F_SEARCHING
ret
_ProcThread endp
_ProcDlgMain procuses ebx edi esi hWnd,wMsg,wParam,lParam
local @dwTemp,@szBuffer[MAX_PATH]:byte
mov eax,wMsg
.if eax ==WM_CLOSE
.if ! (dwOption & F_SEARCHING)
invoke
EndDialog,hWnd,NULL
.endif
.elseif
eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke
LoadIcon,hInstance,IDI_ICON1
invoke
SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
invoke
SendDlgItemMessage,hWnd,IDC_PATH,EM_SETLIMITTEXT,MAX_PATH,0
.elseif
eax == WM_COMMAND
mov eax,wParam
.if ax ==IDC_BROWSE
invoke
_BrowseFolder,hWnd,addr szPath
.if eax
invoke
SetDlgItemText,hWnd,IDC_PATH,addr szPath
.endif
.elseif
ax == IDC_PATH
invoke
GetDlgItemText,hWnd,IDC_PATH,addr @szBuffer,MAX_PATH
mov ebx,eax
invoke
GetDlgItem,hWnd,IDOK
invoke
EnableWindow,eax,ebx
.elseif
ax == IDOK
.if dwOption & F_SEARCHING
or dwOption,F_STOP
.else
invoke
GetDlgItemText,hWnd,IDC_PATH,addr szPath,MAX_PATH
invoke
CreateThread,NULL,0,offset _ProcThread,NULL,\
NULL,addr @dwTemp
invoke
CloseHandle,eax
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
start:
invoke
GetModuleHandle,NULL
mov hInstance,eax
invoke
DialogBoxParam,hInstance,IDD_DIALOG1,NULL,offset _ProcDlgMain,NULL
invoke
ExitProcess,NULL
end start
其他的就没有什么问题了分析到此结束。
下面来介绍一下一些陌生的API函数和结构
结构:
WIN32_FIND_DATA
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; //文件属性
FILETIME ftCreationTime; // 文件创建时间
FILETIME ftLastAccessTime; // 文件最后一次访问时间
FILETIME ftLastWriteTime; // 文件最后一次修改时间
DWORD nFileSizeHigh; // 文件长度高32位
DWORD nFileSizeLow; // 文件长度低32位
DWORD dwReserved0; // 系统保留
DWORD dwReserved1; // 系统保留
TCHAR cFileName[ MAX_PATH ]; // 长文件名
TCHAR cAlternateFileName[ 14 ]; // 8.3格式文件名
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
dwFileAttributes:
该字段可以是下面取值的组合,通过这个字段可以了解找到的是一个文件还是一个子目录,以及其他的文件属性:
FILE_ATTRIBUTE_ARCHIVE---------文件包含归档属性(包含归档属性的文件才可以被删除,修改,拷贝等操作)
FILE_ATTRIBUTE_COMPRESSED--------文件和目录被压缩
FILE_ATTRIBUTE_DIRECTORY---------找到的是一个目录
FILE_ATTRIBUTE_HIDDEN-----------文件包含隐含属性
FILE_ATTRIBUTE_NORMAL---------文件没有其他属性
DILE_ATTRIBUTE_READONLY-------文件包含只读属性
FILE_ATTRIBUTE_SYSTEM---------文件包含系统属性
FILE_ATTRIBUTE_TEMPORARY--------文件是一个临时文件
cFileName:
该字段包含了找到的文件名,但是这个文件名中不包含路径,只有文件名。
下面看一下几个API函数:
FindFirstFile()
功能:
查找指定目录的第一个文件或目录并返回它的句柄
原型:
HANDLE FindFirstFile(
LPCTSTR lpFileName, // 目录名
LPWIN32_FIND_DATA lpFindFileData // 数据缓冲区
);
参数:
lpFileName:
指向字符串的指针用于指定一个有效的目录。
lpFindFileData:
指向一个WIN32_FIND_DATA的指针,用于存放找到文件或目录的信息。
返回值:
如果成功,则返回找到文件或目录的句柄。在FindNextFile和FindClose函数中会用到此句柄。
如果失败,返回INVALID_HANDLE_VALUE。要获得更多的信息调用GetLastError函数。
FindNxtFile()
功能:
可以用来遍历目录或文件时,判断当前目录下是否有下一个目录或文件。
原型:
BOOLFindNextFile(
HANDLE hFindFile, //searchhandle
LPWIN32_FIND_DATA lpFindFileData //databuffer
);
参数:
HANDLE hFindFile搜索的文件句柄 函数执行的时候搜索的是此句柄的下一文件
LPWIN32_FIND_DATA lpFindFileData 指向一个用于保存文件信息的结构体
返回值:
非零表示成功,零表示失败。如不再有与指定条件相符的文件,会将GetLastError设置成ERROR_NO_MORE_FILES
CreateThread()
功能:
该函数在主线程的基础上创建一个新线程
原型:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
SIZE_T dwStackSize, // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreationFlags, // creation option
LPDWORD lpThreadId // thread identifier
);
参数:
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI ThreadProc (LPVOID lpParam) ,格式不正确将无法调用成功。
//也可以直接调用void类型
//但lpStartAddress要这样通过LPTHREAD_START_ROUTINE转换如:(LPTHREAD_START_ROUTINE)MyVoid
//然后在线程声明为:
void MyVoid()
{
return;
}
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
(2)0:表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新线程的id。
返回值:
函数成功,返回线程句柄;函数失败返回false。
下面介绍一下几个指令的意思:
&:
逻辑上表示两者属于缺一不可的关系,还可以表示一个人和另外一个人之意,与and同义。如A&B,表示A与B,A和B。
&&:
x&&y
功能描述: "条件与":x和y均为true,取值是true,否则取值是false