В последней главе 26 тома “Библиотеки системного программиста”, который называется “Программирование для Windows NT. Часть первая”, мы рассказали вам о том, как приложения Microsoft Windows NT работают с файлами. При этом мы привели краткое описание функций программного интерфейса операционной системы, выполняющие “классические” файловые операции, такие как открывание и закрывание файла, чтение блока данных из файла в буфер, расположенный в оперативной памяти, запись содержимого такого буфера в файл и так далее. Все эти операции знакомы вам по операционным системам MS-DOS и Microsoft Windows версии 3.1.
Что же касается операционных систем Microsoft Windows NT и Microsoft Windows 95, то в них появилось новое мощное средство, предназначенное для работы с файлами - файлы, отображаемые в память. Это средство, которое, кстати, может использоваться еще и для обмена данными между параллельно работающими процессами, значительно упрощает программирование файловых операций, сводя их к работе с оперативной памятью.
Например, вы можете создать файл, содержащий записи реляционной базы данных. Открыв затем такой файл с использованием отображения на память, приложение может адресоваться к записям как к элементам массива, расположенного в оперативной памяти. При этом операционная система при необходимотси будет самостоятельно выполнять чтение данных из файла и запись данных в файл без специальных усилий со стороны приложения.
После того как мы изучим методику работы с файлами, отображаемыми на память, мы приведем примеры приложений, работающих с файловой системой обычными средствами.
В операционную систему Microsoft Windows NT встроен эффективный механизм виртуальной памяти, описанный нами в предыдущем томе “Библиотеки системного программиста”. При использовании этого механизма приложениям доступно больше виртуальной оперативной памяти, чем объем физической оперативной памяти, установленной в компьютере.
Как вы знаете, виртуальная память реализована с использованием обычной оперативной памяти и дисковой памяти. Когда приложение обращается к странице виртуальной памяти, отсутствующей в физической памяти, операционная система автоматически читает ее из файла виртуальной памяти в физическую память и предоставляет приложению. Если же приложение изменяет содержимое страницы памяти в физической оперативной памяти, операционная система сохраняет такую страницу в файле виртуальной памяти.
Почему мы вспомнили о виртуальной памяти в главе, посвященной файлам?
Потому что механизм работы с файлами, отображаемыми на память, напоминает механизм работы вирутальной памяти.
Напомним, что в операционной системе Microsoft Windows NT каждому процессу выделяется 2 Гбайта адресного пространства. Любой фрагмент этого пространства может быть отображен на фрагмент файла соответствующего размера.
На рис. 1.1 показано отображение фрагмента адресного пространства приложения, размером в 1 Гбайт, на фрагмент файла, имеющего размер 5 Гбайт.
Рис. 1.1. Отображение фрагмента адресного пространства приложения на файл
С помощью соответсвующей функции программного интерфейса, которую мы скоро рассмотрим, приложение Microsoft Windows NT может выбрать любой фрагмент большого файла для отображения в адресное пространство. Поэтому, несмотря на ограничение адресного пространства величиной 2 Гбайт, вы можете отображать (по частям) в это пространство файлы любой длины, возможной в Microsoft Windows NT. В простейшем случае при работе с относительно небольшими файлами вы можете выбрать в адресном пространстве фрагмент подходящего размера и отобразить его на начало файла.
Как выполняется отображение фрагмента адресного пространства на фрагмент файла?
Если установлено такое отображение, то операционная система обеспечивает тождественность содержимого отображаемого фрагмента памяти и фрагмента файла, выполняя при необходимости операции чтения и записи в файл (с буферизацией и кешированием).
В процессе отображения адресного пространства память не выделяется, а только резервируется. Поэтому отображение фрагмента размером 1 Гбайт не вызовет переполнение файлов виртуальной памяти. Более того, при отображении файлы виртуальной памяти вообще не используются, так как страницы фрагмента связываются с отображаемым файлом.
Если приложение обращается в отображенный фрагмент для чтения, возникает исключение. Обработчик этого исключения загружает в физическую оперативную память соответствующую страницу из отображенного файла, а затем возобновляет выполнение прерванной команды. В результате в физическую оперативную память загружаются только те страницы, которые нужны приложению.
При записи происходит аналогичный процесс. Если нужной страницы нет в памяти, она подгружается из отображенного файла, затем в нее выполняется запись. Загруженная страница остается в памяти до тех пор, пока не будет вытеснена другой страницей при нехватке физической оперативной памяти. Что же касается записи измененной страницы в файл, то эта запись будет выполнена при закрытии файла, по явному запросу приложения или при выгрузке страницы из физической памяти для загрузки в нее другой страницы.
Заметим, что операционная система Microsoft Windows NT активно работает с файлами, отобажаемыми в память. В частности, при загрузке исполнимого модуля приложения соответствующий exe- или dll-файл отображается на память, а затем ему передается управление. Когда пользователь запускает вторую копию приложения, для работы используется файл, который уже отображается в память. В этом случае соответствующие страницы виртуальной памяти отображаются в адресные пространства обоих приложений. При этом возникает одна интересная проблема, связанная с использованием глобальных переменных.
Представьте себе, что в приложении определены глобальные переменные. При запуске приложения область глобальных переменных загружается в память наряду с исполнимым кодом. Если запущены две копии приложения, то страницы памяти, отведенные для кода и глобальных данных, будут отображаться в адресные пространства двух разных процессов. При этом существует потенциальная возможность конфликта, когда разные работающие копии одного и того же приложения попытаются установить разные значения для одних и тех же глобальных переменных.
Операционная система Microsoft Windows NT выходит из этой ситуации следующим способом. Если она обнаружит, что одна из копий приложения пытается изменить страницу, в которой хранятся глобальные переменные, она создает для нее еще одну копию страницы. Таким образом, между приложениями никогда не возникает интерференции.
Аналогичная методика используется и для страниц, содержащих программный код. Если приложение (например, отладчик) пытается изменить страницу, содержащую исполнимый код, операционная система Microsoft Windows NT создает еще одну копию страницы и отображает ее в адресное пространство соответствующего процесса. Подробнее об этом мы расскажем в разделе, посвященном использованию файлов, отображаемых на память, для передачи данных между различными процессами.
Рассмотрим процедуру создания отображения файла на память.
Прежде всего, приложение должно открыть файл при помощи функции CreateFile, известной вам из предыдущего тома “Библиоткеи системного программиста”. Ниже мы привели прототип этой функции:
HANDLE CreateFile( LPCTSTR lpFileName, // адрес строки имени файла DWORD dwDesiredAccess, // режим доступа DWORD dwShareMode,// режим совместного использования файла LPSECURITY_ATTRIBUTES lpSecurityAttributes, // дескриптор // защиты DWORD dwCreationDistribution, // параметры создания DWORD dwFlagsAndAttributes, // атрибуты файла HANDLE hTemplateFile); // идентификатор файла с атрибутами
Через параметр lpFileName вы, как обычно, должны передать этой функции адрес текстовой строки, содержащей путь к открываемому файлу.
С помощью параметра dwDesiredAccess следует указать нужный вам вид доступа. Если файл будет открыт только для чтения, в этом параметре необходимо указать флаг GENERIC_READ. Если вы собираетесь выполнять над файлом операции чтения и записи, следует указать логическую комбинацию флагов GENERIC_READ и GENERIC_WRITE XE "GENERIC_WRITE". В том случае, когда будет указан только флаг GENERIC_WRITE, операция чтения из файла будет запрещена.
Не забудьте также про параметр dwShareMode. Если файл будет использоваться одновременно несколькими процессами, через этот параметр необходимо передать режимы совместного использования файла: FILE_SHARE_READ или FILE_SHARE_WRITE.
Остальные параметры этой функции мы уже описали в предыдущем томе.
В случае успешного завершения, функция CreateFile возвращает идентификатор открытого файла. При ошибке возвращается значение INVALID_HANDLE_VALUE. Здесь все как обычно, пока никакого отображения еще не выполняется.
Для того чтобы создать отображение файла, вы должны вызвать функцию CreateFile Mapping, прототип которой приведен ниже:
HANDLE CreateFileMapping( HANDLE hFile, // идентификатор отображаемого файла LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // дескриптор // защиты DWORD flProtect, // защита для отображаемого файла DWORD dwMaximumSizeHigh, // размер файла (старшее слово) DWORD dwMaximumSizeLow, // размер файла (младшее слово) LPCTSTR lpName); // имя отображенного файла
Через параметр hFile этой функции нужно передать идентификатор файла, для которого будет выполняться отображение в память, или значение 0xFFFFFFFF. В первом случае функция CreateFile Mapping отобразит заданный файл в память, а во втором - создаст отображение с использованием файла виртуальной памяти. Как мы увидим позже, отображение с использованием файла виртуальной памяти удобно для организации передачи данных между процессами.
Обратим ваше внимание на одну потенциальную опасность, связанную с использованием в паре функций CreateFile и CreateFileMapping. Если функция CreateFile завершится с ошибкой и эта ошибка не будет обработана приложением, функция CreateFileMapping получит через параметр hFile значение INVALID_HANDLE_VALUE XE "INVALID_HANDLE_VALUE", численно равное 0xFFFFFFFF. В этом случае она сделает совсем не то, что предполагал разработчик приложения: вместо того чтобы выполнить отображение файла в память, функция создаст отображение с использованием файла виртуальной памяти.
Параметр lpFileMappingAttributes задает адрес дескриптора защиты. В большинстве случаев для этого параметра вы можете указать значение NULL.
Теперь займемся параметром flProtect, задающем защиту для создаваемого отображения файла. Для этого параметра вы можете задать следующий набор значений, комбинируя их с дополнительными атрибутами, которые будут перечислены ниже:
Значение |
Описание |
PAGE_READONLY |
К выделенной области памяти предоставляется доступ только для чтения. При создании или открывании файла необходимо указать флаг GENERIC_READ |
PAGE_READWRITE |
К выделенной области памяти предоставляется доступ для чтения и записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE |
PAGE_WRITECOPY |
К выделенной области памяти предоставляется доступ для копирования при записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE. Режим копирования при записи будет описан позже в главе, посвященной обмену данными между процессами |
Эти значения можно комбинировать при помощи логической операции ИЛИ со следующими атрибутами:
Атрибут |
Описание |
SEC_COMMIT |
Если указан этот атрибут, выполняется выделение физических страниц в памяти или в файле виртуальной памяти. Этот атрибут используется по умолчанию |
SEC_IMAGE |
Используется при отображении программного файла, содержащего исполнимый код. Этот атрибут несовместим с остальными перечисленными в этом списке атрибутами |
SEC_NOCACHE |
Отмена кэширования для всех страниц отображаемой области памяти. Должен использоваться вместе с атрибутами SEC_RESERVE или SEC_COMMIT |
SEC_RESERVE |
Если указан этот атрибут, вместо выделения выполняется резервирование страниц виртуальной памяти. Зарезервированные таким образом страницы можно будет получить в пользование при помощи функции VirtualAlloc XE "VirtualAlloc". Атрибут SEC_RESERVE XE "SEC_RESERVE" можно указывать только в том случае, если в качестве параметра hFile функции CreateFile XE "CreateFile" Mapping XE "CreateFileMapping" передается значение 0xFFFFFFFF |
С помощью параметров dwMaximumSizeHigh и dwMaximumSizeLow необходимо указать функции CreateFileMapping 64-разрядный размер файла. Параметр dwMaximumSizeHigh должен содержать старшее 32-разрядное слово размера, а параметр dwMaximumSizeLow - малдшее 32-разрядное слово размера. Для небольших файлов, длина которых укладывается в 32 разряда, нужно указывать нулевое значение параметра dwMaximumSizeHigh.
Заметим, что вы можете указать нулевые значения и для параметра dwMaximumSizeHigh, и для параметра dwMaximumSizeLow. В этом случае предполагается, что размер файла изменяться не будет.
Через параметр lpName можно указать имя отображения, которое будет доступно всем работающим одновременно приложениям. Имя должно представлять собой текстовую строку, закрытую двоичным нулем и не содержащую символов “\”.
Если отображение будет использоваться только одним процессом, вы можете не задавать для него имя. В этом случае значение параметра lpName следует указать как NULL.
В случае успешного завершения функция CreateFileMapping возвращает идентификатор созданного отображения. При ошибке возвращается значение NULL.
Так как имя отображения глобально, возможно возникновение ситуации, когда процесс пытается создать отображение с уже существующим именем. В этом случае функция CreateFileMapping возвращает идентификатор существующего отображения. Такую ситуацию можно определить с помощью функции GetLastError XE "GetLastError", вызвав ее сразу после функции CreateFileMapping. Функция GetLastError при этом вернет значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS".
Итак, мы выполнили первые два шага, необходимые для работы с файлом, отображаемым на память, - открывание файла функцией CreateFile и создание отображения функцией CreateFileMapping. Теперь, получив от функции CreateFileMapping идентификатор объекта-отображения, мы должны выполнить само отображение, вызвав для этого функцию MapViewOfFile XE "MapViewOfFile" или MapViewOfFileEx XE "MapViewOfFileEx". В результате заданный фрагмент отображенного файла будет доступен в адресном пространстве процесса.
Прототип функции MapViewOfFile приведен ниже:
LPVOID MapViewOfFile( HANDLE hFileMappingObject, // идентификатор отображения DWORD dwDesiredAccess, // режим доступа DWORD dwFileOffsetHigh, // смещение в файле (старшее слово) DWORD dwFileOffsetLow, // смещение в файле (младшее слово) DWORD dwNumberOfBytesToMap);// количество отображаемых байт
Функция MapViewOfFile создает окно размером dwNumberOfBytesToMap байт, которое смещено относительно начала файла на количество байт, заданное параметрами dwFileOffsetHigh и dwFileOffsetLow. Если задать значение параметра dwNumberOfBytesToMap равное нулю, будет выполнено отображение всего файла.
Смещение нужно задавать таким образом, чтобы оно попадало на границу минимального пространства памяти, которое можно зарезервировать. Значение 64 Кбайта подходит в большинстве случаев.
Более точно гранулярность памяти можно определить при помощи функции GetSystemInfo. Этой функции в качестве единственного параметра необходимо передать указатель на структуру типа SYSTEM_INFO, определенную следующим образом:
typedef struct _SYSTEM_INFO { union { DWORD dwOemId; // зарезервировано struct { WORD wProcessorArchitecture; // архитектура системы WORD wReserved; // зарезервировано }; }; DWORD dwPageSize; // размер страницы LPVOID lpMinimumApplicationAddress; // минимальный адрес, // доступный приложениям и библиотекам DLL LPVOID lpMaximumApplicationAddress; // максимальный адрес, // доступный приложениям и библиотекам DLL DWORD dwActiveProcessorMask; // маски процессоров DWORD dwNumberOfProcessors; // количество процессоров DWORD dwProcessorType; // тип процессора DWORD dwAllocationGranularity; // гранулярность памяти WORD wProcessorLevel; // уровень процессора WORD wProcessorRevision; // модификация процессора } SYSTEM_INFO;
Функция заполнит поля этой структуры различной информацией о системе. В частности, в поле dwAllocationGranularity будет записан минимальный размер резервируемой области памяти.
Вернемся к описанию функции MapViewOfFile.
Параметр dwDesiredAccess определяет требуемый режим доступа к отображению, то есть режимы доступа для страниц виртуальной памяти, используемых для отображения. Для этого параметра вы можете указать одно из следующих значений:
Значение |
Описание |
FILE_MAP_WRITE |
Доступ на запись и чтение. При создании отображения функции CreateFileMapping необходимо указать тип защиты |
FILE_MAP_READ |
Доступ только на чтение. При создании отображения необходимо указать тип защиты PAGE_READWRITE или PAGE_READ |
FILE_MAP_ALL_ACCESS |
Аналогично FILE_MAP_WRITE |
FILE_MAP_COPY |
Доступ для копирования при записи. При создании отображения необходимо указать атрибут PAGE_WRITECOPY XE "PAGE_WRITECOPY" |
В случае успешного выполнения отображения функция MapViewOfFile возвращает адрес отображенной области памяти. При ошибке возвращается значение NULL.
При необходимости приложение может запросить отображение в заранее выделенную область адресного пространства. Для этого следует воспользоваться функцией MapViewOfFile XE "MapViewOfFile" Ex:
LPVOID MapViewOfFileEx( HANDLE hFileMappingObject, // идентификатор отображения DWORD dwDesiredAccess, // режим доступа DWORD dwFileOffsetHigh, // смещение в файле (старшее слово) DWORD dwFileOffsetLow, // смещение в файле (младшее слово) DWORD dwNumberOfBytesToMap, // количество отображаемых байт LPVOID lpBaseAddress); // предполагаемый адрес // для отображения файла
Эта функция аналогична только что рассмотренной функции MapViewOfFile за исключением того, что она имеет еще один параметр lpBaseAddress - предполагаемый адрес для выполнения отображения.
Выполнение отображения с использованием функции MapViewOfFileEx используется в тех случаях, когда с помощью файла, отображаемого на память, организуется общая область памяти, доступная нескольким работающим параллельно процессам. При этом вы можете сделать так, что начальный адрес этой области будет одним и тем же для любого процесса, работающего с данным отображением.
Заметим, что функция MapViewOfFileEx сама выполняет резервирование адресов, поэтому вы не должны передавать ей адрес области памяти, полученный от функции VirtualAlloc. Еще одно ограничение заключается в том, что адрес, указанный через параметр lpBaseAddress, должен находиться на границе гранулярности памяти.
Приложение может создавать несколько отображений для разных или одинаковых фрагментов одного и того же файла.
Если несколько процессов используют совместно одно и то же отображение, первый процесс создает это отображение с помощью функции CreateFileMapping, указав имя отображения, а остальные должны открыть его, вызвав функцию OpenFileMapping:
HANDLE OpenFileMapping( DWORD dwDesiredAccess, // режим доступа BOOL bInheritHandle, // флаг наследования LPCTSTR lpName); // адрес имени отображения файла
Через параметр lpName этой функции следует передать имя открываемого отображения. Имя должно быть задано точно также, как при создании отображения функцией CreateFileMapping.
Параметр dwDesiredAccess определяет требуемый режим доступа к отображению и указывается точно также, как и для описанной выше функции MapViewOfFile.
Параметр bInheritHandle определяет возможность наследования идентификатора отображения. Если он равен TRUE, порожденные процессы могут наследовать идентификатор, если FALSE - то нет.
Если созданное отображение больше не нужно, его следует отменить с помощью функции UnmapViewOfFile:
BOOL UnmapViewOfFile(LPVOID lpBaseAddress);
Через единственный параметр этой функции необходимо передать адрес области отображения, полученный от функций MapViewOfFile или MapViewOfFileEx.
В случае успеха функция возвращает значение TRUE. При этом гарантируется, что все измененные страницы оперативной памяти, расположенные в отменяемой области отображения, будут записаны на диск в отображаемый файл. При ошибке функция возвращает значение FALSE.
Если приложение создало несколько отображений для файла, перед завершением работы с файлом все они должны быть отменены с помощью функции UnmapViewOfFile. Далее с помощью функции CloseHandle следует закрыть идентификаторы отображения, полученный от функции CreateFile XE "CreateFile" Mapping XE "CreateFileMapping" и CreateFile.
Как мы только что сказали, после отмены отображения все измененные страницы памяти записываются в отображаемый файл. Если это потребуется, приложение может в любое время выполнить принудительную запись измененных страниц в файл при помощи функции FlushViewOfFile XE "FlushViewOfFile" :
BOOL FlushViewOfFile( LPCVOID lpBaseAddr, // начальный адрес сохраняемой области DWORD dwNumberOfBytesToFlush); // размер области в байтах
С помощью параметров lpBaseAddr и dwNumberOfBytesToFlush вы можете выбрать любой фрагмент внутри области отображения, для которого будет выполняться сохранение измененный страниц на диске. Если задать значение параметра dwNumberOfBytesToFlush равным нулю, будут сохранены все измененные страницы, принадлежащие области отображения.
Приложение Oem2Char, исходные тексты которого мы приведем в этом разделе, выполняет перекодировку текстовых файлов из формата OEM (используется программами MS-DOS) в формат ANSI (используется приложениями Microsoft Windows) и обратно.
Вы можете оттранслировать исходные тексты этого приложения таким образом, чтобы оно использовало для работы с файлами один из трех методов: синхронный или асинхронный ввод/вывод, а также отображение файлов на память.
Главное окно приложения Oem2Char, запущенного в среде Microsoft Windows 95, показано на рис. 1.2. Точно такой же вид это окно будет иметь при запуске этого приложения в среде Microsoft Windows NT версии 4.0.
Рис. 1.2. Главное окно приложения Oem2Char
Выбирая из меню File строку Convert вы можете указать режим перекодировки: из OEM в ANSI или обратно, из ANSI в OEM. Соответстсвующая диалоговая панель, предназначенная для указания режима перекодировки, называется Conversion Options и показана на рис. 1.3.
Рис. 1.3. Диалоговая панель Conversion Options панель, предназначенная для выбора режима перекодировки
По умолчанию приложение выполняет перекодировку текстовых файлов из формата OEM в формат ANSI.
Если исходные тексты приложения оттранслированы таким образом, что для работы с файлами используется синхронный либо асинхронный метод, вам предоставляется возможность выбрать исходный файл, который будет прекодироваться, и файл, в который будет записан результат перекодировки. При использовании отображения файла в память перекодировка выполняется “по месту”.
Выбрав из меню File строку Convert, вы увидите на экране диалоговую панель Select source file, показанную на рис. 1.4.
Рис. 1.4. Диалоговая панель Select source file, предназначенная для выбора исходного файла
С помощью этой диалоговой панели вы можете выбрать исходный файл для перекодировки. Напомним, что при работе с файлами в режиме отображения на память результат перекодировки будет записан в исходный файл.
Если же приложение работает с файлами в синхронном или асинхронном режиме, после выбора исходного файла вам будет представлена возможность выбрать файл для записи результата перекодировки (рис. 1.5). Различные режимы работы с файлами были описаны нами в предыдущем томе “Библиотеки системного программиста”.
Рис. 1.5. Диалоговая панель Select destination file, с помощью которой можно выбрать файл для записи результата перекодировки
Главный файл исходных текстов приложения Oem2Char приведены в листинге 1.1.
Листинг 1.1. Файл oem2char/oem2char.c
// ================================================== // Приложение OEM2CHAR // Демонстрация использования файловых операций // для перекодировки текстовых файлов // // (С) Фролов А.В., 1996 // Email: frolov@glas.apc.org // ================================================== #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" // Различные режимы работы с файлами #define SYNCHRONOUS_IO 1 // синхронные операции #define ASYNCHRONOUS_IO 2 // асинхроные операции #define MEMORYMAPPED_IO 3 // отображение на память // Для использования различных режимов работы // с файлами используйте только одну из // приведенных ниже трех строк //#define FILEOP SYNCHRONOUS_IO //#define FILEOP ASYNCHRONOUS_IO #define FILEOP MEMORYMAPPED_IO #include "oem2char.h" HINSTANCE hInst; char szAppName[] = "Oem2CharApp"; char szAppTitle[] = "Text File Converter"; // Тип преобразования: // OEM -> ANSI или ANSI -> OEM BOOL fConversionType = OEM_TO_ANSI; // Идентификатор файла, который будет // перекодирован в соответствии с содержимым // глобальной переменной fConversionType HANDLE hSrcFile; // Эти определения используются для всех способов // работы с файлами, кроме отображения на память #if FILEOP != MEMORYMAPPED_IO // Идентификатор файла, в который будет записан // результат перекодировки HANDLE hDstFile; // Буфер для работы с файлами CHAR cBuf[2048]; #endif // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; // Сохраняем идентификатор приложения hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если было, выдвигаем окно приложения на // передний план if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл // обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { // Выполняем преобразование файла case ID_FILE_CONVERT: { // Если не удалось открыть файлы, выводим // сообщение об ошибке if(!StartConversion(hWnd)) MessageBox(hWnd, "Conversion Error\n" "Unable to open file(s)", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } case ID_FILE_OPTIONS: { // Отображаем диалоговую панель, предназначенную // для настройки параметров преобразования DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); break; } case ID_FILE_EXIT: { // Завершаем работу приложения PostQuitMessage(0); return 0L; break; } case ID_HELP_ABOUT: { MessageBox(hWnd, "Text File Converter\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция DlgProc // ----------------------------------------------------- LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc_OnCommand); default: return FALSE; } } // ----------------------------------------------------- // Функция DlgProc_OnInitDialog // ----------------------------------------------------- BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // При инициализации диалоговой панели включаем // переключатель "OEM -> ANSI" CheckDlgButton(hdlg, IDC_OEMANSI, 1); return TRUE; } // ----------------------------------------------------- // Функция DlgProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { // Определяем и сохраняем состояние переключателей case IDOK: { if(IsDlgButtonChecked(hdlg, IDC_OEMANSI)) { // Включен режим преобразования из кодировки // OEM в кодировку ANSI fConversionType = OEM_TO_ANSI; } else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM)) { // Включен режим преобразования из кодировки // ANSI в кодировку OEM fConversionType = ANSI_TO_OEM; } EndDialog(hdlg, 0); return TRUE; } // Если пользователь нажимает кнопку Cancel, // завершаем работу диалоговой панели без // изменения режима перекодировки case IDCANCEL: { EndDialog(hdlg, 0); return TRUE; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция StartConversion // ----------------------------------------------------- BOOL StartConversion(HWND hwnd) { OPENFILENAME ofn; char szFile[256]; char szDirName[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; char szDlgTitle[] = "Select source file"; char szDlgTitleSave[] = "Select destination file"; // Подготавливаем структуру для выбора исходного файла memset(&ofn, 0, sizeof(OPENFILENAME)); GetCurrentDirectory(sizeof(szDirName), szDirName); szFile[0] = '\0'; ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szDirName; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrTitle = szDlgTitle; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выводим на экран диалоговую панель, предназначенную // для выбора исходного файла if(GetOpenFileName(&ofn)) { // Если файл выбран, открываем его if (*ofn.lpstrFile) { #if FILEOP == SYNCHRONOUS_IO // В синхронном режиме указываем флаг // FILE_FLAG_SEQUENTIAL_SCAN hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO // В асинхронном режиме необходимо указывать // флаг FILE_FLAG_OVERLAPPED hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); #elif FILEOP == MEMORYMAPPED_IO // В режиме отображения на память мы не используем // дополнительные флаги, однако указываем, что // файл будет открываться не только для чтения, // но и для записи hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif } else return FALSE; } else return FALSE; // Если исходный файл открыть не удалось, // возвращаем значение FALSE if(hSrcFile == INVALID_HANDLE_VALUE) return FALSE; #if FILEOP == MEMORYMAPPED_IO // В том случае, когда используется отображение // файла на память, этот файл перекодируется // "по месту". Для этого используется функция // Oem2Char с одним параметром. Oem2Char(hSrcFile); CloseHandle(hSrcFile); #elif FILEOP == SYNCHRONOUS_IO || FILEOP == ASYNCHRONOUS_IO // При использовании синхронного или асинхронного // режима работы с файлами результат перекодировки // будет записан в новый файл. Поэтому в этом // случае мы открываем второй файл и используем // другой вариант функции Oem2Char - с двумя // параметрами ofn.lpstrTitle = szDlgTitleSave; ofn.Flags = OFN_HIDEREADONLY; if(GetSaveFileName(&ofn)) { // Если файл выбран, открываем его if (*ofn.lpstrFile) { #if FILEOP == SYNCHRONOUS_IO // При использовании синхронных операций указываем // флаг FILE_FLAG_SEQUENTIAL_SCAN hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO // При использовании асинхронных операций // необходимо указать флаг FILE_FLAG_OVERLAPPED hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); #endif } else return FALSE; } else return FALSE; // Если выходной файл открыть не удалось, // возвращаем значение FALSE if(hDstFile == INVALID_HANDLE_VALUE) return FALSE; // Выполняем перекодировку файла hSrcFile с записью // результата перекодировки в файл hDstFile Oem2Char(hSrcFile, hDstFile); // После перекодировки закрываем оба файла CloseHandle(hSrcFile); CloseHandle(hDstFile); #endif // В случае успеха возвращаем значение TRUE return TRUE; } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Синхронный вариант функции // ----------------------------------------------------- #if FILEOP == SYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; // Выполняем перекодировку файла в цикле while(TRUE) { // Читаем блок данных из исходного файла // в буфер cBuf bResult = ReadFile(hSrcFile, cBuf, 2048, &dwBytesRead, NULL); // Проверяем, не был ли достигнут конец файла if(bResult && dwBytesRead == 0) break; // Выполняем преобразование в соответствии с // содержимым глобальной переменной fConversionType if(fConversionType == OEM_TO_ANSI) // Преобразование из OEM в ANSI OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) // Преобразование из ANSI в OEM CharToOemBuff(cBuf, cBuf, dwBytesRead); // Запись содержимого буфера в выходной файл WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, NULL); } } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Асинхронный вариант функции // ----------------------------------------------------- #elif FILEOP == ASYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; DWORD dwError; // Структуры для выполнения асинхронной работы OVERLAPPED ovRead; OVERLAPPED ovWrite; // Инициализация структуры для асинхронного чтения ovRead.Offset = 0; ovRead.OffsetHigh = 0; ovRead.hEvent = NULL; // Инициализация структуры для асинхронной записи ovWrite.Offset = 0; ovWrite.OffsetHigh = 0; ovWrite.hEvent = NULL; // Выполняем перекодировку файла в цикле while(TRUE) { // Запускаем операцию асинхронного чтения bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf), &dwBytesRead, &ovRead); // Проверяем результат запуска if(!bResult) { // Если произошла ошибка, анализируем ее код switch (dwError = GetLastError()) { // При достижении конца файла завершаем работу // цикла и возвращаемся из функции case ERROR_HANDLE_EOF: { return; } // Операция чтения запущена и еще выполняется case ERROR_IO_PENDING: { // Здесь вы можете разместить вызов функции, // которая будет выполняться параллельно с // только что запущенной операцией чтения // // IdleWork(); // Перед тем как перейти к перекодировке // считанного из файла блока, необходимо // дождаться завершения операции WaitForSingleObject(hSrcFile, INFINITE); // Получаем результат выполнения асинхронной // операции чтения bResult = GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE); if(!bResult) { switch (dwError = GetLastError()) { // При достижении конца файла завершаем работу // цикла и возвращаемся из функции case ERROR_HANDLE_EOF: { return; } default: break; } } } default: break; } } // Получаем результат выполнения асинхронной // операции чтения GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE); // Выполняем преобразование if(fConversionType == OEM_TO_ANSI) OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(cBuf, cBuf, dwBytesRead); // Продвигаем указатель позиции, с которой // начнется следующая операция асинхронного // чтения на количество считанных байт ovRead.Offset += dwBytesRead; // Запускаем асинхронную операцию записи bResult = WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, &ovWrite); // Если произошла ошибка, анализируем ее код if(!bResult) { switch (dwError = GetLastError()) { // Операция записи запущена и еще выполняется case ERROR_IO_PENDING: { // Здесь вы можете разместить вызов функции, // которая будет выполняться параллельно с // только что запущенной операцией чтения // // IdleWork(); // Получаем результат выполнения асинхронной // операции записи GetOverlappedResult(hDstFile, &ovWrite, &dwBytesWritten, TRUE); if(!bResult) { switch (dwError = GetLastError()) { default: break; } } } default: break; } } // Продвигаем указатель позиции, с которой // начнется следующая операция асинхронной // записи на количество записанных байт ovWrite.Offset += dwBytesWritten; } } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Вариант функции с использованием отображения // на память // ----------------------------------------------------- #elif FILEOP == MEMORYMAPPED_IO void Oem2Char(HANDLE hSrcFile) { DWORD dwFileSize; HANDLE hFileMapping; LPVOID lpFileMap; // Получаем и сохраняем размер файла dwFileSize = GetFileSize(hSrcFile, NULL); // Создаем объект-отображение для исходного файла hFileMapping = CreateFileMapping(hSrcFile, NULL, PAGE_READWRITE, 0, dwFileSize, NULL); // Если создать не удалось, возвращаем управление if(hFileMapping == NULL) return; // Выполняем отображение файла на память. // В переменную lpFileMap будет записан указатель на // отображаемую область памяти lpFileMap = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); // Если выполнить отображение не удалось, // возвращаем управление if(lpFileMap == 0) return; // Выполняем преобразование файла за один прием if(fConversionType == OEM_TO_ANSI) OemToCharBuff(lpFileMap, lpFileMap, dwFileSize); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(lpFileMap, lpFileMap, dwFileSize); // Отменяем отображение файла UnmapViewOfFile(lpFileMap); // Освобождаем идентификатор созданного // объекта-отображения CloseHandle(hFileMapping); } #endif
В листинге 1.2 приведен файл oem2char.h, в котором находятся определения и прототипы функций.
Листинг 1.2. Файл oem2char/oem2char.h
// Режимы перекодировки #define OEM_TO_ANSI 1 #define ANSI_TO_OEM 2 // ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); void WndProc_OnDestroy(HWND hWnd); LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify); BOOL StartConversion(HWND hwnd); // Выбираем разный прототип функции в зависимости // от выбранного режима работы с файлами #if FILEOP == MEMORYMAPPED_IO void Oem2Char(HANDLE hSrcFile); #elif FILEOP == SYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile); #elif FILEOP == ASYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile); #endif
Файл resource.h (листинг 1.3), который создается автоматически, содержит определения для файла описания ресурсов приложения.
Листинг 1.3. Файл oem2char/resource.h
//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by OEM2CHAR.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDD_DIALOG1 105 #define IDC_OEMANSI 1004 #define IDC_ANSIOEM 1005 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40002 #define ID_FILE_OPTIONS 40004 #define ID_FILE_CONVERT 40005 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 106 #define _APS_NEXT_COMMAND_VALUE 40006 #define _APS_NEXT_CONTROL_VALUE 1010 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
В листинге 1.4 вы найдете файл oem2char.rc. Это файл описания ресурсов приложения.
Листинг 1.4. Файл oem2char/oem2char.rc
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Convert...", ID_FILE_CONVERT MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "&Exit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About", ID_HELP_ABOUT END END ////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure // application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "OEM2CHAR.ICO" IDI_APPICONSM ICON DISCARDABLE "OEM2CHSM.ICO" ////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 169, 59 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Conversion Options" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,103,14,50,14 PUSHBUTTON "Cancel",IDCANCEL,103,31,50,14 GROUPBOX "Conversion type",IDC_STATIC,7,7,78,42 CONTROL "OEM -> ANSI", IDC_OEMANSI,"Button",BS_AUTORADIOBUTTON | WS_GROUP,15,18,61,10 CONTROL "ANSI -> OEM", IDC_ANSIOEM,"Button", BS_AUTORADIOBUTTON,15, 32,61,10 END ////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 162 TOPMARGIN, 7 BOTTOMMARGIN, 52 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Так как объем нашей книги ограничен, мы решили не приводить по одному примеру приложений на каждый метод работы с файлами (синхронный, асинхронный, отображение файла на память), а совместить все в одном приложении.
Для этого мы сделали три определения, показанных ниже:
#define SYNCHRONOUS_IO 1 // синхронные операции #define ASYNCHRONOUS_IO 2 // асинхроные операции #define MEMORYMAPPED_IO 3 // отображение на память
Каждая из этих строк соответствует одному из режимов работы с файлами.
Для того чтобы исходные тексты были оттранслированы для использования нужного вам режима работы с файлами, необходимо определить значение макропеременной FILEOP. Это можно сделать одним из трех способов:
//#define FILEOP SYNCHRONOUS_IO //#define FILEOP ASYNCHRONOUS_IO #define FILEOP MEMORYMAPPED_IO
Перед трансляцией исходных текстов приложения вы должны выбрать одну из этих строк, закрыв две другие символами комментария. При этом с помощью операторов условной трансляции, расположенных в исходных текстах приложения, будет выбран нужный вариант исходного текста.
Строка включения файла определений oem2char.h должна расплагаться после строки определения макропеременной FILEOP, так как в этом файле тоже имеются операторы условной трансляции.
В глобальной переменной fConversionType хранится текущий режим преобразования, который можно изменить при помощи диалоговой панели Conversion Options, показанной на рис. 1.3. Сразу после запуска приложения в этой переменной хранится значение OEM_TO_ANSI, в результате чего приложение будет выполнять перекодировку из OEM в ANSI. Функция диалога диалоговой панели Conversion Options может записать в переменную fConversionType значение ANSI_TO_OEM. В результате приложение будет перекодировать текстовые файлы из ANSI в OEM.
Далее в области глобальных переменных определены переменные для хранения идентификаторов файлов hSrcFile, hDstFile, а также буфер cBuf размером 2048 байт:
HANDLE hSrcFile; #if FILEOP != MEMORYMAPPED_IO HANDLE hDstFile; CHAR cBuf[2048]; #endif
В переменной hSrcFile хранится идентификатор исходного файла. Что же касается переменной hDstFile, то она предназначена для хранения идентификатора файла, в который будет записан результат перекодировки. Эта переменная, а также буфер для временного хранения перекодируемых данных cBuf не нужны при работе с использованием отображения файла в память. Поэтому если значение макропеременной FILEOP не равно константе MEMORYMAPPED_IO, строки определения переменной hDstFile и буфера cBuf пропускаются при трансляции исходных текстов приложения.
Приведем описание функций, определенных в нашем приложении.
Функция WinMain не имеет никаких особенностей. Сразу после запуска приложения она сохраняет идентификатор приложения в глобальной переменной hInst и проверяет, нет ли в памяти копии приложения, запущенной раньше. Для такой проверки используется методика, основанная на вызове функции FindWindow XE "FindWindow" и описанная в предыдущем томе “Библиотеки системного программиста”, посвященного программированию для операционной системы Microsoft Windows NT. Если будет найдена копия работающего приложения, его окно выдвигается на передний план при помощи функций ShowWindow и SetForegroundWindow.
Далее функция WinMain регистрирует класс главного окна прилжения, создает и отображает это окно, а затем запускает цикл обработки сообщений.
В задачу функции WndProc входит обработка сообщений, поступающих в главное окно приложения. Если от главного меню приложения приходит сообщение WM_COMMAND, вызывается функция WndProc_OnCommand. При уничтожении главного окна приложения в его функцию поступает сообщение WM_DESTROY, для обработки которого вызывается функция WndProc_OnDestroy.
Все остальные сообщения передаются функции DefWindowProc.
Эта функция вызывается при уничтожении главного окна приложения для обработки сообщения WM_DESTROY. Функция WndProc_OnDestroy вызывает функцию PostQuitMessage, в результате чего цикл обработки сообщений завершает свою работу.
Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Для обработки мы использовали макрокоманду HANDLE_MSG, описанную в предыдущем томе “Библиотеки системного программиста”.
Если пользователь выберет из меню File строку Convert, будет выполняться преобразование файла. Для этого функция WndProc_OnCommand вызовет функцию StartConversion, передав ей в качестве единственного параметра идентификатор главного окна приложения.
При выборе из меню File строки Options приложение выведет на экран диалоговую панель Conversion Options, вызвав для этого функцию DialogBox:
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);
Диалоговая панель имеет идентификатор IDD_DIALOG1 и определена в файле описания ресурсов приложения.
Функция диалога DlgProc обрабатывает сообщения WM_INITDIALOG и WM_COMMAND, поступающие от диалоговой панели Conversion Options. Для обработки этих сообщений вызываются, соответственно, функции DlgProc_OnInitDialog и DlgProc_OnCommand.
Сообщение WM_INITDIALOG поступает в функцию диалога при инициализации диалоговой панели. Функция DlgProc_OnInitDialog, обрабатывающая это сообщение, выполняет единственную задачу: она включает переключатель OEM -> ANSI, устанавливая таким образом режим перекодировки из OEM в ANSI:
CheckDlgButton(hdlg, IDC_OEMANSI, 1);
Когда в диалоговой панели Conversion Options пользователь нажимает одну из кнопок или клавиши <Esc> и <Enter>, в функцию диалога поступает сообщение WM_COMMAND. Обработчик этого сообщения, расположенный в функции DlgProc_OnCommand, определяет текущее состояние переключателей режима перекодировки, расположенных на посверхности диалоговой панели, и записывает соответствующее значение в глобальную переменную fConversionType:
if(IsDlgButtonChecked(hdlg, IDC_OEMANSI)) { fConversionType = OEM_TO_ANSI; } else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM)) { fConversionType = ANSI_TO_OEM; }
Если при работе с диалоговой панелью пользователь нажимает кнопку Cancel или клавишу <Esc>, содержимое глобальной переменной fConversionType не изменяется.
В задачу функции StartConversion входит выбор и открывание исходного файла и файла, в который будет записан результат перекодировки. Когда приложение работает с файлом в режиме отображения на память, открывается только один файл - исходный.
Для выбора файла мы использовали функцию GetOpenFileName, хорошо знакомую вам по предыдущим томам “Библиотеки системного программиста”, посвященным программированию для операционной системы Microsoft Windows версии 3.1.
Выбранные файлы открываются при помощи функции CreateFile XE "CreateFile". Однако способ открывания зависит от режима работы с файлами. Ниже мы привели фрагмент исходного текста приложения, в котором открывается исходный файл:
#if FILEOP == SYNCHRONOUS_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); #elif FILEOP == MEMORYMAPPED_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif
В синхронном режиме исходный файл будет читаться последовательно, поэтому мы указали режим GENERIC_READ (только чтение) и флаг FILE_FLAG_SEQUENTIAL_SCAN. Так как в процессе перекодировки исходный файл не будет изменяться, нет причин запрещать чтение этого файла для других процессов. Чтобы предоставить доступ другим процессам на чтение исходного файла, мы указали режим совместного использования файла FILE_SHARE_READ XE "FILE_SHARE_READ".
В асинхронном режиме необходимо указывать флаг FILE_FLAG_OVERLAPPED, что мы и сделали в нашем примере.
Что же касается режима отображения файла на память, то сдесь при открывании файла мы указали режимы GENERIC_READ и GENERIC_WRITE. В результате файл открывается и для чтения, и для записи.
После того как в режиме отображения файла на память исходный файл будет открыт, функция StartConversion вызывает функцию Oem2Char, передавая ей в качестве единственного параметра идентификатор исходного файла:
Oem2Char(hSrcFile); CloseHandle(hSrcFile);
Функция Oem2Char выполняет перекодировку файла “по месту”. Далее идентификатор исходного файла закрывается функцией CloseHandle, после чего функция StartConversion возвращает управление.
В синхронном и асинхронном режиме функция StartConversion после открывания исходного файла дополнительно открывает выходной файл, в который будет записан результат перекодировки. Для выбора выходного файла вызывается функция GetSaveFileName.
Так же как и исходный файл, выходной файл открывается при помощи функции CreateFile, причем в синхронном и асинхронном режиме этот файл открывается по-разному:
#if FILEOP == SYNCHRONOUS_IO hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); #endif
В синхронном режиме мы указываем режим доступа на запись GENERIC_WRITE и флаг FILE_FLAG_SEQUENTIAL_SCAN (так как запись в выходной файл будет выполняться последовательно от начала до конца).
В асинхронном режиме необходимо указать флаг FILE_FLAG_OVERLAPPED.
Кроме того, в обоих случаях мы указали режим открывания файла CREATE_ALWAYS. В результате выходной файл будет создан заново даже в том случае, если в выбранном каталоге уже есть файл с таким именем. При этом содержимое старого файла будет уничтожено.
После открывания исходного и выходного файла вызывается функция Oem2Char, выполняющая перекодировку, а затем оба файла закрываются при помощи функции CloseHandle:
Oem2Char(hSrcFile, hDstFile); CloseHandle(hSrcFile); CloseHandle(hDstFile);
Обратите внимание, что в синхронном и асинхронном режиме работы с файлами в нашем приложении используется другой вариант фукнции Oem2Char - вариант с двумя параметрами. Как вы сейчас увидите, в нашем приложении используются три варианта этой функции.
Если приложение подготовлено таким образом, что оно работает с файлами при помощи синхронных операций, используется следующий вариант исходного текста функции Oem2Char:
void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; while(TRUE) { bResult = ReadFile(hSrcFile, cBuf, 2048, &dwBytesRead, NULL); if(bResult && dwBytesRead == 0) break; if(fConversionType == OEM_TO_ANSI) OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(cBuf, cBuf, dwBytesRead); WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, NULL); } }
Здесь мы работаем с файлом обычным образом.
Перекодировка файла выполняется в цикле, который прерывается при достижении конца исходного файла.
Функция Oem2Char при помощи функции ReadFile читает фрагмент исходного файла в буфер cBuf, расположенный в области глобальных переменных. Количество прочитанных байт записывается при этом в локальную переменную dwBytesRead.
При достижении конца исходного файла количество байт, прочитанных функцией ReadFile из файла, а также значение, возвращенное этой функцией, равно нулю. Этот факт мы используем для завершения цикла перекодировки.
После прочтения блока данных из исходного файла функция Oem2Char анализирует содержимое глобальной переменной fConversionType, определяя тип преобразования, который нужно выполнить. В зависимости от содержимого этой переменной вызывается либо функция OemToCharBuff, выполняющая преобразование из кодировки OEM в кодировку ANSI, либо функция CharToOemBuff, которая выполняет обратное преобразование.
На следующем этапе преобразованное содержимое буфера cBuf записывается в выходной файл при помощи функции WriteFile. При этом количество действительно записанных байт сохраняется в локальной переменной dwBytesWritten, однако в нашем приложении оно никак не используется.
Сделаем важное замечание относительно функций OemToCharBuff и CharToOemBuff.
Для преобразования текстовых строк из кодировки OEM в кодировку ANSI и обратно в программном интерфейсе операционной системы Microsoft Windows версии 3.1 имелся набор функций, описанный нами в 12 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть вторая”. Это такие функции как OemToAnsi XE "OemToAnsi", AnsiToOem XE "AnsiToOem", OemToAnsiBuff XE "OemToAnsiBuff", AnsiToOemBuff XE "AnsiToOemBuff".
В программном интерфейсе операционной системы Microsoft Windows NT эти функции оставлены для совместимости, однако ими не рекомендуется пользоваться. Причина заключается в том, что в Microsoft Windows NT можно работать с символами в кодировке Unicode, когда для представления каждого символа используется не один, а два байта.
Соответственно, вместо перечисленных выше функций необходимо использовать функции OemToChar, CharToOem, OemToCharBuff, CharToOemBuff, которые имеют такие же параметры, что и их 16-разрядные прототипы (за исключением того, что функциям OemToCharBuff и CharToOemBuff можно передавать 32-разрядную длину преобразуемой текстовой строки). В зависимости от того, используется ли приложением кодировка Unicode, эти функции могут преобразовывать строки из OEM в строки ANSI или Unicode (и обратно).
Вариант функции Oem2Char, предназначенный для использования асинхронных операций с файлами, выглядит сложнее, однако он позволяет приложению выполнять дополнительную работу в то время когда происходит чтение или запись блока данных.
В асинхронном режиме для чтения и записи необходимо подготовить структуры типа OVERLAPPED. Мы делаем это следующим образом:
OVERLAPPED ovRead; OVERLAPPED ovWrite; ovRead.Offset = 0; ovRead.OffsetHigh = 0; ovRead.hEvent = NULL; ovWrite.Offset = 0; ovWrite.OffsetHigh = 0; ovWrite.hEvent = NULL;
Структура ovRead используется для выполнения операций чтения. В поля OffsetHigh и Offset этой структуры мы записываем нулевые значения, поэтому чтение будет выполняться с самого начала файла.
Что же касается поля hEvent, то в него мы записываем значение NULL. При этом мы не будем создавать для синхронизации отдельный объект-событие, а воспользуемся идентификатором файла.
Структура ovWrite, которая предназначена для выполнения операций записи, используется аналогичным образом.
После подготовки структур ovRead и ovWrite функция Oem2Char начинает цикл перекодировки.
Прежде всего, в этом цикле запускается операция асинхронного чтения, для чего вызывается функция ReadFile:
bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf), &dwBytesRead, &ovRead);
В качестве последнего параметра мы передаем этой функции адрес заранее подготовленной структуры ovRead.
Заметим, что в данном случае функция ReadFile вернет управление еще до завершения операции чтения, поэтому в переменную dwBytesRead не будет записано количествао прочитанных байт (так как пока неизвестно, сколько их удастся прочитать).
Далее функция Oem2Char проверяет код возврата фукнции ReadFile. При этом дополнительно вызывается функция GetLastError.
Если при запуске процедуры чтения был достигнут конец файла, эта функция вернет значение ERROR_HANDLE_EOF. В этом случае функция Oem2Char просто возвращает управление.
Если же функция GetLastErrorвернула значение ERROR_IO_PENDING, то это означает, что в настоящий момент происходит выполнение операции чтения. Приложение может вызвать функцию, например, IdleWork, для выполнения какой-либо фоновой работы.
Перед тем как продолжить свою работу, функция дожидается завершение операции чтения, вызывая для этого функцию WaitForSingleObject, описанную в предыдущем томе “Библиотеки системного программиста”:
WaitForSingleObject(hSrcFile, INFINITE);
При этом главная задача приложения перейдет в состояние ожидания до тех пор, пока не закончится операция чтения. Напомним, что в состоянии ожидания задача не отнимает циклы процессорного времени и не снижает производительность системы.
Далее функция проверяет результат выполнения асинхронной операции чтения, вызывая функцию GetOverlappedResult:
bResult = GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE);
Помимо всего прочего, эта функция записывает в локальную переменную dwBytesRead количество байт, прочитанных из исходного файла.
После дополнительных проверок ошибок функция Oem2Char выполняет преобразование содержимого буфера, заполненного прочитанными данными.
Вслед за этим в структуре ovRead изменяется содержимое поля Offset:
ovRead.Offset += dwBytesRead;
Значение, которое находится в этом поле, увеличивается на количество прочитанных байт. В результате при очередном вызове функции ReadFile будет запущено чтение для следующего блока данных. Так как мы не изменяем поле OffsetHigh, наше приложение способно работать с файлами, имеющими размер не более 4 Гбайт (что, однако, вполне достаточно).
Асинхронная операция записи прочитанных данных запускается при помощи функции WriteFile:
bResult = WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, &ovWrite);
В качестве последнего параметра этой функции передается адрес заранее подготовленной структуры ovWrite.
После анализа кода возврата функции WriteFile вызывается функция GetOverlappedResult, с помощью которой определяется результат завершения операции записи:
GetOverlappedResult(hDstFile, &ovWrite, &dwBytesWritten, TRUE);
Так как через последний параметр этой функции передается значение TRUE, функция GetOverlappedResult выполняет ожидание завершения операции записи. Кроме того, в локальную переменную dwBytesWritten эта функция заносит количество байт данных, записанных в выходной файл.
Так как асинхронные операци чтения и записи не изменяют текущую позицию в файле, после выполнения записи мы изменяем соответствующим образом содержимое поля Offset в структуре ovWrite:
ovWrite.Offset += dwBytesWritten;
Далее цикл перекодировки продолжает свою работу.
Третий вариант функции Oem2Char, который используется для работы через отображение файла на память, выглядит очень просто. В нем даже нет цикла, а перекодировка выполняется за один прием:
void Oem2Char(HANDLE hSrcFile) { DWORD dwFileSize; HANDLE hFileMapping; LPVOID lpFileMap; dwFileSize = GetFileSize(hSrcFile, NULL); hFileMapping = CreateFileMapping(hSrcFile, NULL, PAGE_READWRITE, 0, dwFileSize, NULL); if(hFileMapping == NULL) return; lpFileMap = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); if(lpFileMap == 0) return; if(fConversionType == OEM_TO_ANSI) OemToCharBuff(lpFileMap, lpFileMap, dwFileSize); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(lpFileMap, lpFileMap, dwFileSize); UnmapViewOfFile(lpFileMap); CloseHandle(hFileMapping); }
Вначале функция Oem2Char определяет размер исходного файла, идентификатор которого передается ей через параметр hSrcFile. Для определения размера файла используется функция GetFileSize XE "GetFileSize".
На следующем шаге с помощью функции CreateFileMapping создается объект-отображение. Страницы этого объекта будут доступны и на чтение, и на запись, так как указан режим PAGE_READWRITE XE "PAGE_READWRITE".
Для того чтобы получить доступ к памяти, отображенной на файл, наше приложение вызывает функцию MapViewOfFile. Отображение выполняется для чтения и записи, поэтому мы указали флаг FILE_MAP_WRITE XE "FILE_MAP_WRITE". В случае успешного завершения функции адрес отображенной области записывается в локальную переменную lpFileMap.
Что же касается перекодировки файла, то она выполняется исключительно просто: мы передаем через первые два параметра функции перекодировки адрес, на который отображен файл, а через третий параметр - размер файла.
После того как перекодировка будет выполнена, необходимо вначале отменить отображение, а затем освободить идентификатор объекта-отображения. Первая задача решается в нашем приложении спомощью функции UnmapViewOfFile XE "UnmapViewOfFile", а вторая - с помощью функции CloseHandle XE "CloseHandle", которой в качестве единственного параметра передается идентификатор объекта-отображения.
Если ваше приложение просто открывает файлы или создает новые, возможно, для выбора файлов вам будет достаточно использовать стандартные диалоговые панели, которые мы создавали в предыдущем приложении. Однако во многих случаях вам необходимо предоставить пользователю детальную информацию о дисковых устройствах, такую, например, как тип файловой системы, общий объем диска, размер свободного пространства на диске, а также тип диска (локальный, сетевой, со сменным или несменным носителем данных, устройство чтения CD-ROM XE "CD-ROM" и так далее).
В этой главе мы приведем исходные тексты приложения DiskInfo, которое получает и отображает подробную информацию о всех дисковых устройствах, имеющихся в системе, как локальных, так и удаленных (сетевых). Информация отображается в табличном виде с помощью органа управления List View, который мы подробно описали в 22 томе “Библиотеки системного программиста”, посвященному операционной системе Microsoft Windows 95.
Внешний вид главного окна приложения DiskInfo, запущенного в одном из режимов отображения, показан на рис. 1.6.
Рис. 1.6. Просмотр информации о дисках в табличном виде
В столбце Drive отображаются пиктограммы и названия дисковых устройств, имеющихся в системе. Для каждого типа устройства используется своя пиктограмма.
В столбце Volume name для каждого устройства располагается метка тома (если она есть). В столбце File system мы отображаем имя файловой системы.
Так как максимальная длина имени файлов и каталогов разная для различных файловых систем, то в столбце File name length мы отображаем этй длину для каждого дискового устройства.
В столбцах Total Space и Free Space выводится, соответственно, емкость диска в байтах и размер свободного пространства на диске (также в байтах).
Заметим, что при первом запуске приложение DiskInfo не пытается определить параметры для устройств со сменными носителями данных (устройства НГМД, устройства чтения CD-ROM XE "CD-ROM", магнитооптические накопители и так далее). Это связано с тем, что при попытке определить параметры устройства операционная система Microsoft Windows NT выполняет обращение к соответствующему накопителю. В том случае, если в накопителе нет носителя данных, на экране появится предупреждающее сообщение.
Тем не менее, наше приложение может определить параметры устройств со сменными носителями данных. Для этого вы должны вставить носитель (дискету, компакт-диск и так далее) в устройство, а затем сделать двойной щелчок левой клавишей мыши по пиктограмме устройства. После этого соответствующая строка таблицы будет заполнена, как это показано на рис. 1.7.
Рис. 1.7. Полностью заполненная таблица параметров дисковых устройств
Кроме того, после двойного щелчка по пиктограмме любого дискового устройства на экране появляется диалоговая панель Logical Drive Information, в которой отображается имя устройства, имя файловой системы, серийный номер, а также системные флаги (рис. 1.8 - 1.11). Системные флаги мы описали в последней главе предыдущего тома “Библиотеки системного программиста”.
Рис. 1.8. Просмотр дополнительной информации о диске FAT
Рис. 1.9. Просмотр дополнительной информации о диске NTFS
Рис. 1.10. Просмотр дополнительной информации о диске HPFS
Рис. 1.11. Просмотр дополнительной информации о диске CDFS
С помощью меню Options, показанного на рис. 1.12, вы можете изменить режим отображения списка дисковых устройств.
Рис. 1.12. Меню Options, предназначенное для изменения режима отображения списка дисковых устройств
Если выбрать из меню Options строку Icon view, внешний вид главного окна приложения изменится. Этот список будет отображаться в виде набора пиктограмм стандартного размера с подписью (рис. 1.13).
Рис. 1.13. Просмотр дисковых устройств в виде пиктограмм стандартного размера
Если же из этого меню выбрать строку Small icon view, для отображения списка устройств будут использованы маленькие пиктограммы (рис. 1.14).
Рис. 1.14. Просмотр дисковых устройств в виде пиктограмм маленького размера
Есть и еще один вариант, показанный на рис. 1.15. Он используется при выборе из меню Options строки List View.
Рис. 1.15. Просмотр дисковых устройств в виде списка
Если же из меню Options выбрать строку Report view, список дисковых устройств будет отображаться в виде таблицы, как это было показано на рис. 1.6 и 1.7.
Приложение DiskInfo может работать и в среде операционной системы Microsoft Windows 95 (рис. 1.16).
Рис. 1.16. Просмотр информации о дисковых устройствах в среде операционной системы Microsoft Windows 95
Компьютер, на котором была запущена программа в этом случае, был оборудован 3,5 дюймовым НГМД (устройство A:), 5,25 дюймовым НГМД (устройство B:), обычным жестким диском (устройство C:), магнитооптическим накопителем MaxOptix с емкостью 1,3 Гбайт (устройство D:), и устройством чтения CD-ROM XE "CD-ROM" (устройство E:).
Кроме того, этот компьютер был подключен к сети, в которой есть серверы на базе операционных систем Microsoft Windows NT Server и Novell NetWare. Устройства F: и G: отображаются на сетевые тома сервера Microsoft Windows NT Server, а устройства S:, T: и U: - на сетевые тома сервера Novell NetWare.
Заметим, что в отличие от сервера Microsoft Windows NT Server, сервер Novell NetWare не был настроен таким образом, чтобы можно было работать с длинными именами файлов и каталогов.
Главный файл исходных текстов приложения DiskInfo приведен в листинге 1.5.
Заметим, что так как в приложении используется орган управления List View, в файле проекта вы должны подключить библиотеку comctl32.lib. В противном случае редактор связей выдаст сообщения об ошибках.
Листинг 1.5. Файл DiskInfo/DiskInfo.c
// ================================================== // Приложение DiskInfo // Получение и отображение информации о дисковых // устройствах, имеющихся в системе // // (С) Фролов А.В., 1996 // Email: frolov@glas.apc.org // ================================================== #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <stdio.h> #include "resource.h" #include "afxres.h" #include "diskinfo.h" // Структура для хранения информации о // логическом диске typedef struct tagDISKINFO { char szDriveName[10]; // имя диска UINT nDriveType; // тип диска char szVolumeName[30]; // имя тома DWORD dwVolumeSerialNumber; // серийный номер DWORD dwMaxFileNameLength; // длина имени DWORD dwFileSystemFlags; // системные флаги char szFileSystemName[10]; // имя файловой системы int iImage; // номер пиктограммы DWORD dwFreeSpace; // свободное пространство DWORD dwTotalSpace; // общий объем диска } DISKINFO; // ----------------------------------------------------- // Глобальные переменные // ----------------------------------------------------- HINSTANCE hInst; char szAppName[] = "DriveInfoApp"; char szAppTitle[] = "Logical Drive Information"; HWND hwndList; // Указатель на область памяти, в которую будет // записан массив строк имен дисков LPSTR lpLogicalDriveStrings; // Указатель на массив структур типа DISKINFO, // в котором будет хранится информация о дисках DISKINFO *pdi; // Количество логических дисков в системе int nNumDirves; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_NOTIFY, WndProc_OnNotify); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { int i; RECT rc; HIMAGELIST himlSmall; HIMAGELIST himlLarge; HICON hIcon; LV_COLUMN lvc; LV_ITEM lvi; // Получаем информацию о логических // дисковых устройствах GetDiskInfo(); // Определяем размеры внутренней области главного окна GetClientRect(hWnd, &rc); // Инициализируем библиотеку стандартных // органов управления InitCommonControls(); // Создаем орган управления List View hwndList = CreateWindowEx(0L, WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_LISTVIEW, hInst, NULL); if(hwndList == NULL) return FALSE; // Создаем списки изображений himlSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_MASK, 8, 1); himlLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_MASK, 8, 1); // Добавляем в списки пиктограммы // изображений дисковых устройств // Изображения с номером 0 hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DREMOVE)); ImageList_AddIcon(himlLarge, hIcon); hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DREMOVSM)); ImageList_AddIcon(himlSmall, hIcon); // Изображения с номером 1 hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DFIXED)); ImageList_AddIcon(himlLarge, hIcon); hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DFIXEDSM)); ImageList_AddIcon(himlSmall, hIcon); // Изображения с номером 2 hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DRCD)); ImageList_AddIcon(himlLarge, hIcon); hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DRCDSM)); ImageList_AddIcon(himlSmall, hIcon); // Изображения с номером 3 hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DNET)); ImageList_AddIcon(himlLarge, hIcon); hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_DNETSM)); ImageList_AddIcon(himlSmall, hIcon); // Добавляем списки изображений ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL); ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL); // Вставляем столбцы memset(&lvc, 0, sizeof(lvc)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = (rc.right - rc.left) / 10; lvc.iSubItem = 0; lvc.pszText = "Drive"; ListView_InsertColumn(hwndList, 0, &lvc); lvc.iSubItem = 1; lvc.pszText = "Volume name"; ListView_InsertColumn(hwndList, 1, &lvc); lvc.iSubItem = 2; lvc.pszText = "File system"; ListView_InsertColumn(hwndList, 2, &lvc); lvc.fmt = LVCFMT_RIGHT; lvc.iSubItem = 3; lvc.pszText = "File name length"; ListView_InsertColumn(hwndList, 3, &lvc); lvc.iSubItem = 4; lvc.pszText = "Total Space"; ListView_InsertColumn(hwndList, 4, &lvc); lvc.iSubItem = 5; lvc.pszText = "Free Space"; ListView_InsertColumn(hwndList, 5, &lvc); // Вставляем строки memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM; lvi.pszText = LPSTR_TEXTCALLBACK; // Цикл по всем имеющимся логическим устройствам for(i=0; i<nNumDirves; i++) { lvi.iItem = i; lvi.iSubItem = 0; lvi.cchTextMax = 40; lvi.lParam = (LPARAM)(pdi + i)->szDriveName; lvi.iImage = (pdi + i)->iImage; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 1; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 2; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 3; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 4; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 5; ListView_InsertItem(hwndList, &lvi); } return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { DestroyWindow(hwndList); // Освобождаем память free(lpLogicalDriveStrings); free(pdi); PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { DWORD dwStyle = 0; switch (id) { case ID_OPTIONS_ICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_ICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_ICON); break; } case ID_OPTIONS_SMALLICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_SMALLICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_SMALLICON); break; } case ID_OPTIONS_LISTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_LIST) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_LIST); break; } case ID_OPTIONS_REPORTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_REPORT) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_REPORT); break; } case ID_FILE_EXIT: PostQuitMessage(0); return 0L; break; case ID_HELP_ABOUT: MessageBox(hWnd, "Disk Information Browser\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); break; default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция WndProc_OnNotify // ----------------------------------------------------- LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr) { LV_DISPINFO * lpLvdi = (LV_DISPINFO *)pnmhdr; DISKINFO * lpDiskInfo = (DISKINFO *)(lpLvdi->item.lParam); static char szBuf[20]; DWORD dwSectors, dwClusters, dwFreeClusters, dwBytes; NM_LISTVIEW *lpNm = (NM_LISTVIEW *)pnmhdr; if(idFrom != IDC_LISTVIEW) return 0L; switch(pnmhdr->code) { case LVN_GETDISPINFO: { if(lpLvdi->item.mask & LVIF_TEXT) { switch(lpLvdi->item.iSubItem) { case 0: lpLvdi->item.pszText = lpDiskInfo->szDriveName; break; case 1: lpLvdi->item.pszText = lpDiskInfo->szVolumeName; break; case 2: lpLvdi->item.pszText = lpDiskInfo->szFileSystemName; break; case 3: ltoa(lpDiskInfo->dwMaxFileNameLength, szBuf, 10); lpLvdi->item.pszText = szBuf; break; case 4: ltoa(lpDiskInfo->dwTotalSpace, szBuf, 10); lpLvdi->item.pszText = szBuf; break; case 5: ltoa(lpDiskInfo->dwFreeSpace, szBuf, 10); lpLvdi->item.pszText = szBuf; break; default: break; } break; } } case NM_DBLCLK: { int index; char szBuf[256]; // Определяем номер выбранного элемента списка index = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); if(index == -1) return 0; // Получаем информацию о выбранном устройстве GetVolumeInformation((pdi + index)->szDriveName, (pdi + index)->szVolumeName, 30, &((pdi + index)->dwVolumeSerialNumber), &((pdi + index)->dwMaxFileNameLength), &((pdi + index)->dwFileSystemFlags), (pdi + index)->szFileSystemName, 10); // Определяем объем свободного пространства // на диске и общую емкость диска GetDiskFreeSpace((pdi + index)->szDriveName, &dwSectors, &dwBytes, &dwFreeClusters, &dwClusters); (pdi + index)->dwFreeSpace = dwSectors * dwBytes * dwFreeClusters; (pdi + index)->dwTotalSpace = dwSectors * dwBytes * dwClusters; // Подготавливаем для отображения имя диска, // имя файловой системы, серийный номер и // список системных флагов sprintf(szBuf, "System flags for drive %s (%s)\n" "Serial number: %lX\n", (pdi + index)->szDriveName, (pdi + index)->szFileSystemName, (pdi + index)->dwVolumeSerialNumber); if((pdi + index)->dwFileSystemFlags & FS_CASE_IS_PRESERVED) strcat(szBuf, "\nFS_CASE_IS_PRESERVED"); if((pdi + index)->dwFileSystemFlags & FS_CASE_SENSITIVE) strcat(szBuf, "\nFS_CASE_SENSITIVE"); if((pdi + index)->dwFileSystemFlags & FS_UNICODE_STORED_ON_DISK) strcat(szBuf, "\nFS_UNICODE_STORED_ON_DISK"); if((pdi + index)->dwFileSystemFlags & FS_PERSISTENT_ACLS) strcat(szBuf, "\nFS_PERSISTENT_ACLS"); if((pdi + index)->dwFileSystemFlags & FS_FILE_COMPRESSION) strcat(szBuf, "\nFS_FILE_COMPRESSION"); if((pdi + index)->dwFileSystemFlags & FS_VOL_IS_COMPRESSED) strcat(szBuf, "\nFS_VOL_IS_COMPRESSED"); // Перерисовываем главное окно приложения для // отражения изменений в окне списка InvalidateRect(hWnd, NULL, TRUE); MessageBox(hWnd, szBuf, szAppTitle, MB_OK); return 0L; break; } } return 0L; } // ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy) { MoveWindow(hwndList, 0, 0, cx, cy, TRUE); return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); } // ----------------------------------------------------- // Функция GetDiskInfo // ----------------------------------------------------- void GetDiskInfo(void) { DWORD dwDriveStringsSpace; LPSTR lpTemp; int i; DWORD dwSectors, dwClusters, dwFreeClusters, dwBytes; // Определяем размер блока памяти, необходимый для // записи имен всех логических дисков dwDriveStringsSpace = GetLogicalDriveStrings(0, NULL); // Получаем память lpLogicalDriveStrings = malloc(dwDriveStringsSpace); // Заполняем полученный блок памяти именами дисков GetLogicalDriveStrings(dwDriveStringsSpace, lpLogicalDriveStrings); // Подсчитываем количество дисков, сканируя список // имен, полученный на предыдущем шаге nNumDirves = 0; for(lpTemp = lpLogicalDriveStrings; *lpTemp != 0; nNumDirves++) { lpTemp = strchr(lpTemp, 0) + 1; } // Заказываем память для хранения информации // о всех дисках pdi = malloc(nNumDirves * sizeof(DISKINFO)); // Заполняем массив структур DISKINFO информацией о дисках for(i = 0, lpTemp = lpLogicalDriveStrings; i < nNumDirves; i ++) { // Получаем имя очередного диска strcpy((pdi + i)->szDriveName, lpTemp); // Определяем тип диска (pdi + i)->nDriveType = GetDriveType(lpTemp); // В зависимости от типа диска выбираем способ // заполнения соответствующей структуры DISKINFO switch ((pdi + i)->nDriveType) { // Для сменных устройств и для CD-ROM // записываем пустые значения case DRIVE_REMOVABLE: { // Выбираем пиктограмму с номером 0 (pdi + i)->iImage = 0; strcpy((pdi + i)->szVolumeName, "<Unknown>"); (pdi + i)->dwVolumeSerialNumber = 0; (pdi + i)->dwMaxFileNameLength = 0; (pdi + i)->dwFileSystemFlags = 0; strcpy((pdi + i)->szFileSystemName, "?"); (pdi + i)->dwFreeSpace = 0; (pdi + i)->dwTotalSpace = 0; break; } case DRIVE_CDROM: { (pdi + i)->iImage = 2; strcpy((pdi + i)->szVolumeName, "<Unknown>"); (pdi + i)->dwVolumeSerialNumber = 0; (pdi + i)->dwMaxFileNameLength = 0; (pdi + i)->dwFileSystemFlags = 0; strcpy((pdi + i)->szFileSystemName, "?"); (pdi + i)->dwFreeSpace = 0; (pdi + i)->dwTotalSpace = 0; break; } // Получаем информацию для несменных устройств case DRIVE_FIXED: { (pdi + i)->iImage = 1; GetVolumeInformation(lpTemp, (pdi + i)->szVolumeName, 30, &((pdi + i)->dwVolumeSerialNumber), &((pdi + i)->dwMaxFileNameLength), &((pdi + i)->dwFileSystemFlags), (pdi + i)->szFileSystemName, 10); GetDiskFreeSpace(lpTemp, &dwSectors, &dwBytes, &dwFreeClusters, &dwClusters); (pdi + i)->dwFreeSpace = dwSectors * dwBytes * dwFreeClusters; (pdi + i)->dwTotalSpace = dwSectors * dwBytes * dwClusters; break; } // Получаем информацию для сетевых томов case DRIVE_REMOTE: { (pdi + i)->iImage = 3; GetVolumeInformation(lpTemp, (pdi + i)->szVolumeName, 30, &((pdi + i)->dwVolumeSerialNumber), &((pdi + i)->dwMaxFileNameLength), &((pdi + i)->dwFileSystemFlags), (pdi + i)->szFileSystemName, 10); GetDiskFreeSpace(lpTemp, &dwSectors, &dwBytes, &dwFreeClusters, &dwClusters); (pdi + i)->dwFreeSpace = dwSectors * dwBytes * dwFreeClusters; (pdi + i)->dwTotalSpace = dwSectors * dwBytes * dwClusters; break; } // Прочие дисковые устройства default: { (pdi + i)->iImage = 1; GetVolumeInformation(lpTemp, (pdi + i)->szVolumeName, 30, &((pdi + i)->dwVolumeSerialNumber), &((pdi + i)->dwMaxFileNameLength), &((pdi + i)->dwFileSystemFlags), (pdi + i)->szFileSystemName, 10); GetDiskFreeSpace(lpTemp, &dwSectors, &dwBytes, &dwFreeClusters, &dwClusters); (pdi + i)->dwFreeSpace = dwSectors * dwBytes * dwFreeClusters; (pdi + i)->dwTotalSpace = dwSectors * dwBytes * dwClusters; break; } } // Переходим к следующей строке в списке // имен дисков lpTemp = strchr(lpTemp, 0) + 1; } }
Файл diskinfo.h (листинг 1.6) содержит прототипы функций, определенных в приложении, а также определение идентификатора органа управления List View с именем IDC_LISTVIEW.
Листинг 1.6. Файл DiskInfo/diskinfo.h
#define IDC_LISTVIEW 1234 // ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR* pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem); void GetDiskInfo(void);
Файл resource.h (листинг 1.7) создается автоматически и содержит определения констант для файла описания ресурсов приложения, который будет приведен ниже.
Листинг 1.7. Файл DiskInfo/resource.h
//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by DiskInfo.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_DREMOVE 115 #define IDI_DREMOVSM 116 #define IDI_DFIXED 117 #define IDI_DFIXEDSM 118 #define IDI_DRCD 119 #define IDI_DRCDSM 120 #define IDI_DNET 121 #define IDI_DNETSM 122 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_OPTIONS_ICONVIEW 40004 #define ID_OPTIONS_SMALLICONVIEW 40005 #define ID_OPTIONS_LISTVIEW 40006 #define ID_OPTIONS_REPORTVIEW 40007 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 123 #define _APS_NEXT_COMMAND_VALUE 40008 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл описания ресурсов приложения diskinfo.rc представлен в листинге 1.8.
Листинг 1.8. Файл DiskInfo/diskinfo.rc
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ////////////////////////////////////////////////////////////// // // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Options" BEGIN MENUITEM "&Icon view", ID_OPTIONS_ICONVIEW MENUITEM "&Small icon view", ID_OPTIONS_SMALLICONVIEW MENUITEM "&List view", ID_OPTIONS_LISTVIEW MENUITEM "&Report view", ID_OPTIONS_REPORTVIEW END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure // application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "diskinfo.ico" IDI_APPICONSM ICON DISCARDABLE "dinfosm.ico" IDI_DREMOVE ICON DISCARDABLE "DREMOV.ICO" IDI_DREMOVSM ICON DISCARDABLE "DREMOVSM.ICO" IDI_DFIXED ICON DISCARDABLE "DFIXED.ICO" IDI_DFIXEDSM ICON DISCARDABLE "DFIXEDSM.ICO" IDI_DRCD ICON DISCARDABLE "DRCD.ICO" IDI_DRCDSM ICON DISCARDABLE "DRCDSM.ICO" IDI_DNET ICON DISCARDABLE "DNET.ICO" IDI_DNETSM ICON DISCARDABLE "DNETSM.ICO" ////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" ID_OPTIONS_ICONVIEW "Each item appears as a full-sized icon" ID_OPTIONS_SMALLICONVIEW "Each item appears as a small icon" ID_OPTIONS_LISTVIEW "Each item appears as a small icon arranged in columns" ID_OPTIONS_REPORTVIEW "Each item appears with subitems arranged in columns" END #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
В начале своей работы приложение DiskInfo получает список имен дисков в виде текстовых строк. Каждая такая строка закрыта двоичным нулем, а последняя - двумя нулевыми байтами. Адрес списка приложение записывает в глобальную переменную lpLogicalDriveStrings.
После получения списка имен приложение сканирует его с целью подсчета количества дисковых устройств. Это количество сохраняется в глобальной переменной nNumDirves.
Далее приложение заказывает память для массива структур типа DISKINFO:
typedef struct tagDISKINFO { char szDriveName[10]; // имя диска UINT nDriveType; // тип диска char szVolumeName[30]; // имя тома DWORD dwVolumeSerialNumber; // серийный номер DWORD dwMaxFileNameLength; // длина имени DWORD dwFileSystemFlags; // системные флаги char szFileSystemName[10]; // имя файловой системы int iImage; // номер пиктограммы DWORD dwFreeSpace; // свободное пространство DWORD dwTotalSpace; // общий объем диска } DISKINFO;В каждой структуре этого массива будет храниться информация о соответствующем логическом диске, такая, например, как имя и тип диска, имя тома и так далее. В процессе работы приложение будет отображать и обновлять информацию, записанную в массиве.
Адрес массива структур DISKINFO хранится в глобальной переменной pdi.
В этом разделе мы кратко расскажем о функциях, определенных в приложении DiskInfo.
Функция WinMain проверяет существование запущенной ранее копии приложения. Если такая копия будет обнаружена, окно работающей копии активизируется и выдвигается на передний план. Если нет - функция WinMain выполняет обычную инициализацию приложения, создавая его главное окно и запуская цикл обработки сообщений.
В задачу функции WndProc входит обработка сообщений WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY и WM_SIZE. Для обработки этих сообщений вызываются, соответственно, функции WndProc_OnCreate, WndProc_OnDestroy, WndProc_OnCommand, WndProc_OnNotify и WndProc_OnSize.
Все остальные сообщения передаются функции DefWindowProc, выполняющей обработку по умолчанию.
Эта функция обрабатывает сообщение WM_CREATE, поступающее в функцию окна при его создании.
Вначале функция WndProc_OnCreate вызывает функцию GetDiskInfo, которая получает информацию о логических дисках, имеющихс в системе, и сохраняет ее в массиве структур DISKINFO XE "DISKINFO".
Далее обработчик сообщения WM_CREATE определяет размеры внутренней области главного окна приложения, инициализирует библиотеку стандартных органов управления и создает орган управления List View на базе предопределенного класса окна WC_LISTVIEW, вызывая для этого функцию CreateWindowEx.
На следующем этапе приложение создает два списка изображений. Первый из них (с идентификатором himlSmall) будет содержать пиктограммы дисковых устройств маленького размера, а второй (с идентификатором himlLarge) - эти же пиктограммы, но стандартного размера.
С помощью макрокоманды ImageList_AddIcon в эти списки добавляются пиктограммы с изображениями дисков. Каждое такое изображение хранится в списке под собственным номером. Например, под номером 0 хранятся пиктограммы с идентификатором IDI_DREMOVE и IDI_DREMOVSM (сетевые устройства), под номером 1 - пиктограммы с идентификатором IDI_DFIXED и IDI_DFIXEDSM (диск с несменным носителем данных) и так далее. Номера пиктограмм используются при формировании строк списка, отображаемого при помощи органа управления List View.
После добавления всех пиктограмм сформированные списки подключаются к органу управления List View с помощью макрокоманды ListView_SetImageList.
Далее обработчик сообщения WM_CREATE вставляет столбцы, задавая для них различное выравнивание текста. Текст в столбцах Drive, Volume name и File system выравнивается по левой границе, а текст в столбцах File name length, Total Space и Free Space - по правой.
Вставка строк выполняется в цикле с помощью макрокоманды ListView_InsertItem.
Более подробную информацию о работе с органом управления List View вы можете найти в 22 томе “Библиотеки системного программиста”, посвященному программированию для операционной системы Microsoft Windows 95.
При уничтожении главного окна приложения обработчик сообщения WM_DESTROY удаляет орган управления List View и освобождает память, заказанную у операционной системы для списка строк с именами дисков и массива структур DISKINFO XE "DISKINFO".
Далее обработчик вызывает функцию PostQuitMessage, инициируя завершения цикла обработки сообщений.
Эта функция обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения.
Выбирая строки меню Options, вы можете изменить внешний вид окна органа управления List View, выбрав один из четырех режимов отображения. Соответствующие процедуры мы описали в 22 томе “Библиотеки системного программиста”.
Функция WndProc_OnNotify обрабатывает извещения, поступающие от органа управления List View в главное окно приложения.
Обработчик извещения LVN_GETDISPINFO выбирает информацию из элементов массива структур DISKINFO, и предоставляет ее для отображения в окне органа управления List View.
Для нас сейчас больший интерес представляет обработчик извещения NM_DBLCLK, который получает управление, когда пользователь делает двойной щелчок левой клавишей мыши по пиктограмме дискового устройства в окне органа управления List View.
Вначале с помощью макрокоманды ListView_GetNextItem обработчик извещения NM_DBLCLK определяет номер выбранного элемента и записывает его в локальную переменную index.
Далее вызывается функция GetVolumeInformation, с помощью которой выполняется заполнение соответствующего элемента массива структур DISKINFO. Значение, записанное в переменную index служит при этом индексом в массиве структур.
Заполнение структуры завершается функцией GetDiskFreeSpace, с помощью которой определяется такая информация, как общий объем диска в байтах и объем свободного пространства на диске. Эта функция сохраняет в локальных переменных dwClusters и dwFreeClusters, соответственно, общее количество кластеров, имеющихся на диске, и количество свободных кластеров.
В локальные переменные dwSectors и dwBytes записывается количество секторов в одном кластере и размер сектора в байтах. Эти значения используются для вычисления общего и свободного объема диска исходя из общего количества кластеров и количества свободных кластеров.
После обновления элемента массива структур DISKINFO, соответтсвующего выбранному диску, обработчик извещения NM_DBLCLK отображает на экране диалоговую панель с такой информацией, как название диска, имя файловой системы, серийный номер диска и системные флаги.
Перед выводом указанной диалоговой панели мы перерисовываем содержимое окна органа управления List View, для чего вызываем функцию InvalidateRect.
Зачем мы это делаем?
При запуске приложения мы получаем список логических дисковых устройств и определяем их параметры, однако только для устройств с несменными носителями данных. В противном случае операционная система попытается выполнить обращение к устройству (например, к НГМД) и, если вы заранее не вставите в него носитель данных, на экране появится сообщение об ошибке.
Если же вы вначале вставите носитель в устройство, а затем в окне нашего приложения DiskInfo сделаете двойной щелчок левой клавишей мыши по пиктограмме устройства, обработчик извещения NM_DBLCLK получит параметры этого устройства и сохранит их в соответствующем элементе массива структур DISKINFO. Однако само по себе это не вызовет никаких изменений в списке параметров устройств, который отображается нашим приложением.
Вызывая функцию InvalidateRect, мы выполняем перерисовку главного окна приложения и его дочернего окна - окна органа управления List View. При этом обработчик извещения LVN_GETDISPINFO получает и отображает в окне обновленную информацию о параметрах устройства.
В задачу функции WndProc_OnSize, обрабатывающей сообщение WM_SIZE, входит изменение размеров органа управления List View при изменении размеров главного окна приложения.
Функция GetDiskInfo вызывается обработчиком сообщения WM_CREATE при создании главного окна приложения. Она получает и сохраняет информацию о всех логических дисках, имеющихся в системе.
Из предыдущего тома “Библиотеки системного программиста” вы знаете, что с помощью функции GetLogicalDriveStrings можно получить список имен всех логических дисковых устройств. Через второй параметр этой функции необходимо передать адрес блока памяти, в который будет записан указанный выше список, а через первый - размер этого блока.
Однако заранее приложение не знает, сколько логических дисков имеется в системе и, соответственно, не знает, сколько места потребуется ему для сохранения списка логических устройств. Поэтому вначале мы вызываем функцию GetLogicalDriveStrings, передав ей значение 0 в качестве первого параметра и значение NULL - в качестве второго:
dwDriveStringsSpace = GetLogicalDriveStrings(0, NULL);
В результате функция GetLogicalDriveStrings возвращает размер блока памяти, необходимый для записи всех имен логических дисков.
На следующем шаге наше приложение получает блок памяти необходимого размера, вызывая для этого функцию malloc. Адрес полученного блока и его размер затем передаются функции GetLogicalDriveStrings, которая в этом случае заполняет блок необходимой информацией:
GetLogicalDriveStrings(dwDriveStringsSpace, lpLogicalDriveStrings);
Если в компьютере имеются, например, логические диски A:, C: и D:, в блок памяти с адресом lpLogicalDriveStrings будут записаны следующие три строки:
A:\<0> C:\<0> D:\<0><0>
В конце каждой строки записывается байт с нулевым значением, а в конце последней строки - два нулевых байта.
Получив список имен устройств, наше приложение подсчитывает количество устройств. Для этого оно с помощью функции strchr сканирует список в цикле до тех пор, пока не будет найден его конец, подсчитывая количество проходов в глобальной переменной nNumDirves:
nNumDirves = 0; for(lpTemp = lpLogicalDriveStrings; *lpTemp != 0; nNumDirves++) { lpTemp = strchr(lpTemp, 0) + 1; }
Определив таким образом общее количество логических дисков, приложение заказывает память для массива структур типа DISKINFO, в котором будут хранится параметры логических дисков:
pdi = malloc(nNumDirves * sizeof(DISKINFO));
Заполнение массива структур DISKINFO выполняется в цикле.
Для каждого диска прежде всего выполняется копирование имени диска из соответствующей строки списка, полученного ранее при помощи функции GetLogicalDriveStrings.
Далее приложение определяет тип диска, вызывая для этого функцию GetDriveType (в локальной переменной lpTemp хранится имя диска):
(pdi + i)->nDriveType = GetDriveType(lpTemp);
Заполнение полей структуры DISKINFO выполняется по-разному в зависимости от типа диска.
Если устройство со сменным носителем данных, то в поле iImage, предназначенное для хранения номера пиктограммы диска, записывается нулевое значение. Именно под этим номером мы занесли пиктограмму диска со сменным носителем в список пиктограмм для органа управления List View.
В поле szVolumeName мы записываем строку <Unknown>, так как определение фактических параметров устройств со сменным носителем выполняется при обработке извещения NM_DBLCLK. Аналогичным образом заполняются и остальные поля структуры.
Заполнение структуры DISKINFO для устройств чтения CD-ROM выполняется точно так же, как и устройств со сменным носителем данных, за исключением того что в поле номера пиктограммы iImage записывается значение 2. Это номер пиктограммы с изображением накопителя CD-ROM в списке пиктограмм органа управления List View.
Если текущим устройством, для которого мы определяем параметры, является диск с несменным носителем данных, функция GetDiskInfo получает большинство этих параметров при помощи функции GetVolumeInformation, как это показано ниже:
GetVolumeInformation(lpTemp, (pdi + i)->szVolumeName, 30, &((pdi + i)->dwVolumeSerialNumber), &((pdi + i)->dwMaxFileNameLength), &((pdi + i)->dwFileSystemFlags), (pdi + i)->szFileSystemName, 10);
Для определения общего объема диска и объема свободного пространства дополнительно вызывается функция GetDiskFreeSpace:
GetDiskFreeSpace(lpTemp, &dwSectors, &dwBytes, &dwFreeClusters, &dwClusters);
Значения, полученные от этой функции, обрабатываются следующим образом:
(pdi + i)->dwFreeSpace = dwSectors * dwBytes*dwFreeClusters; (pdi + i)->dwTotalSpace = dwSectors * dwBytes*dwClusters;
Объем свободного пространства в байтах записывается в поле dwFreeSpace. Он вычисляется как произведение сделующих величин: количества свободных кластеров на диске dwFreeClusters, количество секторов в одном кластере dwSectors и количества байт в одном сеткоре dwBytes.
Общий объем диска записывается в поле dwTotalSpace и подсчитывается аналогично, за исключением того что вместо количества свобдных кластеров используется общее количество кластеров на диске dwClusters.
В поле iImage записывается значение 1. Это номер пиктограммы с изображением диска с несменным носителем данных.
Получение и заполнение информации об удаленных (сетевых) дисковых устройствах выполняется аналогично, однако в поле iImage записывается значение 3.
Если тип устройства не поддается классификации, наше приложение получает информацию о его параметрах, а в поле iImage записывает значение 1.