WindowsAPI查缺补漏-文件驱动器目录

碎碎念

硬盘容量=柱面数(磁道数)*磁头数(盘面数)*每磁道扇区数*每扇区字节数。

目录

SetCurrentDirectory

设置当前目录:

1
2
3
BOOL SetCurrentDirectory(
LPCTSTR lpPathName
)

GetCurrentDirectory

获取当前目录:

1
2
3
4
DWORD GetCurrentDirectory(
_In_ DWORD nBufferLength, //缓冲区大小 单位字符
_Out_ LPTSTR lpBuffer //返回当前目录
)

GetFullPathName

获取一个文件完整路径:

1
2
3
4
5
6
DWORD WINAPI GetFullPathName(
_In_ LPCTSTR lpFileName, //文件名称
_In_ DWORD nBufferLength, //缓冲区大小 单位字符
_Out_ LPTSTR lpBuffer, //返回完整路径文件名
_Outptr_opt_ LPTSTR* lpFilePart //返回文件名起始地址指针
)

文件操作

CreateFile

创建或打开文件:

1
2
3
4
5
6
7
8
9
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName, //要创建或打开的文件名称字符串
_In_ DWORD dwDesiredAccess, //文件访问权限
_In_ DWORD dwShareMode, //文件共享模式
_In_opt_ LPSECURITY_ATTRIBUTES lpSecruityAttributes,
_In_ DWORD dwCreationDisposition, //创建或打开标志
_In_ DWORD dwFlagsAndAttributes, //文件标志和系统属性
_In_opt_ HANDLE hTemplateFile //模板文件句柄
) //成功返回文件对象句柄 失败返回INVALID_HANDLE_VALUE

对于lpFileName参数,ANSI路径名限制MAX_PATH个字符,Unicode版本路径名称字符串限制为32767个字符。

对于dwDesiredAccess参数,指定对文件的访问权限,有GENERICE_WRITE和GENERIC_READ。

对于dwShareMode参数,表示文件在被打开后是否允许其他进程或线程再次打开文件:

枚举值 含义
0 独占文件,不允许被再次打开
FILE_SHARE_READ 允许读
FILE_SHARE_WRITE 允许写
FILE_SHARE_DELETE 允许删除

对于dwCreationDisposition参数,决定文件已经存在或不存在所采取的操作,用GetLastError获取错误码。

枚举值 含义
CREATE_NEW 文件不存在时创建一个新文件。已存在则失败,错误码ERROR_FILE_EXISTS
CREATE_ALWAYS 始终创建新文件。成功创建错误码0。已存在但不可写则清空并覆盖,错误码ERROR_ALREADY_EXISTS
OPEN_EXISTING 仅打开已存在文件。不存在失败,错误码ERROR_FILE_NOT_FOUND
OPEN_ALWAYS 始终打开文件。已存在成功,错误码ERROR_ALREADY_EXISTS。有效路径不存在则创建且成功,错误码0
TRUNCATE_EXISTING 打开文件并截断,存在时使其大小为0。不存在则失败,错误码ERROR_FILE_NOT_FOUND

对于dwFlagsAndAttributes参数,分为文件标志和系统属性,文件标志:

枚举值 含义
FILE_ATTRIBUTE_NORMAL 普通文件
FILE_ATTRIBUTE_READONLY 只读
FILE_ATTRIBUTE_HIDDEN 隐藏
FILE_ATTRIBUTE_SYSTEM 操作系统文件
FILE_ATTRIBUTE_ARCHIVE 待备份/删除
FILE_ATTRIBUTE_TEMPORARY 临时存储,尽量在内存中,不久将删除
FILE_ATTRIBUTE_ENCRYPTED 已加密

文件标志:

枚举值 含义
FILE_FLAG_DELETE_ON_CLOSE 关闭句柄后立刻删除文件
FILE_FLAG_OVERLAPPED 异步I/O
FILE_FLAG_NO_BUFFERING 写操作不用系统缓存
FILE_FLAG_WRITE_THROUGH 写操作不通过中间缓存,修改直接写入硬盘

打开已经存在的文件:

1
2
3
4
5
HANDLE hFile;
hFile = CreateFile(TEXT("D:\\Test.txt"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
//函数调用失败
};

新建文件:

1
2
3
4
hFile = CreateFile(TEXT("D:\\Test.txt"), GENERIC_READ | GENERIC_WRTIE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
//函数调用失败
};

关闭文件对象句柄用CloseHandle

ReadFile

从指定文件读取数据:

1
2
3
4
5
6
7
BOOL WINAPI ReadFile(
_In_ HANDLE hFile, //文件句柄
_Out_ LPVOID lpBuffer, //接收文件数据
_In_ DWORD nNumberOfBytesToRead, //要读取的字节数
_Out_opt_ LPDWORD lpNumberOfBytesRead, //实际读到的字节数
_Inout_opt_ LPOVERLAPPED lpOverlapped //异步文件操作 不用就NULL
) //成功TRUE 失败FALSE

当还没读完nNumberOfBytesToRead时读到文件尾,则返回TRUE,但*lpNumberOfBytesRead为0,通过这个判断是否读到文件末尾。

使用异步I/O时,CreateFile的dwFlagsAndAttributes应指定FILE_FLAG_OVERLAPPED。

WriteFile

向指定文件写入数据:

1
2
3
4
5
6
7
BOOL WINAPI WriteFile(
_In_ HANDLE hFIle, //文件句柄
_In_ LPCVOID lpBuffer, //要写入文件的数据缓冲区
_In_ DWORD nNumberOfBytesToWrite, //要写入字节数
_Out_opt_ LPDWORD lpNumberOfBytesWritten, //返回成功写入字节数
_Inout_opt_ LPOVERLAPPED lpOverlapped //异步用 不用NULL
)

FlushFileBuffers

WriteFile写入文件时,写入的数据可能只保存在高速缓存里。为保证所有数据正确写入硬盘,需要用CloseHandle关闭文件句柄,或用这个函数。但把关键数据即时写入硬盘可能需要多次调用这个函数,所以最好是在CreateFile时指定FILE_FLAG_NO_BUFFERING和FILE_FLAG_WRITE_THROUGH标志。

1
2
3
BOOL WINAPI FlushFileBuffers(
_In_ HANDLE hFile
)

SetFilePointerEx

调整文件指针:

1
2
3
4
5
6
BOOL WINAPI SetFilePointerEx(
_In_ HANDLE hFile, //文件句柄
_In_ LARGE_INTEGER liDistanceToMove, //文件指针要移动的字节数
_Out_opt_ PLARGE_INTEGER lpNewFilePointer, //返回新文件指针
_In_ DWORD dwMoveMethod //文件指针移动起点
)

对于dwMoveMethod参数,可以是:

枚举值 含义
FILE_BEGIN 文件开头
FILE_CURRENT 文件指针当前位置
FILE_END 文件末尾

dwMothMethod指定的位置加上liDistanceToMove就是新文件指针位置,liDistanceToMove正数为向文件尾移动,负数向文件头移动。当从文件尾继续向后移动时,表示拓展文件大小。例如:

1
2
3
4
5
6
7
8
9
10
TCHAR szStr[] = TEXT("xxx");
static HANDLE hFile;
LARGE_INTEGER liDistanceToMove = { 900 };
LARGE_INTEGER liNewFilePointer;
hFile = CreateFile(TEXT("D:\\Test.txt"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
SetFilePointerEx(hFile, liDistanceToMove, &liNewFilePointer, FILE_END);
WriteFile(hFile, szStr, _tcslen(szStr) * sizeof(TCHAR), NULL, NULL);
CloseHandle(hFile);
};

需要获取当前文件指针时,把liDistanceToMove值设为0,dwMoveMethod设为FILE_CURRENT,从lpNewFilePointer返回当前文件指针。例如:

1
2
LARGE_INTEGER liDistanceToMove = { 0 };
SetFilePointerEx(hFile, liDistanceToMove, &liNewFilePointer, FILE_CURRENT);

SetEndOfFile

把文件结尾设置为文件指针当前位置,可实现文件截断或扩展。例如设置文件指针为扩展后的大小,并调用该函数即可扩展文件大小。

1
2
3
BOOL WINAPI SetEndOfFile(
_In_ HANDLE hFile
)

例如文件大小扩展为8GB:

1
2
3
4
5
6
7
8
9
static HANDLE hFile;
LARGE_INTEGER liDistanceToMove;
liDistanceToMove.QuadPart = (LONGLONG)8 * 1024 * 1024 * 1024;
LARGE_INTEGER liNewFilePointer;
hFile = CreateFile(TEXT("D:\\Test.txt"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
SetFilePointerEx(hFile, liDistanceToMove, &liNewFilePointer, FILE_BEGIN);
SetEndOfFile(hFile);
};

文件属性

GetFileSizeEx

获取文件大小:

1
2
3
4
5
#include <windows.h>
BOOL WINAPI GetFileSizeEx(
_In_ HANDLE hFile, //文件句柄
_Out_ PLARGE_INTEGER lpFileSize //返回文件大小字节数
)

GetFileType

获取文件类型:

1
2
3
DWORD WINAPI GetFileType(
_In_ HANDLE hFile
)

返回值:

枚举值 含义
FILE_TYPE_UNKNOW 未知文件类型或调用失败。
FILE_TYPE_DISK 磁盘文件。
FILE_TYPE_CHAR 字符文件如LPT设备或控制台。
FILE_TYPE_PIPE 套接字、命名管道或匿名管道。

GetFileTime

获取文件创建/最后访问/最后修改时间:

1
2
3
4
5
6
BOOL WINAPI GetFileTime(
_In_ HANDLE hFile,
_Out_opt_ LPFILETIME lpCreationTime, //文件创建时间
_Out_opt_ LPFILETIME lpLastAccessTime, //最后访问时间
_Out_opt_ LPFILETIME lpLastWriteTime //最后修改时间
)

FILETIME用FileTimeToSystemTime转为SYSTEMTIME结构,这里不讲。

SetFileTime

设置文件创建/最后访问/最后修改时间:

1
2
3
4
5
6
BOOL WINAPI SetFileTime(
_In_ HANDLE hFile,
_Out_opt_ CONST LPFILETIME lpCreationTime, //文件创建时间
_Out_opt_ CONST LPFILETIME lpLastAccessTime, //最后访问时间
_Out_opt_ CONST LPFILETIME lpLastWriteTime //最后修改时间
)

SYSTEMTIME用SystemTimeToFileTime转为FILETIME结构,这里不讲。

GetFileAttributes/SetFileAttributes

获取/设置文件系统属性,文件系统属性指CreateFile的dwFlagsAndAttributes指定的FILE_ATTRIBUTES_*值。已废弃用GetFileInformationByHandle/SetFileInformationByHandle

1
2
3
4
5
6
7
DWORD WINAPI GetFileAttrbutes(
_In_ LPCTSTR lpFileName
) //返回FILE_ATTRIBUTES_*
BOOL WINAPI SetFileAttributes(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwFileAttributes //指定FILE_ATTRIBUTES_*
)

GetFileAttributesEx

GetFileAttributes更全面,已废弃用GetFileInformationByHandle

1
2
3
4
5
BOOL WINAPI GetFileAttributesEx(
_In_ LPCTSTR lpFileName,
_In_ GET_FILEEX_INFO_LEVELS fInfoLevelId,
_Out_ LPVOID lpFileInformation
)

其中GET_FILEEX_INFO_LEVELS结构如下,只能为GetFileExInfoStandard,且lpFileInformation为WIN32_FILE_ATTRIBUTE_DATA结构指针。

1
2
3
4
typedef enum _GET_FILEEX_INFO_LEVELS {
GetFileExInfoStandard,
GetFileExMaxInfoLevel
} GET_FILEEX_INFO_LEVELS;

WIN32_FILE_ATTRIBUTES_DATA结构为:

1
2
3
4
5
6
7
8
typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
DWORD dwFileAttributes; //文件系统属性信息 FILE_ATTRIBUTE_*值的组合
FILETIME ftCreationTime; //文件创建时间
FILETIME ftLastAccessTime; //最后访问时间
FILETIME ftLastWriteTime; //最后修改时间
DWORD nFileSizeHigh; //文件大小高32位
DWORD nFileSizeLow; //文件大小低32位
} WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA;

GetFileInformationByHandle/SetFileInformationByHandle

通过文件句柄后去文件属性信息:

1
2
3
4
BOOL WINAPI GetFileInformationByHandle(
_In_ HANDLE hFile, //文件句柄
_Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation //返回文件信息
)

BY_HANDLE_FILE_INFORMATION比WIN32_FILE_ATTRIBUTE_DATA多了4个字段:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber; //文件所属卷的序列号
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks; //指向该文件的连接数
DWORD nFileIndexHigh; //该文件ID高32位
DWORD nFileIndexLow; //该文件ID低32位
} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION;

当为了确定两个打开的文件句柄是否为同一个文件时,要同时比较卷序列号和文件ID,因为不同逻辑卷上可能有相同ID的文件。

SetFileInformationByHandle略。

复制文件

CopyFile

将现有文件复制到新文件:

1
2
3
4
5
BOOL WINAPI CopyFile(
_In_ LPCTSTR lpExistingFileName, //源文件名
_In_ LPCTSTR lpNewFileName, //目标文件名
_In_ BOOL bFailIfExists
)

当lpExistingFileName不存在则调用失败,GetLastError返回ERROR_FILE_NOT_FOUND。当bFailIfExists为TRUE且lpNewFileName已经存在则调用失败,GetLastError返回ERROR_FILE_EXISTS。当bFailIfExists为FALSE且lpNewFileName已经存在则覆盖存在文件并成功执行,但如果目标文件有FILE_ATTRIBUTE_HIDDEN或FILE_ATTRIBUTE_READONLY属性则调用失败并GetLastError返回ERROR_ACCESS_DENIED。

已废弃用CopyFileEx

CopyFileEx

复制文件。每当一部分复制操作完成时可调用指定的回调函数,在复制期间可以取消正在进行的复制操作,用于复制较大文件。

1
2
3
4
5
6
7
8
BOOL WINAPI CopyFileEx(
_In_ LPCTSTR lpExistingFileName, //源文件名
_In_ LPCTSTR lpNewFileName, //目标文件名
_In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine, //回调函数
_In_opt_ LPVOID lpData, //回调函数参数
_In_opt_ LPBOOL pbCancel, //复制操作时当该指针指向的变量为TRUE时取消复制操作
_In_ DWORD dwCopyFlags //如何复制
)

在复制操作过程中如果将pbCancel指向的变量设置为TRUE,则复制操作取消并返回FALSE,GetLastError返回ERROR_REQUEST_ABORTED,并删除目标文件。

dwCopyFlags可以是以下组合:

枚举值 含义
COPY_FILE_FAIL_IF_EXISTS 见下。
COPY_FILE_ALLOW_DECRYPTED_DESTINATION 复制加密文件时,尝试用源文件密钥对目标文件加密,无法完成则用默认密钥对目标文件加密,都不能完成调用失败且GetLastError返回ERROR_ENCRYPTION_FAILED。即使无法加密也依然完成复制操作。
COPY_FILE_NO_BUFFERING 不使用系统I/O缓存,用于传输非常大的文件。

当lpExistingFileName不存在则调用失败,GetLastError返回ERROR_FILE_NOT_FOUND。当dwCopyFlags指定了COPY_FILE_FAIL_IF_EXISTS且lpNewFileName已经存在则调用失败,GetLastError返回ERROR_FILE_EXISTS。当dwCopyFlags未指定COPY_FILE_FAIL_IF_EXISTS且lpNewFileName已经存在则覆盖存在文件并成功执行,但如果目标文件有FILE_ATTRIBUTE_HIDDEN或FILE_ATTRIBUTE_READONLY属性则调用失败并GetLastError返回ERROR_ACCESS_DENIED。

回调函数格式:

1
2
3
4
5
6
7
8
9
10
11
DWORD CALLBACK CopyProgressRoutine(
_In_ LARGE_INTEGER TotalFileSize, //文件总大小 单位字节
_In_ LARGE_INTEGER TotalBytesTransferred, //已传输的字节总数
_In_ LARGE_INTEGER StreamSize, //当前文件流总大小 单位字节
_In_ LARGE_INTEGER StreamBytesTransferred, //流中已传输的字节总数
_In_ DWORD dwStreamNumber, //流编号
_In_ DWORD dwCallbackReason, //调用回调函数原因
_In_ HANDLE hSourceFile, //源文件句柄
_In_ HANDLE hDestinationFile, //目标文件句柄
_In_opt_ LPVOID lpData //传递过来的回调函数句柄
)

当文件流已建立并即将开始复制,即首次调用回调函数时dwCallbackReason为CALLBACK_STREAM_SWITCH。完成一部分时dwCallbackReason为CALLBACK_CHUNK_FINISHED,一部分一般是1MB,也可能是64KB。

对于回调函数的返回值,一般返回PROGRESS_CONTINUE表示继续。返回PROGRESS_CANCEL或PROGRESS_STOP时取消复制。返回PROGRESS_QUIET时在以后的复制操作过程中停止调用回调函数。

PathFileExists

判断目录或文件是否存在:

1
2
3
BOOL PathFileExists(
_In_ LPCTSTR pszPath
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <windows.h>
#include <Shlwapi.h>
#include "resource.h"
#pragma comment(lib, "Shlwapi.lib")
// 全局变量
HWND g_hwndDlg;
BOOL g_bCancel = FALSE; // 复制操作过程中,如果用户点击了“取消”按钮,设置该变量为TRUE
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数,创建一个新线程负责复制操作
DWORD WINAPI ThreadProc(LPVOID lpParameter);
// CopyFileEx函数的回调函数
DWORD CALLBACK CopyProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred,LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber,DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 初始化源文件、目标文件编辑框
SetDlgItemText(hwndDlg, IDC_EDIT_SOURCE, TEXT("F:\\Test.rar"));
SetDlgItemText(hwndDlg, IDC_EDIT_TARGET, TEXT("F:\\Downloads\\Test.rar"));
// 设置多行编辑控件的缓冲区大小为不限制
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_PROCESS), EM_SETLIMITTEXT, 0, 0);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_COPY: {
// 创建线程进行复制操作
if ((hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL)) != NULL)
CloseHandle(hThread);
break;
};
case IDC_BTN_CANCEL: {
g_bCancel = TRUE;
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
TCHAR szSource[MAX_PATH] = { 0 };
TCHAR szTarget[MAX_PATH] = { 0 };
BOOL bRet = FALSE;
GetDlgItemText(g_hwndDlg, IDC_EDIT_SOURCE, szSource, _countof(szSource));
GetDlgItemText(g_hwndDlg, IDC_EDIT_TARGET, szTarget, _countof(szTarget));
// 指定的源文件是否有效
if (!PathFileExists(szSource)) {
MessageBox(g_hwndDlg, TEXT("指定的源文件不存在!"), TEXT("提示"), MB_OK);
return 0;
};
// 指定的目标文件是否已存在
if (PathFileExists(szTarget)) {
INT nRet = MessageBox(NULL, TEXT("指定的新文件已经存在,是否覆盖目标文件"), TEXT("提示"), MB_OKCANCEL | MB_ICONINFORMATION | MB_DEFBUTTON2);
switch (nRet) {
case IDOK: {
break;
};
case IDCANCEL: {
return 0;
};
};
};
bRet = CopyFileEx(szSource, szTarget, CopyProgressRoutine, NULL, &g_bCancel, 0);
if (!bRet) {
if (GetLastError() == ERROR_REQUEST_ABORTED)
MessageBox(g_hwndDlg, TEXT("用户取消了复制操作,线程函数返回"), TEXT("已取消"), MB_OK);
}
else
MessageBox(g_hwndDlg, TEXT("已经成功复制了文件,线程函数返回"), TEXT("已成功"), MB_OK);
g_bCancel = FALSE;
return 0;
};
DWORD CALLBACK CopyProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData) {
HWND hwndEdit = GetDlgItem(g_hwndDlg, IDC_EDIT_PROCESS);
TCHAR szBuf[256] = { 0 };
// 实时显示复制进度
wsprintf(szBuf, TEXT("文件总大小:%I64X\t已传输:%I64X\t文件流总大小:%I64X\t已传输:%I64X\t流编号:%d\t\n"), TotalFileSize.QuadPart, TotalBytesTransferred.QuadPart, StreamSize.QuadPart, StreamBytesTransferred.QuadPart, dwStreamNumber);
SendMessage(hwndEdit, EM_SETSEL, -1, -1);
SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 继续复制操作
return PROGRESS_CONTINUE;
};

