Win32 备份 API 函数(
BackupRead、BackupWrite 等等),可被用来枚举文件中的流。不过,它们用起来有点怪异,而且看上去更像一个工作区,而不是有效的最终的解决方案。
其思路是,当您想要备份一个文件或整个文件夹时,您需要打包并存储全部可能存在的信息。因此,当需要尝试枚举文件中的流时,BackupRead() 是您最好的朋友。我将重点介绍该函数的原型:
BOOL BackupRead(
HANDLE hFile,
LPBYTE lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
BOOL bAbort,
BOOL bProcessSecurity,
LPVOID *lpContext
);
为了我们的目的,您此处可忽略诸如上下文和安全等方面。
hFile 参数必须通过调用
CreateFile() 获得,而
lpBuffer 应指向
WIN32_STREAM_ID 数据结构:
typedef struct _WIN32_STREAM_ID {
DWORD dwStreamId;
DWORD dwStreamAttributes;
LARGE_INTEGER Size;
DWORD dwStreamNameSize;
WCHAR cStreamName[ANYSIZE_ARRAY];
} WIN32_STREAM_ID, *LPWIN32_STREAM_ID;
这种结构的前 20 个字节表示每个流的标题。流的名称紧随
dwStreamNameSize 字段后面出现,名称后面跟着流的内容。因为传统的文件内容可被视为流 — 尽管是未命名的流,所以要想枚举所有的流,您只需进行循环,直到
BackupRead 返回 False。实际上,
BackupRead 应该能读取所有与给定的文件或文件夹相关的信息:
WIN32_STREAM_ID sid;
ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
DWORD dwStreamHeaderSize = (LPBYTE)&sid.cStreamName -
(LPBYTE)&sid+ sid.dwStreamNameSize;
bContinue = BackupRead(hfile, (LPBYTE) &sid,
dwStreamHeaderSize, &dwRead, FALSE, FALSE,
&lpContext);
上面的这个小段是在流的标题中读到的关键代码。如果该操作是成功的,即可尝试读取该流的实际名称:
WCHAR wszStreamName[MAX_PATH];
BackupRead(hfile, (LPBYTE) wszStreamName, sid.dwStreamNameSize,
&dwRead, FALSE, FALSE, &lpContext);
在访问下一个流之前,首先要调用
BackupSeek(),向前移动备份指示器:
BackupSeek(hfile, sid.Size.LowPart, sid.Size.HighPart,
&dw1, &dw2, &lpContext);
在多数情况下,您可将流视为常规文件 — 如,要删除流,可以用
DeleteFile()。如果想要刷新流的内容,只需使用
ReadFile() 和
WriteFile()。没有正式的和得到支持的方法来移动或重新命名流。在本文的最后部分,我将利用本代码建立一个 NTFS 2000 专用的 Windows shell 扩展,将新的属性页添加到所有带流信息的文件中。