Данный пример иллюстрирует применение некоторых функций для работы с томами и файлами. Это приложение построено на основе примера 1B, использующего распаковщики сообщений. В окне, созданным данным приложением, просто будут перечисляться имена томов и некоторая информация о них, а также о назначении устройств DOS.
Интересно рассмотреть, каким образом осуществляется вывод информации о томах в окно приложения. Это можно было–бы реализовать разными способами:
Хуже некуда. Изменить в примере 1B.CPP обработку сообщения WM_PAINT (функция Cls_OnPaint), так, что бы при обработке сообщения опрашивать все устройства и выводить необходимую информацию. Очень плохо в этом то, что такой опрос может занимать значительное время, и приводить к обращениям к устройствам при каждой перерисовке окна. Еще хуже то, что при попытке чтения информации с устройства возможно возникновение ошибок, о которых система будет сообщать в отдельном всплывающем окошке, например “Cannot read from drive A:”. Это окошко окажется, с большой вероятностью, поверх окна приложения, следовательно, при закрытии окна сообщения, наше приложение снова получит сообщение WM_PAINT, снова попробует обратиться к тому–же устройству... и так далее (что бы этого избежать можно воспользоваться функцией SetErrorMode, отключив вывод сообщений об ошибках).
Чуть лучше. Считывать информацию об устройствах при создании окна, формировать где–то в памяти весь текст, затем отображать его при обработке WM_PAINT. Этот способ качественно лучше тем, что получение данных и их отображение осуществляется в разных местах программы и в разное время. Однако и у него есть минусы — список устройств может изменяться во время работы приложения, а оно этого не отразит, и, кроме того, список может оказаться достаточно большим — больше размеров окна. Первую проблему мы решать сейчас даже не будем — этого легко добиться введя меню с командой Обновить (Refresh) или выполняя такое обновление через определенный интервал времени. Вторая проблема приведет к добавлению собственного интерфейса — обработке сообщений клавиатуры и мыши, что потребует написания значительного кода и продолжительной отладки.
Еще лучше. Чуть разовьем второй способ — получать информацию будем при создании окна, а отображение и работу с мышью и клавиатурой переложим на Windows. Windows предоставляет разработчикам несколько стандартных классов окон, реализующих самые распространенные элементы управления — кнопки, флажки, списки, простейшее окно–редактор и другие. Вот окном–редактором мы и воспользуемся, причем специально укажем, что текст является неизменяемым, то есть редактор будет работать как окно просмотра. В этом случае при создании главного окна приложения, мы должны создать дочернее окно–редактор, занимающее всю внутреннюю область главного окна[ii]. Затем, сформировав весь необходимый текст, передать его редактору. Окно редактирования будет снабжено полосами прокрутки, поддерживать работу с клавиатурой и мышью, осуществлять передачу текста в буфер обмена — и все само, без разработки дополнительного кода. Мы должны доделать совсем немного — обрабатывать сообщения, связанные с изменением размера главного окна (соответственно менять размер дочернего окна), при уничтожении главного окна не забыть уничтожить окно–редактор, а сообщение WM_PAINT мы можем вообще не обрабатывать.
Приложение рассчитано на работу в Win32, однако это связано только лишь с применением функций Win32 API для получения информации о томах. При создании приложения для Windows 3.x вместо функций, не декларированных в Windows API, используются функции–эмуляторы, включаемые в это приложение при компиляции 16ти разрядного приложения. Это обеспечивает возможность нормальной компиляции и работы приложения на обеих платформах, но с несколько ограниченными возможностями в случае применения Windows API (строго говоря, функции–эмуляторы можно было бы сделать и более мощными, опираясь на функции и структуры данных MS–DOS — просто это выходит за рамки данной книги).
Функции DefineDosDevice и QueryDosDevice, применяемые в числе прочих в этом приложении, работают только в виде 32х разрядного приложения и только под Windows NT, так как они имеют смысл исключительно для реализации Win32 в Windows NT.
Файл 2B.CPP
#define STRICT
#include <windows.h>
#include <windowsx.h>
#define UNUSED_ARG(arg) (arg)=(arg)
#ifndef __NT__
// определим необходимые функции при компиляции для Windows API
#include "2b16.cpp"
// текст файла 2b16.cpp приведен ниже
#endif
static char szWndClass[]= "test volume functions";
static HINSTANCE hInstance;
BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{UNUSED_ARG(lpCreateStruct);
char *temp, *p, *s;
DWORD n, i, sernum, complen, flags, spc, bps, fc, tc;
char file_system[ 128 ], buffer[ 1024 ];
UINT count, errmode;
HWND hwndView;
RECT rc;
HFILE hf;
static OFSTRUCT ofs;
static char tempfile[] = “c:\\test.txt”;
static char devZ[] = “Z:”;
// создадим окно-редактор, занимающее всю внутреннюю область окна
GetClientRect(hwnd, &rc);
// теоретически в CREATESTRUCT указаны размеры окна, но к сожалению они
// могут быть указаны как нулевые, хотя это не так. Функция GetClientRect
// в этом случае все равно возвращает корректные данные
hwndView = CreateWindow(
"EDIT", "",
WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_MULTILINE|ES_READONLY,
0, 0, rc.right, rc.bottom, hwnd, (HMENU)1, hInstance, NULL);
if (!IsWindow(hwndView)) {
// если окно просмотра создать не удалось, то завершаем приложение
// сообщив об ошибке
MessageBox(NULL, "Cannot create viewer window!", NULL, MB_OK);
return FALSE;}
// для красоты используем моноширинный шрифт (подробнее см. GDI)
SetWindowFont(hwndView, GetStockObject(ANSI_FIXED_FONT), FALSE);
// функция SetWindowFont в документации не описана, это макрос, определяемый
// в windowsx.h Подробнее — см. исходный текст этого файла.
// запоминаем хендл окна просмотра в структуре описания главного окна
// (при регистрации класса надо зарезервировать 4 байта — что бы и в
// Windows API и в Win32 API использовать одинаковые значения и функции)
SetWindowLong(hwnd, 0, (LONG)hwndView);
// 1. мы не знаем заранее, как много места понадобиться для описания всех
// томов и устройств DOS. Для простоты создадим временный файл с заранее
// заданным именем c:\test.txt (строго говоря, надо было бы проверить наличие
// переменной TEMP или TMP, убедиться, что она указывает на корректный
// каталог - в жизни часто она указывает на несуществующий каталог, диск
// или даже на защищенный диск - всякое бывает - и создать файл там — но это
// все слишком громоздко для примера).
// 2. в этом файле мы соберем нужный текст, а затем разом передадим его окну
// просмотра, после чего файл удалим.
// 3. для работы с файлами используем функции Windows API, что бы сохранить
// переносимость между разными платформами
hf = _lcreat(tempfile, 0);
if (hf == HFILE_ERROR) {
// при ошибке - сообщаем и заканчиваем (окно просмотра будет уничтожено
// позже, при обработке WM_DESTROY
MessageBox(NULL, "Cannot create temporary file!", NULL, MB_OK);
return FALSE;}
// пишем первый заголовок
_lwrite(hf, "******** GET VOLUME INFORMATION ********\r\n\r\n", 44);
SetLastError(ERROR_SUCCESS);
n = GetLogicalDriveStrings(0, NULL);
if (GetLastError()!= ERROR_SUCCESS) {
// Функция GetLogicalDriveStrings не реализована в Win32s и (в книгах
// часто утверждают) в Windows-95, возможно, иногда это и так, но у меня
// в Windows-95 версии 4.00.950a, локализованной для России, работает.
// если все же нет, то эмулируем ее работу посредством GetLogicalDrives
// которая точно есть во всех реализациях Win32
n = GetLogicalDrives();
// узнаем, сколько всего устройств
count = 0;
for (i = 1; i; i <<= 1) if (n & i) count++;
// формируем строку с именами устройств
temp = new char [ count * 4 + 1 ];
if (temp) {
p = temp;
for (i = 0; i < 32; i ++) if (n & (1L << i)) {
*p++ = (char)('A' + i); *p++ = ':'; *p++ = '\\'; *p++ = '\0';}
*p++ = '\0';}
} else {
// если функция GetLogicalDriveStrings работает, то используем ее
temp = new char [ n + 1 ];
if (temp) GetLogicalDriveStrings(n, temp);
}
if (temp) {
// исключаем обработку сообщений о критической ошибке
errmode = SetErrorMode(SEM_FAILCRITICALERRORS);
// если строка получена, то разбираем ее по частям
for (p = temp; *p; p += lstrlen(p) + 1) {
wsprintf(buffer, "Root: %s", p);
switch (GetDriveType(p)) {
case 0: s = "???"; break;
case 1: s = "invalid"; break;
case DRIVE_REMOVABLE: s = "REMOVABLE"; break;
case DRIVE_FIXED: s = "FIXED"; break;
case DRIVE_REMOTE: s = "REMOTE"; break;
case DRIVE_CDROM: s = "CD-ROM"; break;
case DRIVE_RAMDISK: s = "RAM DISK"; break;
default: s = "what?!"; break;}
// укажем явное преобразование указателя на строку ‘s’ к типу
// LPSTR — так как в Windows API для моделей памяти с одним сегментом
// данных (tiny,small,medium) указатели по умолчанию 16-ти разрядные
// а для функций Windows обязательно нужны 32-х разрядные
wsprintf(buffer+lstrlen(buffer), " type=%s volume='", (LPSTR)s);
sernum = complen = flags = 0; file_system[0] = '\0';
if (
GetVolumeInformation(
p, buffer+lstrlen(buffer), 64,
&sernum, &complen, &flags,
file_system, sizeof(file_system)))
{// если информация о томе прочитана -> получаем и выводим более
// подробные сведения
wsprintf(buffer+lstrlen(buffer), "' serial=%08lX\r\n", sernum);
_lwrite(hf, buffer, lstrlen(buffer));
spc = bps = fc = tc = 0L;
GetDiskFreeSpace(p, &spc, &bps, &fc, &tc);
bps *= spc;
wsprintf(
buffer,
" comp. length=%lu cluster=%lu total=%luK free=%luK\r\n",
complen, bps, bps*tc/1024, bps*fc/1024);
_lwrite(hf, buffer, lstrlen(buffer));
wsprintf(buffer, " file system ='%s' flags=", file_system);
s = buffer + lstrlen(buffer);
if (flags & FS_CASE_IS_PRESERVED) {
lstrcpy(s, "CASE_PRESERVED "); s+= lstrlen(s);}
if (flags & FS_CASE_SENSITIVE) {
lstrcpy(s, "CASE_SENSITIVE "); s+= lstrlen(s);}
if (flags & FS_UNICODE_STORED_ON_DISK) {
lstrcpy(s, "UNICODE "); s+= lstrlen(s);}
if (flags & FS_PERSISTENT_ACLS) {
lstrcpy(s, "ACL "); s+= lstrlen(s);}
if (flags & FS_FILE_COMPRESSION) {
lstrcpy(s, "MAY_COMPRESS "); s+= lstrlen(s);}
if (flags & FS_VOL_IS_COMPRESSED) {
lstrcpy(s, "COMPRESSED "); s+= lstrlen(s);}
lstrcpy(s, "\r\n");
_lwrite(hf, buffer, lstrlen(buffer));
} else {
// если информация о томе не получена, то просто сообщаем
lstrcpy(
buffer+lstrlen(buffer),
"' ***** NO VOLUME INFORMATION!\r\n");
_lwrite(hf, buffer, lstrlen(buffer));}}
delete temp;
// восстанавливаем режим обработки критических ошибок
SetErrorMode(errmode);} else {
// если возникла ошибка (не хватило памяти для получения списка устройств)
// то просто выводим текст с сообщением, но не заканчиваем, что бы не
// заботиться об уничтожении временного файла досрочно
_lwrite(hf, "NOT ENOUGHT MEMORY TO GET INFORMATION!\r\n", 40);}
// пишем второй заголовок
_lwrite(hf, "\r\n******** READ DOS DEVICES MAP ********\r\n\r\n", 44);
// просто для примера назначаем Z: как каталог, содержащий win.com
GetWindowsDirectory(buffer, sizeof(buffer));
SetLastError(ERROR_SUCCESS);
DefineDosDevice(0, devZ, buffer);
if (GetLastError()!= ERROR_SUCCESS) {
// функция DefineDosDevice не реализована - это не Windows NT!
_lwrite(hf, "NOTE: DefineDosDevice() is not implemented!\r\n", 45);
} else {
// если назначить удалось - это NT, получаем полный список и выводим его
// хотя может быть и Windows 98
buffer[ QueryDosDevice(NULL, buffer, 1024) ] = ‘\0’;
// функция QueryDosDevice возвращает число символов, скопированных в буфер
// и в случае Windows 98 мы получим 0, хотя сам буфер может содержать мусор.
for (p = buffer; *p; p += lstrlen(p) + 1) {
_lwrite(hf, " ", 15 - lstrlen(p));
_lwrite(hf, p, lstrlen(p));
_lwrite(hf, " = ", 3);
// используем массив file_system в качестве временной строки
QueryDosDevice(p, file_system, sizeof(file_system));
_lwrite(hf, file_system, lstrlen(file_system));
_lwrite(hf, "\r\n", 2);}
GetWindowsDirectory(buffer, sizeof(buffer));
// удаляем назначенное нами устройство
DefineDosDevice(
DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, "Z:", buffer);}
// узнаем длину файла с текстом и выделяем необходимый блок памяти
n = _llseek(hf, 0L, 1);
temp = new char [ (int)n + 1 ];
if (temp) {
// загружаем его в память и добавляем оканчивающий символ ‘\0’
_llseek(hf, 0L, 0);
temp[ _lread(hf, temp, (int)n) ] = '\0';
// и передаем окну просмотра
SetWindowText(hwndView, temp);
// наш временный буфер больше не нужен
delete temp;
} else {
// если памяти не хватило – сообщаем
SetWindowText(hwndView, "*** Cannot load text ***");}
// закрываем временный файл и удаляем его - система сама этого не сделает
_lclose(hf);
OpenFile(tempfile, &ofs, OF_DELETE);
return TRUE;}
void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy)
{UNUSED_ARG(state);
// при изменении размеров главного окна меняем размеры окна просмотра
HWND hwndView = (HWND)GetWindowLong(hwnd, 0); if (IsWindow(hwndView)) MoveWindow(hwndView, 0,0, cx, cy, TRUE);}
void Cls_OnDestroy(HWND hwnd)
{// при закрытии главного окна закрываем его дочернее (лучше это сделать
// самим, хотя в крайнем случае система это сделает за вас)
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) DestroyWindow(hwndView);
PostQuitMessage(0);}
void Cls_OnSetFocus(HWND hwnd, HWND hwndOldFocus)
{UNUSED_ARG(hwndOldFocus);
// при получении фокуса главным окном - активируем окно просмотра, что бы
// работала клавиатура
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) SetFocus(hwndView);}
LONG WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {
HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, Cls_OnSize);
HANDLE_MSG(hWnd, WM_SETFOCUS, Cls_OnSetFocus);
default: break;}
return DefWindowProc(hWnd, uMsg, wParam, lParam);}
static BOOL init_instance(HINSTANCE hInstance)
{WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 4; // для хендла окна просмотра
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass(&wc) == NULL? FALSE: TRUE;}
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{UNUSED_ARG(lpszCmdLine);
MSG msg;
HWND hWnd;
if (!hPrevInst) {
if (!init_instance(hInst)) return 1;}
hWnd= CreateWindow(
szWndClass, // class name
"window header", // window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT,CW_USEDEFAULT, // window position
CW_USEDEFAULT,CW_USEDEFAULT, // window size
NULL, // parent window
NULL, // menu
hInst, // current instance
NULL // user-defined parameters);
if (!hWnd) return 1;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}
return msg.wParam;}
Файл 2B16.CPP
// включаем описания необходимых функций библиотеки времени выполнения
#include <dos.h>
#include <io.h>
// функции GetLastError и SetLastError не описаны в Windows API, эмулируем их
#define ERROR_SUCCESS 0
static int error_code = ERROR_SUCCESS;
int GetLastError(void)
{return error_code;}
int SetLastError(int err)
{int prev = error_code; return error_code = err;}
// функция GetLogicalDriveStrings не реализована в Windows API, причем в некоторых // реализациях Win32 она представлена заглушкой, которую мы и эмулируем
int GetLogicalDriveStrings(DWORD size, char* buf)
{UNUSED_ARG(size);
UNUSED_ARG(buf);
SetLastError(1);
return 0;}
// функция GetLogicalDrives не реализована в Windows API, но нам она нужна
// для нормальной работы приложения - эмулируем ее
DWORD GetLogicalDrives(void)
{DWORD dwDevices = 0L;
unsigned n, uTotal, uCurrent, uTest;
// запоминаем текущее устройство
_dos_getdrive(&uCurrent);
_dos_setdrive(uCurrent, &uTotal);
for (n = 1; n <= uTotal; n++) {
_dos_setdrive(n, &uTotal);
_dos_getdrive(&uTest);
// если устройство удалось сделать текущим - оно описано в системе
if (uTest == n) dwDevices |= 1L << (n -1);}
// восстанавливаем прежнее устройство
_dos_setdrive(uCurrent, &uTotal);
return dwDevices;}
// функция GetDriveType в Win32 API реализована несколько иначе, чем в Windows;
// переопределим функцию Win32 API UINT GetDriveType(LPSTR) через функцию
// Windows API UINT GetDriveType(UINT), заодно определим пару недостающих // символов
#ifndef DRIVE_CDROM
#define DRIVE_CDROM 5
#endif
#ifndef DRIVE_RAMDISK
#define DRIVE_RAMDISK 6
#endif
UINT GetDriveType(char* pRoot)
{char c;
if (pRoot) {
c = (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') return GetDriveType(c - 'A');}
return 0;}
// функция GetVolumeInformation не определена в Windows API. Эмулируем ее // в сильно упрощенном виде, заодно определяем необходимые символы.
#define FS_CASE_IS_PRESERVED 1
#define FS_CASE_SENSITIVE 2
#define FS_UNICODE_STORED_ON_DISK 4
#define FS_PERSISTENT_ACLS 16
#define FS_FILE_COMPRESSION 32
#define FS_VOL_IS_COMPRESSED 64
BOOL GetVolumeInformation(
char *pRoot, char* pVolume, int cbVolume,
DWORD *sernum, DWORD *complen, DWORD *flags,
char *pFS, int cbFS)
{BOOL fReturnCode = FALSE;
char c;
unsigned uDrive, uCurrent, n;
UNUSED_ARG(cbVolume);
UNUSED_ARG(cbFS);
if (pRoot) {
c= (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') {
uDrive = (unsigned)(c - 'A' + 1);
_dos_getdrive(&uCurrent);
_dos_setdrive(uDrive, &n);
_dos_getdrive(&n);
if (uDrive == n) {// убедимся, что устройство определено
// строго говоря все это стоит определить точнее:
pVolume[0] = '\0';
*sernum = 0L;
*complen = 12;
*flags = (long)FS_CASE_IS_PRESERVED;
pFS = '\0';
// проверим, есть ли доступ к устройству - если мы вернем
// TRUE, то позже нами будет предпринята попытка узнать
// размер тома
if (!access(pRoot, F_OK)) fReturnCode = TRUE;}
// восстановим текущее устройство
_dos_setdrive(uCurrent, &n);}}
return fReturnCode;}
// функция GetDiskFree не определена в Windows API, эмулируем ее посредством
// функции _dos_getdiskfree стандартной библиотеки времени выполнения.
void GetDiskFreeSpace(char* pRoot, DWORD* spc, DWORD* bps, DWORD* fc, DWORD* tc)
{char c;
diskfree_t df;
if (pRoot) {
c = (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') {
if (!_dos_getdiskfree(c - 'A' + 1, &df)) {
*spc = df.sectors_per_cluster;
*bps = df.bytes_per_sector;
*fc = df.avail_clusters;
*tc = df.total_clusters;}}}}
// функции DefineDosDevice и QueryDosDevice не реализованы в Windows API.
// используем вместо них заглушки, так как они имеют смысл только для NT
#define DDD_REMOVE_DEFINITION 1
#define DDD_EXACT_MATCH_ON_REMOVE 2
BOOL DefineDosDevice(DWORD dwFlags, LPSTR lpDosDevice, LPSTR lpPath)
{UNUSED_ARG(dwFlags);
UNUSED_ARG(lpDosDevice);
UNUSED_ARG(lpPath);
SetLastError(1);
return FALSE;}
DWORD QueryDosDevice(LPSTR lpDosDevice, LPSTR lpPath, DWORD ucchMax)
{UNUSED_ARG(lpDosDevice);
UNUSED_ARG(lpPath);
UNUSED_ARG(ucchMax);
SetLastError(1);
return 0L;}
Резюме
Любопытно, что в существующем виде приложение по–разному работает на разных платформах — для получения информации о томах и доступном пространстве предпринимается попытка чтения с устройства. Однако том в устройстве может отсутствовать вовсе — например, в дисководе может не быть дискеты. Разные платформы реагируют на такое событие разным образом — Windows–95, убедившись что тома нет, просто возвращает нули, а Windows NT или Windows 3.x выдают сообщение о системной ошибке.
В приложении специально применяется функция SetErrorMode, которая позволяет отключить вывод сообщений о невозможности чтения с диска. Любопытно, что в документации часто указано, что эта функция для Win32 API реализована только для RISC процессоров — странно, но на обычных Intel Pentium она тоже сработала. В пояснениях к результатам теста реакция системы на критические ошибки приводится так, как будто эта функция не применяется. На самом деле в приведенном приложении (с применяемой функцией SetErrorMode) сообщений об ошибках не будет.
Приложение 2B было протестировано на 2х компьютерах:
А) компьютер с Windows–95[iii] и Windows 3.11 + Win32s
диск A — 3.5”, дискета не вставлена
диск C — EIDE HDD, том “Bootable”
диск D — SCSI HDD, под Windows 3.11 доступен через ASPI драйвер, том “SCSI_VOL”
диск E — IDE CD–ROM, диск не вставлен
Б) компьютер с Windows NT
диск A — 3.5”, дискета не вставлена
диск C — SCSI HDD,
диск D — IDE CD–ROM, диск не вставлен
Всего было выполнено 6 тестов — запускались 16ти и 32х разрядные версии приложения в среде Windows 3.11 + Win32s, Windows–95 и Windows NT Server 4.0. Результаты тестов следующие:
Среда Windows 3.11+Win32s, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=71712K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='' flags=CASE_PRESERVED
Root: E:\ type=REMOTE volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
16ти разрядное приложение пытается обратиться к дискам в функции access (в эмуляции GetVolumeInformation), что для дисков A (гибкий диск) и E (CD–ROM) приводит к системному сообщению об ошибке “Cannot read from drive...”;
Локальный CD–ROM распознается как сетевое устройство.
Среда Windows 3.11+Win32s, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='BOOTABLE' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=71728K
file system ='FAT' flags=
Root: D:\ type=RAM DISK volume='SCSI_VOL' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='FAT' flags=
Root: E:\ type=CD-ROM volume='SCSI_VOL' serial=00000000
maximal component length=12 cluster=0 total=0K free=0K
file system ='FAT' flags=
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Реакция приложения совершенно другая (используются функции, входящие в реализацию Win32 API, а не их эмуляция нашим приложением).
При обращении к диску A генерируется сообщение об ошибке,
При обращении к диску E (CD–ROM) функция GetVolumeInformation (причем именно ее реализация в Win32s, а не ее эмуляция у нас) дает сообщение об ошибке и сообщает, что информация о томе успешно (?!) получена, после чего следует попытка узнать свободное пространство на отсутствующем диске E — с еще одним сообщением об ошибке (вместо сообщения “no volume information”).
Характерно, что тип диска E — CD–ROM — определен корректно, файловая система — CDFS (файловая система CD дисков) почему–то распознана как FAT, а метку тома позаимствовали у предыдущего диска[iv].
Кроме того, SCSI диск, доступный через ASPI, был распознан как RAM DISK.
Среда Windows 95, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=70208K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='' flags=CASE_PRESERVED
Root: E:\ type=REMOTE volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Уже неплохо — результаты совпадают с тем, что было получено для 16ти разрядного приложения в среде Windows 3.11, реакция на получение информации об отсутствующем диске такая–же — сообщение об ошибке.
Среда Windows 95, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: a:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: c:\ type=FIXED volume='BOOTABLE' serial=0E3219D9
maximal component length=255 cluster=16384 total=822272K free=69696K
file system ='FAT' flags=CASE_PRESERVED UNICODE
Root: d:\ type=FIXED volume='SCSI_VOL' serial=025511DA
maximal component length=255 cluster=32768 total=1065792K free=45408K
file system ='FAT' flags=CASE_PRESERVED UNICODE
Root: e:\ type=CD-ROM volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Пожалуй, самый приличный результат. Все срабатывает корректно и без сообщений о критических ошибках, даже если не использовать функцию SetErrorMode. Типы дисков и файловые системы определяются корректно.
Среда Windows NT, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=999968K free=615136K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=REMOTE volume='' serial=00000000
maximal component length=12 cluster=16384 total=601632K free=0K
file system ='' flags=CASE_PRESERVED
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Поведение приложения вполне соответствует обычному 16ти разрядному приложению в среде Windows 3.x, но вот полный размер диска, превышающий 1Г определяется с ошибкой — диск C на 1.33Г был определен как диск размером 999М.
Среда Windows NT, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00AE5141
maximal component length=255 cluster=512 total=1405655K free=615143K
file system ='NTFS' flags=CASE_PRESERVED CASE_SENSITIVE UNICODE ACL MAY_COMPRESS
Root: D:\ type=CD-ROM volume='ASART3' serial=E2F025BC
maximal component length=221 cluster=2048 total=601644K free=0K
file system ='CDFS' flags=CASE_SENSITIVE
******** READ DOS DEVICES MAP ********
DISPLAY1 = \Device\Video0
NDIS = \Device\Ndis
DISPLAY2 = \Device\Video1
Z: = \??\C:\WINNT
D: = \Device\CdRom0
$VDMLPT1 = \Device\ParallelVdm0
COM1 = \Device\Serial0
COM2 = \Device\Serial10000
PIPE = \Device\NamedPipe
UNC = \Device\Mup
PhysicalDrive0 = \Device\Harddisk0\Partition0
PRN = \DosDevices\LPT1
A: = \Device\Floppy0
Scsi0: = \Device\ScsiPort0
DC21X41 = \Device\DC21X41
LPT1 = \Device\Parallel0
Scsi1: = \Device\ScsiPort1
C: = \Device\Harddisk1\Partition1
AUX = \DosDevices\COM1
MAILSLOT = \Device\MailSlot
NUL = \Device\Null
Единственное, что кажется несколько странным, так это сообщение о критической ошибке при попытке вызова GetVolumeInformation для отсутствующего диска, хотя под Windows–95 эта функция работает молча — она же и так возвращает результат “не удалось”, так зачем же еще давать сообщение? Сообщение об ошибке отключается с помощью SetErrorMode, хотя это и не согласуется с формальным описанием функции.
Итого:
Если ваше приложение может работать в Windows 3.x с Win32s, то необходимо особенно тщательное тестирование всех операций с файлами и томами, так как реакция системы может существенно отличаться от той, которая будет в случае Windows–95 и Windows NT. Кроме того особое внимание стоит уделить функциям, возвращающим информацию о томе (типа GetVolumeInformation) — они производят попытку реального обращения к томам, что может привести либо к возникновению сообщений об ошибках либо даже к «зависанию» всего приложения, если работа с томом сопровождается какой–либо ошибкой. Так, например, отдельной проверки и отладки потребуют все случаи работы со сменными дисками (гибкими, компакт–дисками, магнито–оптическими дисками и т.д.) и с сетевыми томами (особенно случаи, когда удаленный компьютер, предоставляющий свои тома в общее пользование, зависает, отключается или происходят какие–либо неполадки в работе сети — в такой ситуации возможно даже зависание компьютера, с которого производится вызов функции GetVolumeInformation).