移动与删除

MoveFile

移动一个文件或目录,已废弃用MoveFileEx

1
2
3
4
BOOL WINAPI MoveFile(
_In_ LPCTSTR lpExistingFileName, //源文件/目录
_In_ LPCTSTR lpNewFileName //目标文件/目录
)

源文件/目录不存在则调用失败,GetLastError返回ERROR_FILE_NOT_FOUND。目标文件/目录已存在则调用失败,文件时GetLastError返回ERROR_FILE_EXISTS ,目录时返回ERROR_ALREADY_EXISTS。目标目录/文件上一层目录不存在时调用失败,GetLastError返回ERROR_PATH_NOT_FOUND。移动目录时必须在同一个逻辑驱动器中,否则调用失败,GetLastError返回ERROR_ACCESS_DENIED。

MoveFileEx

移动一个文件或目录:

1
2
3
4
5
BOOL WINAPI MoveFile(
_In_ LPCTSTR lpExistingFileName, //源文件/目录
_In_ LPCTSTR lpNewFileName //目标文件/目录
_In_ DWORD dwFlags //移动选项标志
)

源文件/目录不存在则调用失败,GetLastError返回ERROR_FILE_NOT_FOUND。目标文件/目录已存在则调用失败,文件时GetLastError返回ERROR_FILE_EXISTS ,目录时返回ERROR_ALREADY_EXISTS。目标目录/文件上一层目录不存在时调用失败,GetLastError返回ERROR_PATH_NOT_FOUND。默认情况下移动目录/文件时必须在同一个逻辑驱动器中,否则调用失败,GetLastError返回ERROR_NOT_SAME_DEVICE。

dwFlags选项:

枚举值 含义
MOVEFILE_REPLACE_EXISTING 目标文件存在时覆盖。
MOVEFILE_COPY_ALLOWED 允许把一个文件移动到其他逻辑驱动器中。
MOVEFILE_DELAY_UNTIL_REBOOT 下次重启系统后移动文件/目录,不能与MOVEFILE_COPY_ALLOWED同时使用。

对于MOVEFILE_DELAY_UNTIL_REBOOT选项,MoveFileEx向注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\PendingFileRenameOperations中添加lpExistingFileName和lpNewFileName,并加上内核“/??/”前缀。当lpNewFileName指定为NULL时重启系统能够后删除lpExistingFileName。

MoveFileWithProgress

功能与MoveFileEx相同,指定一个接收进度通知的回调函数,具体同上且略。

1
2
3
4
5
6
7
BOOL WINAPI MoveFileWithProgress(
_In_ LPCTSTR lpExistingFileName, //源文件/目录
_In_opt_ LPCTSTR lpNewFileName, //目标文件/目录
_In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine, //回调函数
_In_opt_ LPVOID lpData, //传递给回调函数的参数
_In_ DWORD dwFlags //移动选项标志
)

DeleteFile

删除一个文件:

1
2
3
BOOL WINAPI DeleteFile(
_In_ LPCTSTR lpFileName
)

删除只读文件时需要用SetFileAttributes删除FILE_ATTRIBUTE_READONLY属性,否则调用失败且GetLastError返回ERROR_ACCESS_DENIED。删除一个处于打开状态的文件时需要先用CloseHandle关闭文件,否则调用失败且GetLastError返回ERROR_SHARING_VIOLATION,但如果CreateFile时指定FILE_SHARE_DELETE则无所谓。

无缓冲I/O

使用FILE_FLAG_NO_BUFFERING标志时,读写文件的偏移量必须为扇区大小整数倍,读写的字节数必须是扇区大小的整数倍,缓冲区地址必须是物理扇区大小的整数倍。

机械硬盘物理扇区大小为512字节,固态硬盘为4KB,所以引入逻辑扇区大小的概念。逻辑扇区大小是逻辑寻址单位,物理扇区大小是硬盘原子写入单位。即临时解决兼容性的方案是固态硬盘逻辑扇区为512字节。

一般页面大小大于扇区大小,所以页面对齐的内存也是扇区对齐的。为保证读写文件的缓冲区地址一定是物理扇区大小整数倍,用VirtualAlloc分配缓冲区,该函数保证内存区域起始地址是分配粒度64KB整数倍。

可用GetDiskFreeSpace获取逻辑扇区大小。也可用DeviceIoControl发送IOCTL_DISK_GET_DRIVE_GEOMETRY_EX控制代码获取逻辑扇区大小,发送IOCTL_STORAGE_QUERY_PROPERTY获取物理扇区大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <Windows.h>
#include <strsafe.h>
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
HANDLE hDevice = INVALID_HANDLE_VALUE;
DISK_GEOMETRY_EX diskGeometryEx = { 0 };
STORAGE_PROPERTY_QUERY storagePropertyQuery;
storagePropertyQuery.PropertyId = StorageAccessAlignmentProperty;
storagePropertyQuery.QueryType = PropertyStandardQuery;
STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR saad = { sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR) };
DWORD dwBytesReturned;
TCHAR szBuf[512] = { 0 };
TCHAR szTemp[256] = { 0 };
// 驱动器0的逻辑扇区大小和物理扇区大小
hDevice = CreateFile(TEXT("\\\\.\\PhysicalDrive0"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice != INVALID_HANDLE_VALUE) {
DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &diskGeometryEx, sizeof(diskGeometryEx), &dwBytesReturned, NULL);
DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &storagePropertyQuery, sizeof(storagePropertyQuery), &saad, sizeof(saad), &dwBytesReturned, NULL);
StringCchPrintf(szBuf, _countof(szBuf), TEXT("驱动器0:\r\n逻辑扇区大小:%d\r\n物理扇区大小:%d\r\n\r\n"), diskGeometryEx.Geometry.BytesPerSector, saad.BytesPerPhysicalSector);
CloseHandle(hDevice);
};
// 驱动器1的逻辑扇区大小和物理扇区大小
hDevice = INVALID_HANDLE_VALUE;
diskGeometryEx = { 0 };
saad = { sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR) };
hDevice = CreateFile(TEXT("\\\\.\\PhysicalDrive1"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice != INVALID_HANDLE_VALUE) {
DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &diskGeometryEx, sizeof(diskGeometryEx), &dwBytesReturned, NULL);
DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &storagePropertyQuery, sizeof(storagePropertyQuery), &saad, sizeof(saad), &dwBytesReturned, NULL);
StringCchPrintf(szTemp, _countof(szTemp), TEXT("驱动器1:\r\n逻辑扇区大小:%d\r\n物理扇区大小:%d\r\n\r\n"), diskGeometryEx.Geometry.BytesPerSector, saad.BytesPerPhysicalSector);
StringCchCat(szBuf, _countof(szBuf), szTemp);
CloseHandle(hDevice);
};
MessageBox(NULL, szBuf, TEXT("驱动器扇区大小"), MB_OK);
return 0;
};

其中各结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _DISK_GEOMETRY_EX {
DISK_GEOMETRY Geometry; // DISK_GEOMETRY结构
LARGE_INTEGER DiskSize; // 硬盘大小,以字节为单位
BYTE Data[1]; // 附加数据
} DISK_GEOMETRY_EX, * PDISK_GEOMETRY_EX;
typedef struct __WRAPPED__ _DISK_GEOMETRY {
LARGE_INTEGER Cylinders; // 柱面数
MEDIA_TYPE MediaType; // 设备类型,枚举值,例如软盘、硬盘、U盘等
DWORD TracksPerCylinder; // 磁头数(盘面数)
DWORD SectorsPerTrack; // 每磁道扇区数
DWORD BytesPerSector; // 每扇区字节数
} DISK_GEOMETRY, * PDISK_GEOMETRY;
typedef struct _STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR {
DWORD Version; // 该结构的大小
DWORD Size; // 返回的数据大小
DWORD BytesPerCacheLine; //
DWORD BytesOffsetForCacheAlignment; //
DWORD BytesPerLogicalSector; // 每逻辑扇区字节数
DWORD BytesPerPhysicalSector; // 每物理扇区字节数
DWORD BytesOffsetForSectorAlignment;//
} STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR, * PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR;

逻辑驱动器和目录

SetVolumeLabel

为一个卷(逻辑驱动器)设置卷标:

1
2
3
4
BOOL WINAPI SetVolumeLabel(
_In_opt_ LPCTSTR lpRootPathName, //逻辑驱动器根目录
_In_opt_ LPCTSTR lpVolumeName //新卷标名称 最大32字符
)

例子:

1
SetVolumeLabel(TEXT("C:\\"), TEXT("系统"));

GetVolumeInformation

获取一个逻辑驱动器详细信息:

1
2
3
4
5
6
7
8
9
10
BOOL WINAPI GetVolumeInformation(
_In_opt_ LPCTSTR lpRootPathName, //逻辑驱动器根目录
_Out_opt_ LPTSTR lpVolumeNameBuffer, //返回卷标名称
_In_ DWORD nVolumeNameSize, //卷标名称缓冲区大小 单位字符
_Out_opt_ LPDWORD lpVolumeSerialNumber, //卷序列号
_Out_opt_ LPDWORD lpMaximumComponentLength, //文件系统支持的最大文件组名长度 通常255
_Out_opt_ LPDWORD lpFileSystemFlags, //某些文件系统标志
_Out_opt_ LPTSTR lpFileSystemNameBuffer, //文件系统名称缓冲区
_In_ DWORD nFileSystemNameSize //文件系统名称缓冲区大小 单位字符
)

卷序列号是格式化硬盘时系统分配的一个序号,每次格式化后都可能变。不变的是硬盘制造商分配的硬盘序列号。

文件组名长度即完整路径文件名中以反斜杠分割的每一部分。

lpFileSystemFlags常用标志:

枚举值 含义
FILE_CASE_SENSITIVE_SEARCH 支持区分大小写
FILE_CASE_PRESERVED_NAMES 保存文件/目录时保留大小写
FILE_UNICODE_ON_DISK 文件名支持Unicode
FILE_FILE_COMPRESSION 支持文件压缩
FILE_SUPPORTS_ENCRYPTION 支持文件加密
FILE_READ_ONLY_VOLUME 只读
FILE_VOLUME_IS_COMPRESSED 压缩卷

lpFileSystemNameBuffer可能返回FAT32、NTFS、ReFS等。

GetLogicalDrives

获取系统中所有可用的逻辑驱动器:

1
DWORD WINAPI GetLogicalDrives(VOID); //返回可用逻辑驱动器位掩码 失败返回0

位0为驱动器A,位1位驱动器B,以此类推。

GetLogicalDriveStrings

返回字符串类型逻辑驱动器列表:

1
2
3
4
DWORD WINAPI GetLogicalDriveStrings(
_In_ DWORD nBufferLength, //缓冲区大小 单位字符 不好阔终止空字符
_Out_ LPTSTR lpBuffer //缓冲区指针
) //成功返回缓冲区字符个数

一般第一次调用时nBufferLength位0,lpBuffer为NULL,返回所需缓冲区大小,包括终止空字符,分配合适大小后第二次调用。

GetDriveType

确定逻辑驱动器类型:

1
2
3
UINT WINAPI GetDriveType(
_In_opt_ LPCTSTR lpRootPathName //根目录
)

返回值:

枚举值 含义
DRIVE_UNKNOW 无法确定
DRIVE_NO_ROOT_DIR lpRootPathName无效
DRIVE_REMOVABLE 可移动介质如U盘
DRIVE_FIXED 固定介质如固态/可移动硬盘
DRIVE_REMOTE 远程(网络)驱动器
DRIVE_CDROM CD-ROM光盘驱动器
DRIVE_RAMDISK RAM磁盘

GetDiskFreeSpace

获取一个逻辑驱动器总容量或空闲磁盘空间,已废弃用GetDiskFreeSpaceEx

1
2
3
4
5
6
7
8
9
BOOL WINAPI GetDiskFreeSpace(
_In_ LPCTSTR lpRootPathName, //逻辑驱动器根目录
_Out_ LPDWORD lpSectorsPerCluster, //每簇扇区数
_Out_ LPDWORD lpBytesPerSector, //每扇区字节数
_Out_ LPDWORD lpNumberOfFreeClusters, //空闲簇总数
_Out_ LPDWORD lpTotalNumberOfClusters //簇总数
)
//驱动器总容量为簇总数*每簇扇区数*每扇区字节数
//空闲磁盘空间为空闲簇总数*每簇扇区数*每扇区字节数

GetDiskFreeSpaceEx

解决GetDiskFreeSpace计算麻烦的问题:

1
2
3
4
5
6
BOOL WINAPI GetDiskFreeSpaceEx(
_In_opt_ LPCTSTR lpDirectoryName, //驱动器根目录/子目录
_Out_opt_ PULARGE_INTEGER lpFreeBytesAvailable, //空闲磁盘空间
_Out_opt_ PULARGE_INTEGER lpTotalNumberOfBytes, //驱动器总容量
_Out_opt_ PULARGE_INTEGER lpTotalNumberOfFreeBytes //空闲磁盘空间
)

目录操作

CreateDirectory

创建一个目录:

1
2
3
4
BOOL WINAPI CreateDirectory(
_In_ LPCTSTR lpPathName, //要创建的目录名
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes //安全属性结构
)

指定的目录已存在则调用失败,GetLastError返回ERROR_ALREADY_EXISTS。当将被创建的目录名与该目录下已有文件名重名时调用失败,GetLastError返回ERROR_ALREADY_EXISTS。上层目录中有不存在的调用失败,GetLastError返回ERROR_PATH_NOT_FOUND。

RemoveDirectory

删除一个目录:

1
2
3
BOOL WINAPI RemoveDirectory(
_In_ LPCTSTR lpPathName //要删除的目录名
)

删除的不是空目录时调用失败,GetLastError返回ERROR_DIR_NOT_EMPTY。注意这是直接删除而不是移动到回收站。

例子

创建和删除目录,当上级目录不存在时自动创建,当删除的目录不为空时递归删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 创建目录
BOOL MyCreateDirectory(LPTSTR lpPathName) {
TCHAR szBuf[MAX_PATH] = { 0 };
LPTSTR lp;
// 首先判断目录是否已经存在
if (PathFileExists(lpPathName))
return TRUE;
// 如果是这样F:\Downloads\Web\JavaWeb\末尾有一个\反斜杠,则去掉
if (lpPathName[_tcslen(lpPathName) - 1] == TEXT('\\'))
lpPathName[_tcslen(lpPathName) - 1] = TEXT('\0');
// 递归创建上一级目录
lp = _tcsrchr(lpPathName, TEXT('\\'));
for (INT i = 0; i < lp - lpPathName; i++)
szBuf[i] = *(lpPathName + i);
MyCreateDirectory(szBuf);
// 创建相应的目录
if (CreateDirectory(lpPathName, NULL))
return TRUE;
return FALSE;
};
// 删除目录
BOOL MyRemoveDirectory(LPTSTR lpPathName) {
TCHAR szDirectory[MAX_PATH] = { 0 };
TCHAR szSearch[MAX_PATH] = { 0 };
TCHAR szDirFile[MAX_PATH] = { 0 };
HANDLE hFindFile;
WIN32_FIND_DATA fd = { 0 };
// 如果路径结尾没有\则添加一个
StringCchCopy(szDirectory, _countof(szDirectory), lpPathName);
if (szDirectory[_tcslen(szDirectory) - 1] != TEXT('\\'))
StringCchCat(szDirectory, _countof(szDirectory), TEXT("\\"));
// 拼接搜索字符串
StringCchCopy(szSearch, _countof(szSearch), szDirectory);
StringCchCat(szSearch, _countof(szSearch), TEXT("*.*"));
// 递归遍历目录
hFindFile = FindFirstFile(szSearch, &fd);
if (hFindFile != INVALID_HANDLE_VALUE) {
do {
// 如果是代表当前目录的.或者代表上一级目录的..则跳过
if (_tcscmp(fd.cFileName, TEXT(".")) == 0 || _tcscmp(fd.cFileName, TEXT("..")) == 0)
continue;
// 找到的文件或子目录名称
StringCchCopy(szDirFile, _countof(szDirFile), szDirectory);
StringCchCat(szDirFile, _countof(szDirFile), fd.cFileName);
// 处理本次找到的文件或子目录
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
// 如果是目录,递归调用
MyRemoveDirectory(szDirFile);
else {
// 删除只读属性
if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
SetFileAttributes(szDirFile, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
// 删除文件
DeleteFile(szDirFile);
};
} while (FindNextFile(hFindFile, &fd));
// 关闭查找句柄
FindClose(hFindFile);
};
// 删除相应的目录
if (RemoveDirectory(lpPathName))
return TRUE;
return FALSE;
};

GetWindowsDirectory

获取Windows操作系统安装目录,通常是C:\Windows,末尾没有“\”,已废弃用SHGetKnownFolderPath

1
2
3
4
UINT GetWindowsDirectory(
_Out_ LPTSTR lpBuffer,
_In_ UINT uSize //指定缓冲区大小 单位字符 通常MAX_PATH
) //成功返回复制到缓冲区中字节数 不包含终止空字符 失败返回0

GetSystemDirectory

获取系统目录,通常是C:\Windows\system32,末尾没有“\”,已废弃用SHGetKnownFolderPath

1
2
3
4
UINT GetSystemDirectory(
_Out_ LPTSTR lpBuffer,
_In_ UINT uSize
)

GetTempPath

获取临时目录,临时目录在磁盘空间不足时自动删除,通常C:\Users\用户名\AppData\Local\Temp\,末尾有“\”,已废弃用SHGetKnownFolderPath

1
2
3
4
UINT GetTempPath(
_In_ UINT uSize,
_Out_ LPTSTR lpBuffer
)

该函数按照这个顺序获取临时目录:TMP环境变量、TEMP环境变量、USERPROFILE环境变量、Windows目录。

GetUserName

获取系统当前登录用户名:

1
2
3
4
BOOL WINAPI GetUserName(
_Out_ LPTSTR lpBuffer, //返回用户名的缓冲区 最大字符个数UNLEN
_Inout_ LPDWORD lpnSize //缓冲区大小 单位字符
) //返回复制到缓冲区中字符数 包括终止空字符

UNLEN在Lmcons.h中定义。

SHGetKnownFolderPath

获取默写目录完整路径。

1
2
3
4
5
6
7
#include <Shlobj.h>
HRESULT SHGetKnownFolderPath(
_In_ REFKNOWNFOLDERID rfid, //通过该GUID指定一个目录
_In_ DWORD dwFlags, //一些标志 一般0
_In_opt_ HANDLE hToken, //用户访问令牌 一般NULL
_Out_ PWSTR *ppszPath //返回完整路径
) //成功S_OK 失败E_FAIL或E_INVALIDARG 可用SUCCEEDED宏判断

GUID指全局唯一标识符,是一个128位/16字节的二进制数,格式XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,它的生成使用网卡MAC、纳秒级时间、芯片ID码等,保证每次生成的GUID永远不会重复,即一个GUID在同一时空所有机器上都是唯一的。

其中REFKNOWNFOLDERID结构如下:

1
2
3
4
5
6
7
8
#define REFKNOWNFOLDERID const KNOWNFOLDERID &
typedef GUID FOLDERTYPEID;
typedef struct _GUID {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[ 8 ];
} GUID;

常用目录对应的GUID:

GUID 含义
F38BF404-1D43-42F2-9305-67DE0B28FC23 Windows目录,如C:\Windows
1AC14E77-20E7-4E5D-B744-2EB1AE5198B7 系统目录,如C:\Windows\system32
0762D272-C50A-4BB0-A382-697DCD729B80 用户目录,如C:\Users
5E6C858F-0E22-4760-9AFE-EA3317B67173 当前用户目录,如C:\Users\用户名
B4BFCC3A-DB2C-424C-B029-7FE99A87C641 用户桌面文件,如C:\Users\用户名\Desktop
FDD39AD0-238F-46AF-ADB4-6C85480369C7 我的文档,如C:\Users\用户名\Documents
905E63B6-C1BF-494E-B29C-65B732D3D21A 应用程序安装目录,如C:\Program Files(x86)和C:\Program Files
62AB5D82-FDC1-4DC3-A9DD-070D1D495D97 所有用户应用程序数据目录,如C:\ProgramData
F1B32785-6FBA-4 当前用户应用程序数据目录(配置/临时文件),如C:\Users\用户名\AppData\Local
3EB685DB-65F9-4CF6-A03A-E3EF65729F3D 当前用户应用程序数据目录(漫游用户配置文件),如C:\Users\用户名\AppData\Roaming
8983036C-27C0-404B-8F08-102D10DCFD74 鼠标右键发送到目录,如C:\Users\用户名\AppData\Roaming\Microsoft\Windows\SendTo
82A5EA35-D9CD-47C5-9629-E15D2F714E6E 所有用户开机自动启动程序目录,如C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
B97D20BB-F46A-4C97-BA10-5E3608430854 当前用户开机自动启动程序目录,如C:\Users\用户名\AppData\Roadming\Microsoft\Windows\Start Menu\Porgrams\Startup
374DE290-123F-4565-9164-39C4925E467B 用户下载文件夹,如C:\Users\ 用户名\Downloads
FD228CB7-AE11-4AE3-864C-16F3910AB8FE 字体文件夹,如C:\Windows\Fonts

例如获取我的文档目录完整路径:

1
2
3
4
5
6
7
8
9
10
11
#include <Rpcdce.h>
#pragma comment(lib,"Rpcrt4.lib") //UuidFromString要这俩
GUID guid;
PWSTR lpPath;
HRESULT hResult;
UuidFromString((RPC_WSTR)TEXT("FDD39AD0-238F-46AF-ADB4-6C85480369C7"), &guid); //也可以直接对guid结构体赋值
hResult = SHGetKnownFolderPath(guid, 0, NULL, &lpPath);
if (SUCCEEDED(hResult)) {
MessageBox(hwndDlg, lpPath, TEXT("文档目录完整路径"), MB_OK);
CoTaskMemFree(lpPath);
};

CoTaskMemFree释放lpPath:

1
2
3
VOID CoTaskMemFree(
_In_opt_ LPVOID pv
)

环境变量

GetEnvironmentStrings

获取指向调用进程的环境块的指针。默认情况下,子进程继承父进程环境变量,由命令行启动的程序继承cmd进程的环境变量。

1
LPTSTR WINAPI GetEnvironmentStrings(VOID) //成功返回指针 失败返回NULL

环境快中环境变量组个数如下:

1
2
3
4
Var1=Value1\0
Var2=Value2\0
Var3=Value3\0
...

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndEdit;
LPTSTR lpEnvironmentStrings;
switch (uMsg) {
case WM_INITDIALOG: {
hwndEdit = GetDlgItem(hwndDlg, IDC_EDIT_ENV);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_LOOK: {
SetWindowText(hwndEdit, TEXT(""));
lpEnvironmentStrings = GetEnvironmentStrings();
while (lpEnvironmentStrings[0] != TEXT('\0')) {
SendMessage(hwndEdit, EM_SETSEL, -1, -1);
SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)lpEnvironmentStrings);
SendMessage(hwndEdit, EM_SETSEL, -1, -1);
SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\n"));
lpEnvironmentStrings += _tcslen(lpEnvironmentStrings) + 1;
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

GetEnvironmentVariable

获取当前进程指定环境变量的值:

1
2
3
4
5
DWORD WINAPI GetEnvironmentVariable(
_In_opt_ LPCTSTR lpName, //环境变量名
_Out_opt_ LPTSTR lpBuffer, //接收环境变量值的缓冲区
_In_ DWORD nSize //缓冲区大小 最大32767字符
) //成功返回复制到缓冲区中字符数 不包括终止空字符 失败返回0

可用两次调用法。

SetEnvironmentVariable

设置当前进程中指定环境变量的值:

1
2
3
4
BOOL WINAPI SetEnvironmentVariable(
_In_ LPCTSTR lpName, //环境变量名
_In_opt_ LPCTSTR lpValue //环境变量的值 最大32767字符
)

当lpName指定的不存在且lpValue不为NULL,则创建该环境变量。当lpName指定一个环境变量且lpValue为NULL,则删除该环境变量。注意这个函数只更改该进程环境变量,其他进程的不会改变,系统环境变量也不会改变。

系统环境变量与注册表HEKY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment下一一对应,修改系统环境变量可操作注册表,然后广播WM_SETTINGCHANGE消息(lParam为字符串“Environment”),其他应用程序收到该系统信息更改通知。

FreeEnvironmentStrings

释放环境块;

1
2
3
BOOL WINAPI FreeEnvironmentStrings(
_In_ LPTSTR lpszEnvironmentBlock //当前进程环境块
)

ExpandEnvironmentStrings

展开环境变量字符串,如“%USERPROFILE%”等:

1
2
3
4
5
DWORD WINAPI ExpandEnvironmentStrings(
_In_ LPCTSTR lpSrc, //包含环境变量字符串的缓冲区
_Out_opt_ LPTSTR lpDst, //返回展开结果的缓冲区
_In_ DWORD nSize //lpDst大小 单位字符
)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndEdit;
LPCTSTR lpSrc[] = { TEXT("SystemDrive\t= %SystemDrive%"),TEXT("windir\t\t= %windir%"),TEXT("TEMP\t\t= %TEMP%"),TEXT("ProgramFiles\t= %ProgramFiles%"),TEXT("USERNAME\t= %USERNAME%"),TEXT("USERPROFILE\t= %USERPROFILE%"),TEXT("ALLUSERSPROFILE\t= %ALLUSERSPROFILE%"),TEXT("APPDATA\t\t= %APPDATA%"),TEXT("LOCALAPPDATA\t= %LOCALAPPDATA%") };
TCHAR szDst[BUFFER_SIZE] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
hwndEdit = GetDlgItem(hwndDlg, IDC_EDIT_ENV);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_LOOK: {
for (INT i = 0; i < _countof(lpSrc); i++) {
ExpandEnvironmentStrings(lpSrc[i], szDst, BUFFER_SIZE);
SendMessage(hwndEdit, EM_SETSEL, -1, -1);
SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szDst);
SendMessage(hwndEdit, EM_SETSEL, -1, -1);
SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\n"));
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

SHFileOperation

这函数还蛮重要的,单独拎出来一章记。

用于复制、移动、重命名或删除一个文件或目录。

1
2
3
INT SHFileOperation(
_Inout_ LPSHFILEOPSTRUCT lpFileOp
) //成功0 失败非0

其中SHFILEOPSTRUCT结构包含操作类型和所需信息:

1
2
3
4
5
6
7
8
9
10
typedef struct _SHFILEOPSTRUCT{
HWND hwnd; //窗口句柄
UINT wFunc; //指定执行哪个操作
PCZZSTR pFrom; //源文件/目录名
PCZZSTR pTo; //目标文件/目录名
FILEOP_FLAGS fFlags; //操作标志
BOOL fAnyOperationsAborted; //是否被用户取消
LPVOID hNameMappings; //通常NULL
PCTSTR lpszProgressTitle; //通常NULL
} SHFILEOPSTRUCT, *LPSHFILEOPSTRUCT;

wFunc可以是:FO_COPY复制、FO_MOVE移动、FO_RENAME重命名、FO_DELETE删除。

pFrom和pTo都可以同时指定若干个完整路径名,但每个完整路径名后必须手动添加终止空字符,pTo在复制和移动时不存在时自动创建。

fFlags有些常用设置:

枚举值 含义
FOF_RENAMEONCOLLISION 目标未知存在同名文件/目录时自动指定新文件/目录名
FOF_ALLOWUNDO 删除文件/目录时搁回收站
FOF_MULTIDESTFILES 指定多个源/目标文件/目录
FOF_NOCONFIRMMKDIR 需要创建新目录时不弹出对话框提示是否创建
FOF_SIMPLEPROGRESS 显示一个操作进度对话框,标题为lpszProgressTitle
FOF_SILENT 不显示操作进度对话框
FOF_NOERRORUI 发生错误时不弹出对话框
FOF_NOCONFIRMATION 弹出所有对话框全部答复确定
FOF_NO_UI 不显示任何对话框

fAnyOperationsAborted为返回值,当操作在完成前被用户终止则为TRUE,否则FALSE。

监视目录变化

ReadDirectoryChangesW

监视一个目录中文件/目录变化信息:

1
2
3
4
5
6
7
8
9
10
BOOL WINAPI ReadDirectoryChagesW(
_In_ HANDLE hDirectory, //要监视的目录句柄
_Out_ LPVOID lpBuffer, //返回目录变化信息的缓冲区
_In_ DWORD nBufferLength, //lpBuffer缓冲区大小 单位字节
_In_ BOOL bWatchSubtree, //TRUE同时监控子目录 FALSE不监控
_In_ DWORD dwNotifyFilter, //监视类型
_Out_opt_ LPDWORD lpBytesReturned, //返回写入lpBuffer的字节数
_Inout_opt_ LPOVERLAPPED lpOverlapped, //异步操作用
_In_opt_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //完成例程指针
)

对于hDirectory需要获取该目录FILE_LIST_DIRECTORY访问权限。用CreateFile指定FILE_FLAG_BACKUP_SEMANTICS标志来获取一个目录句柄。

lpBuffer返回后被解析为FILE_NOTIFY_INFORMATION结构。

dwNotifyFilter由常用监视类型,对于同步操作,发生指定事件时函数才返回。

枚举值 含义
FILE_NOTIFY_CHANGE_FILE_NAME 文件名
FILE_NOTIFY_CHANGE_DIR_NAME 目录名
FILE_NOTIFY_CHANGE_ATTRIBTUES 属性
FILE_NOTIFY_CHANGE_SIZE 大小
FILE_NOTIFY_CHANGE_LAST_WRITE 最后写入时间
FILE_NOTIFY_CHANGE_LAST_ACCESS 最后访问时间
FILE_NOTIFY_CHANGE_CREATION 创建时间
FILE_NOTIFY_CHANGE_SECURITY 安全描述符

同步操作时可将lpBuffer信息解析为FILE_NOTIFY_INFORMATION结构:

1
2
3
4
5
6
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset; //下一个FILE_NOTIFY_INFORMATION结构偏移地址 只重命名时用
DWORD Action; //发生变化的类型
DWORD FileNameLength; //FileName大小 单位字节
WCHAR FileName[1]; //可变长度文件/目录名
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

对于Action字段表示发生变化的类型:

枚举值 含义
FILE_ACTION_ADDED 新建
FILE_ACTION_REMOVED 删除
FILE_ACTION_MODIFIED 时间戳、大小、属性等修改或创建
FILE_ACTION_RENAMED_OLD_NAME
FILE_ACTION_RENAMED_NEW_NAME

对于FILE_ACTION_RENAMED_OLD_NAME,第一个FILE_NOTIFY_INFORMATION的FileName为旧名称。NextEntryOffset指向的结构的FileName为新名称,Action为FILE_ACTION_RENAMED_NEW_NAME。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <windows.h>
#include <shlwapi.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment(lib, "Shlwapi.lib")
// 自定义消息
#define WM_DIRECTORYCHANGES (WM_APP + 1)
// 全局变量
HWND g_hwndDlg;
BOOL g_bStarting; // 工作线程开始、结束标志
TCHAR g_szShowChanges[1024]; // 显示指定目录中文件、子目录变化结果所用的缓冲区
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndEditChanges;
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 多行编辑控件窗口句柄
hwndEditChanges = GetDlgItem(hwndDlg, IDC_EDIT_CHANGES);
// 初始化监视目录编辑框
SetDlgItemText(hwndDlg, IDC_EDIT_PATH, TEXT("C:\\"));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
// 创建线程,开始监视目录变化
g_bStarting = TRUE;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread)
CloseHandle(hThread);
break;
};
case IDC_BTN_STOP: {
g_bStarting = FALSE;
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_DIRECTORYCHANGES: {
// 处理自定义消息,显示g_szShowChanges中的目录变化结果
SendMessage(hwndEditChanges, EM_SETSEL, -1, -1);
SendMessage(hwndEditChanges, EM_REPLACESEL, TRUE, (LPARAM)g_szShowChanges);
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
TCHAR szPath[MAX_PATH] = { 0 }; // 获取监视目录编辑控件中的路径
HANDLE hDirectory = INVALID_HANDLE_VALUE; // 要监视的目录的句柄
TCHAR szBuffer[1024] = { 0 }; // 返回目录变化信息的缓冲区
DWORD dwBytesReturned; // 实际写入到缓冲区的字节数
PFILE_NOTIFY_INFORMATION pFNI, pFNINext;
TCHAR szFileName[MAX_PATH], szFileNameNew[MAX_PATH];
// 清空多行编辑控件
SetDlgItemText(g_hwndDlg, IDC_EDIT_CHANGES, TEXT(""));
// 打开目录
GetDlgItemText(g_hwndDlg, IDC_EDIT_PATH, szPath, _countof(szPath));
hDirectory = CreateFile(szPath, /*GENERIC_READ | GENERIC_WRITE | */FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hDirectory == INVALID_HANDLE_VALUE) {
MessageBox(g_hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("Error"), MB_OK);
return 0;
};
while (g_bStarting) {
if (!PathFileExists(szPath)) {
MessageBox(g_hwndDlg, TEXT("监视目录文件夹已被删除"), TEXT("Error"), MB_OK);
return 0;
};
// 对于同步操作,直到指定目录中的文件、目录发生变化时,ReadDirectoryChangesW函数才返回 因此使用异步操作比较恰当一些
ZeroMemory(szBuffer, sizeof(szBuffer));
ReadDirectoryChangesW(hDirectory, szBuffer, sizeof(szBuffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY, &dwBytesReturned, NULL, NULL);
pFNI = (PFILE_NOTIFY_INFORMATION)szBuffer;
ZeroMemory(szFileName, sizeof(szFileName));
ZeroMemory(szFileNameNew, sizeof(szFileNameNew));
memcpy_s(szFileName, sizeof(szFileName), pFNI->FileName, pFNI->FileNameLength);
if (pFNI->NextEntryOffset) {
pFNINext = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pFNI + pFNI->NextEntryOffset);
memcpy_s(szFileNameNew, sizeof(szFileNameNew), pFNINext->FileName, pFNINext->FileNameLength);
};
// 工作线程把目录变化结果写入g_szShowChanges中
ZeroMemory(g_szShowChanges, sizeof(g_szShowChanges));
switch (pFNI->Action) {
case FILE_ACTION_ADDED: {
wsprintf(g_szShowChanges, TEXT("新建文件、目录:%s\n"), szFileName);
PostMessage(g_hwndDlg, WM_DIRECTORYCHANGES, 0, 0);
break;
};
case FILE_ACTION_REMOVED: {
wsprintf(g_szShowChanges, TEXT("删除文件、目录:%s\n"), szFileName);
PostMessage(g_hwndDlg, WM_DIRECTORYCHANGES, 0, 0);
break;
};
case FILE_ACTION_MODIFIED: {
wsprintf(g_szShowChanges, TEXT("修改文件、目录:%s\n"), szFileName);
PostMessage(g_hwndDlg, WM_DIRECTORYCHANGES, 0, 0);
break;
};
case FILE_ACTION_RENAMED_OLD_NAME: {
wsprintf(g_szShowChanges, TEXT("文件目录重命名:%s --> %s\n"),
szFileName, szFileNameNew);
PostMessage(g_hwndDlg, WM_DIRECTORYCHANGES, 0, 0);
break;
};
};
};
return 0;
};

获取硬盘序列号

DeviceIoControl

向指定设备驱动程序发送指定控制代码:

1
2
3
4
5
6
7
8
9
10
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice, //要执行操作的设备句柄
_In_ DWORD dwIoControlCode, //控制代码
_In_opt_ LPVOID lpInBuffer, //输入缓冲区
_In_opt_ DWORD nInBufferSize, //输入缓冲区大小 单位字节
_Out_opt_ LPVOID lpOutBuffer, //输出缓冲区
_Out_opt_ DWORD nOutBufferSize, //输出缓冲区大小 单位字节
_Out_opt_ LPDWORD lpBytesReturned, //实际返回输出缓冲区字节数
_Inout_opt_ LPOVERLAPPED lpOverlapped //异步操作
)

hDevice用CreateFile打开句柄,例如逻辑驱动器C为“\\.\C:”,物理驱动器0为“\\.\PhysicalDrive0”。

lpBytesReturned和lpOverlapped不能同时为NULL。

IOCTL_STORAGE_QUERY_PROPERTY

查询存储设备或适配器属性。lpInBuffer指向STORAGE_PROPERTY_QUERY结构:

1
2
3
4
5
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId; //属性ID
STORAGE_QUERY_TYPE QueryType; //查询类型
BYTE AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;

对于PropertyId有:

枚举值 含义 lpOutBuffer对应结构
StorageDeviceProperty 设备描述符 STORAGE_DEVICE_DESCRIPTOR
StorageAdapterProperty 适配器描述符 STORAGE_ADAPTER_DESCRIPTOR
StorageDeviceIdProperty SCSI重要产品数据页提供的设备ID STORAGE_DEVICE_ID_DESCRIPTOR
StorageDeviceUniqueIdProperty 设备唯一ID STORAGE_DEVICE_UNIQUE_IDENTIFIER
StorageDeviceWriteCacheProperty 写缓存属性 STORAGE_WRITE_CACHE_PROPERTY
StorageMiniportProperty 微型端口驱动程序描述符 STORAGE_MINIPORT_DESCRIPTOR

对于QueryType有:

枚举值 含义
PropertyStandardQuery 查询相关描述符
PropertyExistsQuery 驱动程序是否支持指定描述符,lpOutBuffer和nOutBufferSize都为NULL,DeviceIoControl返回TRUE表示支持

其中重点STORAGE_DEVICE_DESCRIPTOR结构如下,注意输出缓冲区中所有字符串都为ASCII格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef _Struct_size_bytes_(Size) struct _STORAGE_DEVICE_DESCRIPTOR {
DWORD Version; //该结构大小 单位字节
DWORD Size; //描述符总大小 单位字节
BYTE DeviceType; //设备类型
BYTE DeviceTypeModifier; //设备类型修饰符
BOOLEAN RemovableMedia; //是否为可移动介质
BOOLEAN CommandQueueing; //是否支持命令队列
DWORD VendorIdOffset; //设备供应商ID字符串相对结构开始偏移
DWORD ProductIdOffset; //设备产品ID字符串相对结构开始偏移
DWORD ProductRevisionOffset; //设备产品修订版本字符串相对结构开始偏移
DWORD SerialNumberOffset; //设备序列号字符串相对结构开始偏移
STORAGE_BUS_TYPE BusType; //设备连接到总线的类型
DWORD RawPropertiesLength; //总线特定属性数据字符数
BYTE RawDeviceProperties[1]; //总线特定属性数据第一字节的占位符
} STORAGE_DEVICE_DESCRIPTOR, * PSTORAGE_DEVICE_DESCRIPTOR;

其中STORAGE_BUS_TYPE结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef enum _STORAGE_BUS_TYPE {
BusTypeUnknown = 0x00, //未知
BusTypeScsi, //SCSI
BusTypeAtapi, //ATAPI
BusTypeAta, //ATA
BusType1394, //IEEE 1394
BusTypeSsa, //SSA
BusTypeFibre, //光纤通道
BusTypeUsb, //USB
BusTypeRAID, //RAID
BusTypeiScsi, //iSCSI
BusTypeSas, //串行链接的SCSI 即SAS
BusTypeSata, //SATA
BusTypeSd, //安全数字SD
BusTypeMmc, //多媒体卡MMC
BusTypeVirtual, //虚拟
BusTypeFileBackedVirtual, //文件支持的虚拟
//BusTypeSpaces,
//BusTypeNvme,
//BusTypeSCM,
//BusTypeUfs,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, * PSTORAGE_BUS_TYPE;

IOCTL_DISK_GET_DRIVE_GEOMETRY_EX

获取一个物理驱动器大小、磁头数、柱面数等。

SMART_GET_VERSION

获取磁盘设备是否支持SMART技术,即自我监控、分析和报告技术,可对硬盘温度、内部电路和盘片表面介质材料等进行检测。SMART信息为512字节,存放在硬盘控制内存中,前2字节为SMART版本信息,之后每12字节为一个SMART属性。

例子

获取物理驱动器ProductIdOffset产品ID、SerialNumberOffset序列号、BusType设备接口类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow){
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndEditInfo;
TCHAR szDriverName[MAX_PATH] = { 0 };
HANDLE hDriver;
STORAGE_PROPERTY_QUERY storagePropertyQuery; // 输入缓冲区
CHAR cOutBuffer[1024] = { 0 }; // 输出缓冲区
PSTORAGE_DEVICE_DESCRIPTOR pStorageDeviceDesc;
DWORD dwBytesReturned;
CHAR szBuf[1024] = { 0 };
LPCSTR arrBusType[] = { "未知的总线类型", "SCSI总线类型", "ATAPI总线类型","ATA总线类型", "IEEE 1394总线类型", "SSA总线类型","光纤通道总线类型", "USB", "RAID总线类型","iSCSI总线类型", "串行连接的SCSI总线类型", "SATA","安全数字(SD)总线类型", "多媒体卡(MMC)总线类型", "虚拟总线类型","文件支持的虚拟总线类型" };
switch (uMsg) {
case WM_INITDIALOG: {
hwndEditInfo = GetDlgItem(hwndDlg, IDC_EDIT_INFO);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_GETINFO: {
for (INT i = 0; i < 5; i++) {
// 打开物理驱动器
wsprintf(szDriverName, TEXT("\\\\.\\PhysicalDrive%d"), i);
hDriver = CreateFile(szDriverName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
if (hDriver == INVALID_HANDLE_VALUE) {
wsprintfA(szBuf, "打开物理驱动器%d失败!\r\n\r\n", i);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessageA(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
continue;
};
// 控制代码IOCTL_STORAGE_QUERY_PROPERTY
ZeroMemory(&storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY));
storagePropertyQuery.PropertyId = StorageDeviceProperty;
storagePropertyQuery.QueryType = PropertyStandardQuery;
DeviceIoControl(hDriver, IOCTL_STORAGE_QUERY_PROPERTY, &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY), cOutBuffer, sizeof(cOutBuffer), &dwBytesReturned, NULL);
pStorageDeviceDesc = (PSTORAGE_DEVICE_DESCRIPTOR)cOutBuffer;
wsprintfA(szBuf, "物理驱动器%d\r\n产品 ID:\t%s\r\n序列号:\t%s\r\n接口类型:\t%s\r\n\r\n", i, (LPBYTE)pStorageDeviceDesc + pStorageDeviceDesc->ProductIdOffset, (LPBYTE)pStorageDeviceDesc + pStorageDeviceDesc->SerialNumberOffset, arrBusType[pStorageDeviceDesc->BusType]);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessageA(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 关闭设备句柄
CloseHandle(hDriver);
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

WMI管理技术

WMI可以访问、配置、管理并监视几乎所有Windows资源,所有WMI接口基于COM组件对象模型,这里不建议用WMI获取信息,因为速度慢。

创建WMI应用程序的步骤:

  1. 初始化COM,因为WMI基于COM技术,所以必须执行对CoInitializeExCoInitializeSecurity函数的调用才能访问WMI。
  2. 创建到WMI指定命名空间的连接,要创建到WMI指定命名空间的连接,需要首先调用CoCreateInstance函数创建与指定CLSID关联的类的对象,该函数在最后一个参数中返回一个IWbemLocator类对象指针,然后可以通过IWbemLocator::ConnectServer方法连接到WMI指定命名空间,IWbemLocator::ConnectServer在最后一个参数中返回一个IWbemServices类对象指针(代理)。
  3. 设置WMI连接的安全级别,获取到IWbemServices代理的指针以后,必须在代理上设置安全性以通过代理访问WMI,如果没有设置适当的安全属性,则COM不允许一个进程访问另一进程,程序可以通过调用CoSetProxyBlanket函数来设置IWbemServices代理上的安全级别,在为IWbemServices代理设置安全级别以后,可以使用WMI提供的各种功能。
  4. 访问WMI,例如可以通过IWbemServices::ExecQuery方法执行WQL查询语句,IWbemServices::ExecQuery在最后一个参数中返回一个IEnumWbemClassObject类对象指针(枚举器),程序可以通过该枚举器访问查询结果。
  5. 清理工作。

如果需要查询其他设备的信息,更改一下IWbemServices::ExecQuery方法的WQL查询语句即可,另外IWbemClassObject::Get方法的第一个参数应该设置为需要获取的字段,例如:

1
2
3
4
select * from Win32_BaseBoard where SerialNumber is not null #获取主板序列号
select * from Win32_Processor where ProcessorId is not null #获取处理器CPUID
select * from Win32_BIOS where SerialNumber is not null #获取BIOS序列号
select * from Win32_NetworkAdapter where MACAddress is not null and not (PNPDeviceID like 'ROOT%') #获取网卡Mac地址

其他的自己查:https://docs.microsoft.com/zh-cn/windows/win32/cimwin32prov/computer-system-hardware-classes。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <windows.h>
#include <wbemidl.h>
#include <Propvarutil.h>
#include <comdef.h>
#include "resource.h"
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "Propsys.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
static HWND hwndEditInfo;
HRESULT hr;
IWbemLocator* pIWbemLocator = NULL;
IWbemServices* pIWbemServices = NULL;
IEnumWbemClassObject* pIEnumWbemClassObject = NULL;
IWbemClassObject* pIWbemClassObject = NULL;
ULONG uReturned = 0;
VARIANT variant;
TCHAR szBuf[256] = { 0 };
switch (uMsg){
case WM_INITDIALOG: {
hwndEditInfo = GetDlgItem(hwndDlg, IDC_EDIT_INFO);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_GETINFO: {
// 初始化COM
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
return FALSE;
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
if (FAILED(hr)) {
CoUninitialize();
return FALSE;
};
// 创建到WMI指定命名空间ROOT\CIMV2的连接
hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pIWbemLocator);
if (FAILED(hr)) {
CoUninitialize();
return FALSE;
};
hr = pIWbemLocator->ConnectServer((BSTR)TEXT("ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pIWbemServices);
if (FAILED(hr)) {
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
// 设置WMI连接的安全级别
hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
// 访问WMI
// 获取硬盘序列号
hr = pIWbemServices->ExecQuery((BSTR)TEXT("WQL"), (BSTR)TEXT("select * from Win32_DiskDrive where SerialNumber is not null"), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pIEnumWbemClassObject);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
while (pIEnumWbemClassObject) {
hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturned);
if (uReturned == 0)
break;
hr = pIWbemClassObject->Get(TEXT("SerialNumber"), 0, &variant, NULL, NULL);
if (SUCCEEDED(hr)) {
wsprintf(szBuf, TEXT("硬盘序列号:\t%s\n"), variant.bstrVal);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessage(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
VariantClear(&variant);
pIWbemClassObject->Release();
pIWbemClassObject = NULL;
};
};
// 获取主板序列号
hr = pIWbemServices->ExecQuery((BSTR)TEXT("WQL"), (BSTR)TEXT("select * from Win32_BaseBoard where SerialNumber is not null"), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pIEnumWbemClassObject);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturned);
if (uReturned == 0)
break;
hr = pIWbemClassObject->Get(TEXT("SerialNumber"), 0, &variant, NULL, NULL);
if (SUCCEEDED(hr)) {
wsprintf(szBuf, TEXT("主板序列号:\t%s\n"), variant.bstrVal);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessage(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
VariantClear(&variant);
pIWbemClassObject->Release();
pIWbemClassObject = NULL;
};
// 获取处理器CPUID
hr = pIWbemServices->ExecQuery((BSTR)TEXT("WQL"), (BSTR)TEXT("select * from Win32_Processor where ProcessorId is not null"), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pIEnumWbemClassObject);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturned);
if (uReturned == 0)
break;
hr = pIWbemClassObject->Get(TEXT("ProcessorId"), 0, &variant, NULL, NULL);
if (SUCCEEDED(hr)) {
wsprintf(szBuf, TEXT("CPUID:\t\t%s\n"), variant.bstrVal);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessage(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
VariantClear(&variant);
pIWbemClassObject->Release();
pIWbemClassObject = NULL;
};
// 获取BIOS序列号
hr = pIWbemServices->ExecQuery((BSTR)TEXT("WQL"), (BSTR)TEXT("select * from Win32_BIOS where SerialNumber is not null"), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pIEnumWbemClassObject);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturned);
if (uReturned == 0)
break;
hr = pIWbemClassObject->Get(TEXT("SerialNumber"), 0, &variant, NULL, NULL);
if (SUCCEEDED(hr)) {
wsprintf(szBuf, TEXT("BIOS序列号:\t%s\n"), variant.bstrVal);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessage(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
VariantClear(&variant);
pIWbemClassObject->Release();
pIWbemClassObject = NULL;
};
// 获取网卡Mac地址
hr = pIWbemServices->ExecQuery((BSTR)TEXT("WQL"), (BSTR)TEXT("select * from Win32_NetworkAdapter where MACAddress is not null and not (PNPDeviceID like 'ROOT%')"), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pIEnumWbemClassObject);
if (FAILED(hr)) {
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
return FALSE;
};
while (pIEnumWbemClassObject) {
hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturned);
if (uReturned == 0)
break;
hr = pIWbemClassObject->Get(TEXT("MACAddress"), 0, &variant, NULL, NULL);
if (SUCCEEDED(hr)) {
wsprintf(szBuf, TEXT("MAC地址:\t%s\n"), variant.bstrVal);
SendMessage(hwndEditInfo, EM_SETSEL, -1, -1);
SendMessage(hwndEditInfo, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
VariantClear(&variant);
pIWbemClassObject->Release();
pIWbemClassObject = NULL;
};
};
// 清理工作
pIEnumWbemClassObject->Release();
pIWbemServices->Release();
pIWbemLocator->Release();
CoUninitialize();
break;
};
case IDCANCEL:
EndDialog(hwndDlg, 0);
break;
}
return TRUE;
};
};
return FALSE;
};

可移动硬盘和U盘监控

当计算机硬件设备或硬件配置发生变化时系统向窗口过程广播WM_DEVICECHANGE消息。其wParam参数常见值有:

枚举值 含义
DBT_DEVICEARRAIVAL 已插入设备或媒体并现在可用
DBT_DEVICEQUERYREMOVE 请求删除设备或媒体,任何应用程序可拒绝该请求并删除操作
DBT_EVICEQUERYREMOVEFAILED 删除设备或媒体的请求已被取消
DBT_DEVICEREMOVEPENDING 设备或介质将被删除,应用程序无法拒绝
DBT_DEVICEREMOVECOMPLETE 设备或媒体已被删除
DBT_DEVICETYPESPECIFIC 发生了特定于设备的事件
DBT_CUSTOMEVENT 发生了自定义事件
DBT_USERDEFINED 用户自定义事件

处理完该消息后返回TRUE表示接收请求,返回BROADCAST_QUERY_DENY表示拒绝请求。这里只关注DBT_DEVICEARRAIVAL或DBT_DEVICEDEVICEMOVECOMPLETE,这时lParam为指向DEV_BROADCAST_HDR结构的指针,结构如下:

1
2
3
4
5
6
struct _DEV_BROADCAST_HDR {
DWORD dbch_size; //该结构大小
DWORD dbch_devicetype; //设备类型
DWORD dbch_reserved; //保留字段
};
typedef struct _DEV_BROADCAST_HDR DEV_BROADCAST_HDR;

其中dbch_devicetype表示设备类型:

枚举值 含义 lParam结构
DBT_DEVTYP_DEVICEINTERFACE 设备类 DEV_BROADCAST_DEVICEINTERFACE
DBT_DEVTYP_HANDLE 文件系统句柄 DEV_BROADCAST_HANDLE
DBT_DEVTYP_OEM OEM或IHV定义的设备类型 DEV_BROADCASE_OEM
DBT_DEVTYP_PORT 串/并行端口设备 DEV_BROADCAST_PORT
DBT_DEVTYP_VOLUME 逻辑卷 DEV_BROADCAST_VOLUME

这里只关注DBT_DEVTYP_VOLUME,其中DEV_BROADCAST_VOLUME结构为:

1
2
3
4
5
6
7
8
struct _DEV_BROADCAST_VOLUME {
DWORD dbcv_size; //该结构大小
DWORD dbcv_devicetype; //DBT_DEVTYP_VOLUME
DWORD dbcv_reserved; //保留
DWORD dbcv_unitmask; //逻辑驱动器位掩码
WORD dbcv_flags; //标志位
};
typedef struct _DEV_BROADCAST_VOLUME DEV_BROADCAST_VOLUME;

dbcv_unitmask中,如位0是驱动器A、位1是驱动器B。

dbcv_flags是标志位:

枚举值 含义
DBTF_MEDIA 更改会影响驱动器中的媒体,否则影响物理设备或驱动器
DBTF_NET 逻辑卷是网络卷

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <windows.h>
#include <Dbt.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
PDEV_BROADCAST_HDR pDevBroadcastHdr = NULL;
PDEV_BROADCAST_VOLUME pDevBroadcastVolume = NULL;
DWORD dwDriverMask, dwIndex;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_DEVICECHANGE: {
switch (wParam) {
case DBT_DEVICEARRIVAL: {
pDevBroadcastHdr = (PDEV_BROADCAST_HDR)lParam;
if (pDevBroadcastHdr->dbch_devicetype == DBT_DEVTYP_VOLUME) {
pDevBroadcastVolume = (PDEV_BROADCAST_VOLUME)lParam;
dwDriverMask = pDevBroadcastVolume->dbcv_unitmask;
dwIndex = 0x00000001;
TCHAR szDriverName[] = TEXT("A:\\");
for (szDriverName[0] = TEXT('A'); szDriverName[0] <= TEXT('Z'); szDriverName[0]++) {
if ((dwDriverMask & dwIndex) > 0)
MessageBox(hwndDlg, szDriverName, TEXT("设备已插入"), MB_OK);
// 检测下一个辑驱动器位掩码
dwIndex = dwIndex << 1;
};
};
break;
};
case DBT_DEVICEREMOVECOMPLETE: {
pDevBroadcastHdr = (PDEV_BROADCAST_HDR)lParam;
if (pDevBroadcastHdr->dbch_devicetype == DBT_DEVTYP_VOLUME) {
pDevBroadcastVolume = (PDEV_BROADCAST_VOLUME)lParam;
dwDriverMask = pDevBroadcastVolume->dbcv_unitmask;
dwIndex = 0x00000001;
TCHAR szDriverName[] = TEXT("A:\\");
for (szDriverName[0] = TEXT('A'); szDriverName[0] <= TEXT('Z'); szDriverName[0]++) {
if ((dwDriverMask & dwIndex) > 0)
MessageBox(hwndDlg, szDriverName, TEXT("设备已拔出"), MB_OK);
// 检测下一个辑驱动器位掩码
dwIndex = dwIndex << 1;
};
};
break;
};
};
return TRUE;
};
};
return FALSE;
};

获取主板和BIOS序列号

GetSystemFirmwareTable

获取指定固件表:

1
2
3
4
5
6
UINT WINAPI GetSystemFirmwareTable(
_In_ DWORD FirmwareTableProviderSignature, //固件表提供程序的ID
_In_ DWORD FirmwareTableID, //固件表ID
_Out_ PVOID pF1irmwareTableBuffer, //返回请求的固件表的缓冲区
_In_ DWORD BufferSize //pFirmwareTableBuffer大小 单位字节
)

其中FirmwareTableProviderSignature可以是:

含义
'ACPI' ACPI固件表提供程序
'FIRM' 原始固件表提供程序
'RSMB' 原始SMBIOS固件表提供程序

对于FirmwareTableID获取SMBIOS固件表指定0就行。pFirmwareTableBuffer位NULL时函数返回所需缓冲区大小。

PFirmwareTableBuffer返回的缓冲区时RawSMBIOSTable(现更名为RawSMBIOSData???)结构:

1
2
3
4
5
6
7
8
struct RawSMBIOSData{
BYTE Used20CallingMethod;
BYTE SMBIOSMajorVersion;
BYTE SMBIOSMinorVersion;
BYTE DmiRevision;
DWORD Length; //原始SMBIOS固件数据长度 单位字节
BYTE SMBIOSTableData[]; //原始SMBIOS固件表数组 长度可变
};

注册表HEKY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\mssmbios\Data子键下有个键名SMBiosData,键值是原始SMBIOS固件表,但GetSystemFirmwareTable不是读的这个。

后面太复杂了略。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <windows.h>
#include <strsafe.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
//////////////////////////////////////////////////////////////////////////
typedef unsigned __int64 QWORD;
// 原始SMBIOS固件表结构
typedef struct _RawSMBIOSTable{
BYTE m_bUsed20CallingMethod;
BYTE m_bSMBIOSMajorVersion;
BYTE m_bSMBIOSMinorVersion;
BYTE m_bDmiRevision; // 开头4个字段我们不关心
DWORD m_dwLength; // 原始SMBIOS固件表数据的长度,以字节为单位
BYTE m_bSMBIOSTableData[1]; // 偏移8字节,原始SMBIOS固件表数据,可变长度
}RawSMBIOSTable, * PRawSMBIOSTable;
// SMBIOS结构的格式化区域中的结构头
typedef struct _SMBIOSStructHeader{
BYTE m_bType; // 结构类型
BYTE m_bLength; // 该类型结构的格式化区域长度(请注意长度取决于主板或系统支持的版本)
WORD m_wHandle; // 结构句柄(0~0xFEFF范围内的数字)
}SMBIOSStructHeader, * PSMBIOSStructHeader;
#pragma pack(1)
// 系统信息(Type 1)SMBIOS结构的格式化区域的完整定义
typedef struct _Type1SystemInformation{
SMBIOSStructHeader m_sHeader; // SMBIOS结构头SMBIOSStructHeader
BYTE m_bManufacturer; // Manufacturer字符串的编号
BYTE m_bProductName; // Product Name字符串的编号
BYTE m_bVersion; // Version字符串的编号
BYTE m_bSerialNumber; // BIOS Serial Number字符串的编号
UUID m_uuid; // UUID
BYTE m_bWakeupType; // 标识导致系统启动的事件(原因)
BYTE m_bSKUNumber; // SKU Number字符串的编号
BYTE m_bFamily; // Family字符串的编号
}Type1SystemInformation, * PType1SystemInformation;
// 基板信息(Type 2)SMBIOS结构的格式化区域的完整定义
typedef struct _Type2BaseboardInformation{
SMBIOSStructHeader m_sHeader; // SMBIOS结构头SMBIOSStructHeader
BYTE m_bManufactur; // Manufactur字符串的编号
BYTE m_bProduct; // Product字符串的编号
BYTE m_bVersion; // Version字符串的编号
BYTE m_bSerialNumber; // Baseboard Serial Number字符串的编号
BYTE m_bAssetTag; // Asset Tag字符串的编号
BYTE m_bFeatureFlags; // 基板特征标志
BYTE m_bLocationInChassis; // Location In Chassis字符串的编号
WORD m_wChassisHandle; // Chassis Handle
BYTE m_bBoardType; // 基板类型
//BYTE m_bNumberOfContainedObjectHandles;
//WORD m_wContainedObjectHandles[1];
}Type2BaseboardInformation, * PType2BaseboardInformation;
// 处理器信息(Type 4)SMBIOS结构的格式化区域的完整定义
typedef struct _Type4ProcessorInformation{
SMBIOSStructHeader m_sHeader; // SMBIOS结构头SMBIOSStructHeader
BYTE m_bSocketDesignation; // Socket Designation字符串的编号
BYTE m_bProcessorType; // 处理器类型,ENUM值,例如03是中央处理器
BYTE m_bProcessorFamily; // 处理器家族,ENUM值,例如0xC6是Intel® Core™ i7 processor
BYTE m_bProcessorManufacturer; // Processor Manufacturer字符串的编号
QWORD m_qProcessorID; // CPUID(本书结尾还会介绍),包含描述处理器功能的特定信息
BYTE m_bProcessorVersion; // Processor Version字符串的编号
BYTE m_bVoltage; // Voltage(电压)
WORD m_wExternalClock; // 外部时钟频率,以MHz为单位
WORD m_wMaxSpeed; // 适用于233MHz处理器
WORD m_wCurrentSpeed; // 处理器在系统引导时的速度,处理器可以支持多种速度
BYTE m_bStatus; // CPU和CPU插槽的状态
BYTE m_bProcessorUpgrade; // Processor Upgrade,ENUM值
WORD m_wL1CacheHandle; // 一级缓存信息结构的句柄
WORD m_wL2CacheHandle; // 二级缓存信息结构的句柄
WORD m_wL3CacheHandle; // 三级缓存信息结构的句柄
BYTE m_bSerialNumber; // 处理器序列号字符串的编号,由制造商设置,通常不可更改
BYTE m_bAssetTag; // Asset Tag字符串的编号
BYTE m_bPartNumber; // 处理器部件号字符串的编号,由制造商设置,通常不可更改
BYTE m_bCoreCount; // 处理器的核心数
BYTE m_bCoreEnabled; // 由BIOS启用并可供系统使用的核心数
BYTE m_bThreadCount; // 处理器的线程数
WORD m_wProcessorCharacteristics;// 处理器特性,例如0x0004表示64位处理器
// 最后4个字段2.6及以上版本才支持
//WORD m_wProcessorFamily2; // 处理器家族2
//WORD m_wCoreCount2; // 处理器的核心数,用于个数大于255时
//WORD m_wCoreEnabled2; // 由BIOS启用并可供系统使用的核心数,用于个数大于255时
//WORD m_wThreadCount2; // 处理器的线程数,用于个数大于255时
}Type4ProcessorInformation, * PType4ProcessorInformation;
#pragma pack()
//////////////////////////////////////////////////////////////////////////
// 全局变量
HINSTANCE g_hInstance;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 从SMBIOS结构的未格式化区域中查找指定编号的字符串
LPSTR FingStrFromStruct(PSMBIOSStructHeader pStructHeader, BYTE bNum);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HWND hwndEdit = NULL;
DWORD dwBufferSize = 0;
LPBYTE lpBuf = NULL;
PRawSMBIOSTable pRawSMBIOSTable = NULL; // 原始SMBIOS固件表
LPBYTE lpData = NULL; // 原始SMBIOS固件表数据
PSMBIOSStructHeader pStructHeader = NULL; // SMBIOS结构的格式化区域中的结构头
CHAR szBuf[1024] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
hwndEdit = GetDlgItem(hwndDlg, IDC_EDIT_INFO);
// 第1次调用获取所需的缓冲区大小
dwBufferSize = GetSystemFirmwareTable('RSMB', 0, NULL, 0);
if (dwBufferSize == 0)
return TRUE;
// 第2次调用获取原始SMBIOS固件表
lpBuf = new BYTE[dwBufferSize];
ZeroMemory(lpBuf, dwBufferSize);
if (GetSystemFirmwareTable('RSMB', 0, lpBuf, dwBufferSize) != dwBufferSize)
return TRUE;
// 解析获取到的原始SMBIOS固件表数据
pRawSMBIOSTable = (PRawSMBIOSTable)lpBuf;
lpData = pRawSMBIOSTable->m_bSMBIOSTableData;
while ((lpData - pRawSMBIOSTable->m_bSMBIOSTableData) < pRawSMBIOSTable->m_dwLength) {
// 根据SMBIOS结构的格式化区域中的结构头的m_bType字段确定结构类型
// 确定结构类型以后再把pStructHeader转换为指向对应的格式化区域完整定义的指针
pStructHeader = (PSMBIOSStructHeader)lpData;
if (pStructHeader->m_bType == 1) {
PType1SystemInformation pType1 = (PType1SystemInformation)pStructHeader;
CHAR szUUID[64] = { 0 };
wsprintfA(szUUID, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", pType1->m_uuid.Data1, pType1->m_uuid.Data2, pType1->m_uuid.Data3, pType1->m_uuid.Data4[0], pType1->m_uuid.Data4[1], pType1->m_uuid.Data4[2], pType1->m_uuid.Data4[3], pType1->m_uuid.Data4[4], pType1->m_uuid.Data4[5], pType1->m_uuid.Data4[6], pType1->m_uuid.Data4[7]);
ZeroMemory(szBuf, sizeof(szBuf));
StringCchPrintfA(szBuf, _countof(szBuf), "系统信息(类型1):\r\nType\t\t%d\r\nLength\t\t0x%X\r\nHandle\t\t%d\r\nManufacturer\t%s\r\nProduct Name\t%s\r\nVersion\t\t%s\r\nSerial Number\t%s\r\nUUID\t\t%s\r\nWake-up Type\t%d\r\nSKU Number\t%s\r\nFamily\t\t%s\r\n\r\n", pType1->m_sHeader.m_bType, pType1->m_sHeader.m_bLength, pType1->m_sHeader.m_wHandle, FingStrFromStruct(pStructHeader, pType1->m_bManufacturer), FingStrFromStruct(pStructHeader, pType1->m_bProductName), FingStrFromStruct(pStructHeader, pType1->m_bVersion), FingStrFromStruct(pStructHeader, pType1->m_bSerialNumber), szUUID, pType1->m_bWakeupType, FingStrFromStruct(pStructHeader, pType1->m_bSKUNumber), FingStrFromStruct(pStructHeader, pType1->m_bFamily));
SendMessageA(hwndEdit, EM_SETSEL, -1, -1);
SendMessageA(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
}
else if (pStructHeader->m_bType == 2) {
PType2BaseboardInformation pType2 = (PType2BaseboardInformation)pStructHeader;
ZeroMemory(szBuf, sizeof(szBuf));
StringCchPrintfA(szBuf, _countof(szBuf), "基板信息(类型2):\r\nType\t\t%d\r\nLength\t\t0x%X\r\nHandle\t\t%d\r\nManufactur\t%s\r\nProduct\t\t%s\r\nVersion\t\t%s\r\nSerial Number\t%s\r\nAsset Tag\t%s\r\nFeature Flags\t%d\r\nLocation In Chassis\t%s\r\nChassis Handle\t%d\r\nBoard Type\t%d\r\n\r\n", pType2->m_sHeader.m_bType, pType2->m_sHeader.m_bLength, pType2->m_sHeader.m_wHandle, FingStrFromStruct(pStructHeader, pType2->m_bManufactur), FingStrFromStruct(pStructHeader, pType2->m_bProduct), FingStrFromStruct(pStructHeader, pType2->m_bVersion), FingStrFromStruct(pStructHeader, pType2->m_bSerialNumber), FingStrFromStruct(pStructHeader, pType2->m_bAssetTag), pType2->m_bFeatureFlags, FingStrFromStruct(pStructHeader, pType2->m_bLocationInChassis), pType2->m_wChassisHandle, pType2->m_bBoardType);
SendMessageA(hwndEdit, EM_SETSEL, -1, -1);
SendMessageA(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
}
else if (pStructHeader->m_bType == 4) {
PType4ProcessorInformation pType4 = (PType4ProcessorInformation)pStructHeader;
ZeroMemory(szBuf, sizeof(szBuf));
StringCchPrintfA(szBuf, _countof(szBuf), "处理器信息(类型4):\r\nType\t\t\t%d\r\nLength\t\t\t0x%X\r\nHandle\t\t\t%d\r\nSocket Designation\t\t%s\r\nProcessor Type\t\t%d\r\nProcessor Family\t\t%d\r\nProcessor Manufacturer\t%s\r\nProcessor ID\t\t%I64X\r\nProcessor Version\t\t%s\r\nVoltage\t\t\t%d\r\nExternal Clock\t\t%d\r\nMax Speed\t\t%d\r\nCurrent Speed\t\t%d\r\nStatus\t\t\t%d\r\nProcessor Upgrade\t\t%d\r\nL1 Cache Handle\t\t%d\r\nL2 Cache Handle\t\t%d\r\nL3 Cache Handle\t\t%d\r\nSerial Number\t\t%s\r\nAsset Tag\t\t%s\r\nPart Number\t\t%s\r\nCore Count\t\t%d\r\nCore Enabled\t\t%d\r\nThread Count\t\t%d\r\nProcessor Characteristics\t%d\r\n\r\n", pType4->m_sHeader.m_bType, pType4->m_sHeader.m_bLength, pType4->m_sHeader.m_wHandle, FingStrFromStruct(pStructHeader, pType4->m_bSocketDesignation), pType4->m_bProcessorType, pType4->m_bProcessorFamily, FingStrFromStruct(pStructHeader, pType4->m_bProcessorManufacturer), pType4->m_qProcessorID, FingStrFromStruct(pStructHeader, pType4->m_bProcessorVersion), pType4->m_bVoltage, pType4->m_wExternalClock, pType4->m_wMaxSpeed, pType4->m_wCurrentSpeed, pType4->m_bStatus, pType4->m_bProcessorUpgrade, pType4->m_wL1CacheHandle, pType4->m_wL2CacheHandle, pType4->m_wL3CacheHandle, FingStrFromStruct(pStructHeader, pType4->m_bSerialNumber), FingStrFromStruct(pStructHeader, pType4->m_bAssetTag), FingStrFromStruct(pStructHeader, pType4->m_bPartNumber), pType4->m_bCoreCount, pType4->m_bCoreEnabled, pType4->m_bThreadCount, pType4->m_wProcessorCharacteristics);
SendMessageA(hwndEdit, EM_SETSEL, -1, -1);
SendMessageA(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
// 遍历完本SMBIOS结构的未格式化区域,以指向下一个SMBIOS结构
lpData += pStructHeader->m_bLength;
while ((*(LPWORD)lpData) != 0) lpData++;
lpData += 2;
};
delete[]lpBuf;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OPEN: {
break;
};
};
return TRUE;
};
case WM_CLOSE: {
EndDialog(hwndDlg, 0);
return TRUE;
};
};
return FALSE;
};
LPSTR FingStrFromStruct(PSMBIOSStructHeader pStructHeader, BYTE bNum) {
// 指向SMBIOS结构的未格式化区域(字符串数组)
LPBYTE lpByte = (LPBYTE)pStructHeader + pStructHeader->m_bLength;
// 字符串编号从1开始
for (BYTE i = 1; i < bNum; i++)
lpByte += strlen((LPSTR)lpByte) + 1;
return (LPSTR)lpByte;
};

内存映射文件

CreateFileMapping

1
2
3
4
5
6
7
8
HANDLE WINAPI CreateFileMapping(
_In_ HANDLE hFile, //文件对象句柄
_In_opt_ LPSECURITY_ATTRIBUTES lpAttributes, //安全属性结构
_In_ DWORD flProtect, //页面保护属性
_In_ DWORD dwMaximumSizeHigh, //文件映射大小高32位 单位字节
_In_ DWORD dwMaximumSizeLow, //文件映射大小低32位
_In_opt_ LPCTSTR lpName //文件映射对象名称
)

fProtect可以以下选一个:

枚举值 含义
PAGE_READONLY/PAGE_WRITECOPY 可读、写时复制
PAGE_READWRITE 可读可写、写时复制
PAGE_EXECUTE_READ/PAGE_EXECUTE_WRITECOPY 可读可执行、写时复制
PAGE_EXECUTE_READWRITE 可读可写可执行、写时复制

hFile还可以指定位INVALID_HANDLE_VALUE,系统使用页面交换文件创建或打开一个文件映射对象,通常用于进程间共享数据,此时fProtect还可以同时使用:

枚举值 含义
SEC_COMMIT 默认属性,提交所有页面
SEC_RESERVE 页面处于预定/保留状态,需要时用VirtualAlloc提交
SEC_IMAGE 是可执行文件

对于dwMaximumSizeHigh和dwMaximumSizeLow:若文件映像基于hFile指定的文件,这俩都可以为0,文件映射对象大小等于hFile指定文件大小;当hFile指定的文件为0,这俩参数也为0则调用失败返回NULL,GetLastError返回ERROR_FILE_INVALID;若这俩参数指定的大小大于hFile指定的文件大小则扩展文件到指定大小,扩展失败则调用失败返回HULL,GetLastError返回ERROR_DISK_FULL;若是基于页面交换文件映射对象,这俩参数必须指定明确大小。

lpName为NULL则创建一个匿名文件映射对象,已存在则打开并返回该文件映射对象句柄。

MapViewOfFile

把文件映射对象的部分或全部映射到进程的虚拟地址空间中,返回内存指针lpMemory,通过该指针读写文件:

1
2
3
4
5
6
7
LPVOID WINAPI MapViewOfFile(
_In_ HANDLE hFileMappingObject, //文件映射对象句柄
_In_ DWORD dwDesiredAccess, //访问类型
_In_ DWORD dwFileOffsetHigh, //文件映射对象偏移量高32位
_In_ DWORD dwFileOffsetLow, //文件映射对象偏移量低32位
_In_ SIZE_T dwNumberOfBytesToMap //视图大小(要映射的字节数)
)

对于dwDesiredAccess有:

枚举值 含义
FILE_MAP_READ 可读
FILE_MAP_WRITE/FILE_MAP_ALL_ACCESS 可读可写
FILE_MAP_COPY 写时复制
FILE_MAP_EXECUTE 可执行

该函数从dwFileOffsetHigh和dwFileOffsetLow偏移量开始映射dwNumberOfBytesToMap字节。当dwNumberOfBytesToMap位0则映射到文件映射对象末尾。偏移量必须是分配粒度整数倍。

MapViewOfFileEx

同上,可指定映射基地址:

1
2
3
4
5
6
7
8
LPVOID WINAPI MapViewOfFile(
_In_ HANDLE hFileMappingObject,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwFileOffsetHigh,
_In_ DWORD dwFileOffsetLow,
_In_ SIZE_T dwNumberOfBytesToMap,
_In_opt_ LPVOID lpBaseAddress //映射基地址 内存分配粒度整数倍
)

OverFileMapping

打开一个名命名文件映射对象:

1
2
3
4
5
HANDLE WINAPI OpenFileMapping(
_In_ DWORD dwDesiredAccess, //访问类型
_In_ BOOL bInheritHandle, //返回是否可被子进程继承
_In_ LPCTSTR lpName //文件映射对象名称
)

FlushViewOfFile

系统对文件视图的页面做缓存处理,吸入文件视图时不一定随时更新磁盘上文件,用该函数强制系统把部分或全部已修改的数据(脏页)写回到磁盘中:

1
2
3
4
BOOL WINAPI FlushViewOfFile(
_In_ LPCVOID lpBaseAddress, //视图起始地址 自动向下取整到页面大小整数倍
_In_ SIZE_T dwNumberOfBytesToFlush //要刷新的字节数 自动向上取整到页面大小整数倍 为0从基地址刷新到视图末尾
)

UnmapViewOfFile

撤销对视图的映射:

1
2
3
BOOL WINAPI UnmapViewOfFile(
_In_ LPCVOID lpBaseAddress //内存映射文件的视图的起始地址
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <windows.h>
#include <tchar.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szPath[MAX_PATH] = { 0 }; // 文件路径
TCHAR szBuf[512] = { 0 }; // 追加数据
LARGE_INTEGER liFileSize;
HANDLE hFile, hFileMap;
LPVOID lpMemory;
switch (uMsg) {
case WM_INITDIALOG: {
SetDlgItemText(hwndDlg, IDC_EDIT_PATH, TEXT("D:\\Test.txt"));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OPEN: {
// 打开一个文件
GetDlgItemText(hwndDlg, IDC_EDIT_PATH, szPath, _countof(szPath));
hFile = CreateFile(szPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
CloseHandle(hFile);
return TRUE;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件内容显示到编辑控件中
SetDlgItemText(hwndDlg, IDC_EDIT_TEXT, (LPTSTR)lpMemory);
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDC_BTN_APPEND: {
if (!GetDlgItemText(hwndDlg, IDC_EDIT_APPEND, szBuf, _countof(szBuf))) {
MessageBox(hwndDlg, TEXT("请输入追加内容"), TEXT("提示"), MB_OK);
break;
};
// 打开一个文件
GetDlgItemText(hwndDlg, IDC_EDIT_PATH, szPath, _countof(szPath));
hFile = CreateFile(szPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 为hFile文件对象创建一个文件映射内核对象
// 扩展文件到指定的大小
GetFileSizeEx(hFile, &liFileSize);
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, liFileSize.HighPart, liFileSize.LowPart + _tcslen(szBuf) * sizeof(TCHAR), NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 写入追加数据
memcpy_s((LPBYTE)lpMemory + liFileSize.QuadPart, _tcslen(szBuf) * sizeof(TCHAR), szBuf, _tcslen(szBuf) * sizeof(TCHAR));
FlushViewOfFile(lpMemory, 0);
// 把新文件内容显示到编辑控件中
SetDlgItemText(hwndDlg, IDC_EDIT_TEXT, (LPTSTR)lpMemory);
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

多进程间数据共享

启动多个同一个.exe时,可执行代码的页面为两个进程所共享。但当一个程序实例修改并写入一个内存页,则将导致混乱,这时引进写时复制。系统统计多少页面可写,从页面交换文件中分配空间来容纳,当线程视图写入一个共享页面时,系统从分配的页面交换文件中找一个空闲页面,并把要修改的页面复制到空闲页面,更新进程页面表,这个进程即可访问它自己的副本。

CoCreateGuid

动态生成一个GUID:

1
2
3
4
5
6
GUID guid;
TCHAR szGUID[64] = { 0 };
// 生成一个GUID
CoCreateGuid(&guid);
// 转换为字符串
wsprintf(szGUID, TEXT("%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"),guid.Data1, guid.Data2, guid.Data3,guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <windows.h>
#include "resource.h"
#define BUF_SIZE 4096
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HANDLE hFileMap;
static LPVOID lpMemory;
switch (uMsg) {
case WM_INITDIALOG: {
// 创建或打开一个 命名文件映射内核对象,BUF_SIZE字节
hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, TEXT("2F4368E6-09A1-4D5E-ACC9-C1BDBB041BF7"));
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 创建一个计时器,每一秒钟在静态控件中刷新显示一次共享数据
SetTimer(hwndDlg, 1, 1000, NULL);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_EDIT_SHARE: {
// 编辑控件中的内容改变
if (HIWORD(wParam) == EN_UPDATE)
GetDlgItemText(hwndDlg, IDC_EDIT_SHARE, (LPTSTR)lpMemory, BUF_SIZE);
break;
};
case IDCANCEL: {
// 清理工作
KillTimer(hwndDlg, 1);
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_TIMER: {
SetDlgItemText(hwndDlg, IDC_STATIC_SHARE, (LPTSTR)lpMemory);
return TRUE;
};
};
return FALSE;
};

大型文件处理

复制大型文件,注意MapViewOfFile的文件映射对象偏移量参数必须指定为内存分配粒度整数倍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BOOL CopyLargeFile(LPCTSTR lpFileName1, LPCTSTR lpFileName2) {
HANDLE hFile1, hFile2, hFileMap;
LPVOID lpMemory;
//打开文件1
hFile1 = CreateFile(lpFileName1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile1 == INVALID_HANDLE_VALUE)
return FALSE;
//创建文件2
hFile2 = CreateFile(lpFileName2, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile2 == INVALID_HANDLE_VALUE)
return FALSE;
//为hFile1文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile1, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap)
return FALSE;
//获取文件大小 内存分配粒度
__int64 qwFileSize;
DWORD dwFileSizeHigh;
SYSTEM_INFO si;
qwFileSize = GetFileSize(hFile1, &dwFileSizeHigh);
qwFileSize += (((__int64)dwFileSizeHigh) << 32);
GetSystemInfo(&si);
//把文件映射对象hFileMap不断映射到进程的虚拟地址空间中
__int64 qwFileOffset = 0; //文件映射对象偏移量
DWORD dwBytesInBlock; //本次映射大小
while (qwFileSize > 0) {
dwBytesInBlock = si.dwAllocationGranularity;
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBytesInBlock);
if (!lpMemory)
return FALSE;
//对已映射部分进程操作
WriteFile(hFile2, lpMemory, dwBytesInBlock, NULL, NULL);
//取消本次映射 进行下一轮映射
UnmapViewOfFile(lpMemory);
qwFileOffset += dwBytesInBlock;
qwFileSize -= dwBytesInBlock;
};
//清理工作
CloseHandle(hFileMap);
CloseHandle(hFile1);
CloseHandle(hFile2);
return TRUE;
};

APC异步过程调用

ReadFileEx/WriteFileEx

在指定文件或其他I/O设备中读/写数据,系统异步报告完成状态,并在操作完成/取消时且调用线程处于可通知/提醒的等待状态时调用指定的例程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL WINAPI ReadFileEx(
_In_ HANDLE hFile, //文件或其他I/O设备句柄
_Out_ LPVOID lpBuffer, //接收为你教案或其他I/O设备数据的缓冲区
_In_ DWORD nNumberOfBytesToRead, //要读取的字节数
_Inout_ LPOVERLAPPED lpOverlapped,
_In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //完成例程
); //成功TRUE 失败FALSE
BOOL WINAPI WriteFileEx(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer, //要写入文件或其他I/O设备的数据的缓冲区
_In_ DWORD nNumberOfBytesToWrite, //要写入的字节数
_Inout_ LPOVERLAPPED lpOverlapped,
_In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

对于hFile,在CreateFile时dwFlagsAndAttributes需要FILE_FLAG_OVERLAPPED标志。

OVERLAPPED结构为:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _OVERLAPPED {
ULONG_PTR Internal; //I/O请求的状态代码
ULONG_PTR InternalHigh; //已传输的字节数
union {
struct {
DWORD Offset; //从文件开始读取/写入的字节偏移的低32位
DWORD OffsetHigh; //从文件开始读取/写入的字节偏移的高32位
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, * LPOVERLAPPED;

要写入文件末尾时,Offset和OffsetHigh都指定为0xFFFFFFFF。

完成例程lpCompletionRoutine定义形式为:

1
2
3
4
5
VOID WINAPI OverlappedCompletionRoutine(
_In_ DWORD dwErrorCode, //I/O请求的状态代码
_In_ DWORD dwNumberOfBytesTransfered, //已传输字节数
_Inout_ LPOVERLAPPED lpOverlapped //调用I/O时指定的
)

这里的dwErrorCode和dwNumberOfBytesTransfered与OVERLAPPED结构的Internal和InternalHigh效果一样。异步I/O操作成功完成并执行完成例程,则dwErrorCode值为ERROR_SUCCESS。

使调用线程处于可通知的等待状态的方法有SleepExMsgWaitForMultipleObjectsExWaitForSingleObjectExWaitForMultipleObjectsEx等,当函数执行成功,异步I/O完成后并等待调用线程进入可通知的等待状态后,系统调用完成例程并执行结束后,等待函数返回值为WAIT_IO_COMPLETION。

系统创建一个线程时,同时创建一个相关联的APC异步过程调用队列。调用ReadFileEx/WriteFileEx函数后,系统将完成例程地址传递给设备驱动程序,设备驱动程序在发出I/O请求的线程APC队列中添加一项,该项包括完成例程的地址和发出I/O请求时所使用的OVERLAPPED结构的地址。调用等待函数时,若APC队列中没有项目则一直等待直到超时。出现一项时系统取出并删除,调用完成例程。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <windows.h>
#include "resource.h"
#pragma comment(lib, "Winmm.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 传递给完成例程的自定义数据结构
typedef struct _IOData{
HANDLE m_hFileSource; // 源文件句柄
HANDLE m_hFileDest; // 目标文件句柄
HANDLE m_hFileMap; // 源文件映射对象
LPVOID m_lpMemory; // 映射的虚拟地址
}IOData, * PIOData;
// 全局变量
HINSTANCE g_hInstance;
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 完成例程
VOID WINAPI OverlappedCompletionRoutine(
DWORD dwErrorCode, // I/O请求的状态代码
DWORD dwNumberOfBytesTransfered,// 已传输的字节数
LPOVERLAPPED lpOverlapped // 当初调用I/O函数时指定的那个OVERLAPPED结构的指针
);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
TCHAR szFileNameSource[MAX_PATH] = { 0 };
TCHAR szFileNameDest[MAX_PATH] = { 0 };
OPENFILENAME ofnSource = { 0 };
ofnSource.lStructSize = sizeof(ofnSource);
ofnSource.hwndOwner = hwndDlg;
ofnSource.lpstrFilter = TEXT("All(*.*)\0*.*\0");
ofnSource.nFilterIndex = 1; // 默认选择第1个过滤器
ofnSource.lpstrFile = szFileNameSource; // 返回用户选择的文件名的缓冲区
ofnSource.lpstrFile[0] = NULL; // 不需要初始化文件名编辑控件
ofnSource.nMaxFile = _countof(szFileNameSource);
ofnSource.lpstrTitle = TEXT("请选择要打开的文件"); // 对话框标题栏中显示的字符串
ofnSource.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_CREATEPROMPT;
OPENFILENAME ofnDest = { 0 };
ofnDest.lStructSize = sizeof(ofnDest);
ofnDest.hwndOwner = hwndDlg;
ofnDest.lpstrFilter = TEXT("All(*.*)\0*.*\0");
ofnDest.nFilterIndex = 1; // 默认选择第1个过滤器
ofnDest.lpstrFile = szFileNameDest; // 返回用户选择的文件名的缓冲区
ofnDest.lpstrFile[0] = NULL; // 不需要初始化文件名编辑控件
ofnDest.nMaxFile = _countof(szFileNameDest);
ofnDest.lpstrTitle = TEXT("请选择要保存的文件名"); // 对话框标题栏中显示的字符串
ofnDest.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT;
HANDLE hFileSource = INVALID_HANDLE_VALUE, hFileDest = INVALID_HANDLE_VALUE;
HANDLE hFileMap = NULL;
LPVOID lpMemory = NULL;
DWORD dwFileSizeLow = 0;
DWORD dwFileSizeHigh = 0;
BOOL bRet = FALSE;
DWORD dwRet = 0;
PIOData pIOData = NULL;
LPOVERLAPPED lpOverlapped = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_SOURCE: {
if (GetOpenFileName(&ofnSource))
SetDlgItemText(hwndDlg, IDC_EDIT_SOURCE, szFileNameSource);
break;
};
case IDC_BTN_DEST: {
if (GetSaveFileName(&ofnDest))
SetDlgItemText(hwndDlg, IDC_EDIT_DEST, szFileNameDest);
break;
};
case IDC_BTN_COPY: {
// 打开源文件
GetDlgItemText(hwndDlg, IDC_EDIT_SOURCE, szFileNameSource, _countof(szFileNameSource));
hFileSource = CreateFile(szFileNameSource, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (hFileSource == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("打开源文件失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 获取源文件大小
dwFileSizeLow = GetFileSize(hFileSource, &dwFileSizeHigh);
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFileSource, NULL, PAGE_READWRITE, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 创建目标文件
GetDlgItemText(hwndDlg, IDC_EDIT_DEST, szFileNameDest, _countof(szFileNameDest));
hFileDest = CreateFile(szFileNameDest, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (hFileDest == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("创建目标文件失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 分配一个IOData结构,把相关句柄传递过去以释放
pIOData = new IOData;
pIOData->m_hFileSource = hFileSource;
pIOData->m_hFileDest = hFileDest;
pIOData->m_hFileMap = hFileMap;
pIOData->m_lpMemory = lpMemory;
// 异步写入目标文件
lpOverlapped = new OVERLAPPED;
ZeroMemory(lpOverlapped, sizeof(OVERLAPPED));
lpOverlapped->Offset = 0;
lpOverlapped->OffsetHigh = 0;
lpOverlapped->hEvent = pIOData; // 通过hEvent字段传递给完成例程一个自定义数据结构
bRet = WriteFileEx(hFileDest, lpMemory, dwFileSizeLow, lpOverlapped, OverlappedCompletionRoutine);
// 可以接着去做一些其他事情,这里播放一首音乐
PlaySound(TEXT("爱是一缕寂寞的愁.wav"), NULL, SND_FILENAME | SND_ASYNC/* | SND_LOOP*/);
break;
};
case IDC_BTN_QUERY: {
dwRet = SleepEx(INFINITE, TRUE);
break;
};
};
return TRUE;
};
case WM_CLOSE: {
EndDialog(hwndDlg, 0);
return TRUE;
};
};
return FALSE;
};
VOID WINAPI OverlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
PIOData pIOData = (PIOData)(lpOverlapped->hEvent);
MessageBox(g_hwndDlg, TEXT("写入目标文件完成"), TEXT("提示"), MB_OK);
// 清理工作
UnmapViewOfFile(pIOData->m_lpMemory);
CloseHandle(pIOData->m_hFileMap);
CloseHandle(pIOData->m_hFileSource);
CloseHandle(pIOData->m_hFileDest);
delete pIOData;
delete lpOverlapped;
return;
};

CancelIo

取消调用线程为指定文件或其他I/O设备发出的所有未完成的异步I/O操作:

1
2
3
BOOL WINAPI CancelIo(
_In_ HANDLE hFile
)

CancelIoEx

同上,取消当前进程的另一个线程中异步I/O操作:

1
2
3
4
BOOL WINAPI CancelIoEx(
_In_ HANDLE hFile,
_In_opt_ LPOVERLAPPED lpOverlapped
)

CancelSynchronousIo

取消指定线程中未完成的同步I/O操作:

1
2
3
BOOL WINAPI CancelSynchronousIo(
_In_ HANDLE hThread
)

线程句柄需要THREAD_TERMINATE访问权限。

QueueUserAPC

将一个用户模式APC对象添加到指定线程APC队列:

1
2
3
4
5
DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC, //APC回调函数指针
_In_ HANDLE hThread, //线程句柄
_In_ ULONG_PTR dwData //传递给APC函数的参数
) //成功非0 失败0

APC回调函数定义格式:

1
2
3
VOID APCProc(
_In_ ULONG_PTR Parameter //传递来的参数
)