Файлова система

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Файлова система
Для роботи з файлами в Windows міститься свій набір функцій, які дозволяють виконувати основні операції над файлами. У перших 16ти розрядних версіях Windows (за 3.x включно) ці функції грунтувалися на застосуванні функцій MS-DOS. Безпосередньо в Windows описаний тільки мінімальний набір базових функцій, для виконання інших операцій завжди можна скористатися перериванням 0x21. На відміну від них, 32х розрядні версії Windows містять вже повний набір функцій для роботи з файлами, при цьому використання функцій MS-DOS не допускається. Функції Win32 API для роботи з файлами - це розширений набір функцій у порівнянні з Windows API.
Робота з файлами, каталогами і томами в Windows API
У Windows 3.x застосовується файлова система MS-DOS. У перших версіях Windows взагалі вся робота з дисками просто переадресовувалися до функцій MS-DOS, в пізніх версіях, наприклад Windows for Workgroup 3.11, в окремих випадках може використовувати власні засоби, що реалізують доступ до диска і до файлів в 32х розрядному захищеному режимі. Це трохи збільшує продуктивність системи, що працює в захищеному режимі, тому що при зверненнями до даних на диску виключаються перемикання процесора із захищеного режиму до реального і назад. При цьому система перевіряє, чи можливо за допомогою наявних драйверів звертатися до даного диску, чи ні. Якщо це неможливо (наприклад, використовуються нестандартні DOS-драйвери), то для доступу до диска і файлів застосовуються засоби MS-DOS, як у попередніх версіях Windows.
Файли (file) групуються в каталоги (directory або folder, папки). В одному каталозі може знаходитися велика кількість файлів або інших вкладених каталогів. Каталоги утворюють строго деревоподібну структуру - даний каталог може містити скільки завгодно вкладених, але сам він може бути вкладений тільки в один батьківський каталог (каталог верхнього рівня). На кожному томі (volume) існує один каталог самого верхнього рівня - кореневий каталог (root directory). Для позначення конкретного томи використовуються букви англійського алфавіту, починаючи з "a".
Імена файлів (file name) і каталогів (directory name, folder name) в Windows API, як і в MS-DOS, складаються з імені, довжиною до 8 символів і розширення, довжиною до 3 символів, який відокремлюється від імені однієї точкою. Таким чином максимальна довжина імені складає 12 символів, включаючи розділяє точку. Для точного завдання файлу необхідно вказати не тільки його ім'я, але також і каталог, в якому він знаходиться. Причому, так як каталоги можуть бути вкладені, то необхідно задавати список каталогів, які треба пройти на шляху від поточного каталогу, або від кореневого каталогу томи до каталогу, що містить потрібний файл.
Ім'я томи, шлях до файлу та ім'я файлу складають так зване повне ім'я файлу (path name). Повне ім'я починається з назви томи, за яким слідує двокрапка, зворотна коса риска та список каталогів на шляху до файлу, поділюваних зворотними косими рисами, потім слідує ім'я файлу та його розширення, відокремлюване точкою. Наприклад: E: \ Mka \ Book \ Samples \ 1a.cpp
Вказує на файл 1a.cpp, що знаходиться на каталозі Samples, вкладеному в підкаталог Book і, разом з ним, в підкаталог Mka, що зберігається на томі E.
Для кожного тому існує поняття поточного каталогу (current directory). Крім того існує поняття поточного томи (диска) (current drive). Для завдання файлу, що знаходиться в поточному каталозі поточного томи, досить просто вказати ім'я цього файлу. Ви можете легко зробити поточний диск іншим і перейти в ньому в будь-якій іншій каталог. У цьому випадку система запам'ятає той каталог, в якому ви були перед переходом на інший диск, і буде вважати його поточним каталогом для колишнього диска.
Так, наприклад:
1a.cpp - задає файл 1a.cpp в поточному каталозі поточного диска
E: 1a.cpp - задає файл 1a.cpp в поточному каталозі томи E
E: \ 1a.cpp - задає файл 1a.cpp в кореневому каталозі томи E
\ 1a.cpp - задає файл 1a.cpp в кореневому каталозі поточного диска
Система допускає використання спеціальних імен "." (Точка) і ".." (дві точки) для завдання поточного каталогу й для завдання каталогу верхнього рівня. Наприклад, нехай поточний каталог E: \ Mka \ Book \ Samples, тоді:
. \ 1a.doc - задає файл E: \ Mka \ Book \ Samples \ 1a.cpp
.. \ Chapter2.doc - задає файл E: \ Mka \ Book \ Chapter2.doc
У системі забороняється використовувати імена файлів і каталогів, що збігаються з іменами стандартних пристроїв, наприклад CON, COM2, LPT1, PRN і так далі.
Система обмежує максимальну довжину повного імені файлу 144 символами. Оскільки заздалегідь відомо, що повне ім'я не перевищить цієї довжини, то часто для роботи з іменами файлів використовуються статично виділяються області, розмір яких не змінюється.
Власне в Windows 3.x міститься невеликий набір функцій, які повторюють основні операції над файлами - створення, відкриття, позиціонування файлового покажчика, читання, запис і закриття; крім цього передбачено кілька додаткових функцій. Функцій для розділення доступу, перейменування або копіювання файлів, створення і зміни каталогів і іншого не передбачається. При необхідності можна скористатися перериванням 0x21, якими функціями стандартної C-бібліотеки.
HFILE _lcreat (lpszFilename, fnAttribute);
HFILE _lopen (lpszFilename, fnOpenMode);
LONG _llseek (hf, lOffset, nOrigin);
UINT _lread (hf, hpvBuffer, cbBuffer);
UINT _lwrite (hf, hpvBuffer, cbBuffer);
long _hread (hf, hpvBuffer, cbBuffer);
long _hwrite (hf, hpvBuffer, cbBuffer);
HFILE _lclose (hf);
Призначення цих функцій зрозуміло з назви. Серед них цікаві тільки дві процедури - _hread і _hwrite - які призначені для читання і запису великих фрагментів файлів за одну операцію. Під більшими в даному випадку розуміються фрагменти, що перевищують 65535 байт (64K), тобто перевищують розмір одного 16ти розрядного сегмента. Для 16-ти розрядної платформи Windows 3.x це дуже зручно.
Незручним у функціях Windows API для роботи з файлами є те, що для прапорів цих функцій передбачені тільки числові значення, а не символічні. Так, наприклад, для функції _lcreat параметр fnAttribute рівний 0, відповідає створенню звичайного файлу, а 2 - прихованого. Або для функції _llseek параметр nOrigin рівний 1 вказує на завдання нового положення щодо поточного, а не відносно початку файлу. Всі ці константи так і треба вказувати у вигляді числа, попередньо перевіривши їх значення по керівництву.
Використовувані в Windows хендла файлів є хендла файлів MS-DOS. Це дозволяє легко застосовувати звичайні засоби для роботи з файлами. З іншого боку це призводить до типової помилку: у Windows прийнято, що значення 0 для хендла є неприпустимим; тобто звичайна перевірка при отримання хендла будь-якого об'єкту виглядає так:
HANDLE hObject;
hObject = Get ...(...); / / отримуємо хендл об'єкта
if (hObject) {/ / перевіряємо його ...
/ / Все в порядку
} Else {
/ / Помилка!}
Однак при роботі з файлами такий спосіб помилковий - хендл файлу, рівний 0, в MS-DOS відповідає стандартному пристрою для виведення (stdout), а не неприпустимого. Для позначення помилки застосовується значення хендла файлу не 0, а -1. Для зручності в windows.h визначений спеціальний символ HFILE_ERROR, рівний -1. Попередній фрагмент при роботі з файлами повинен виглядати так:
HFILE hFile;
hFile = _lcreat ("c: \ \ myfile.dat", 0);
if (hFile! = HFILE_ERROR) {
/ / Все в порядку
...
_lclose (hFile);
} Else {
/ / Помилка!}
Крім уже розглянутих в Windows передбачена спеціальна функція, призначена для відкриття, пошуку, видалення файлів і виконання деяких інших операцій:
HFILE OpenFile (lpszFileName, lpOpenBuff, fuMode);
Параметр lpOpenBuf є покажчиком на структуру OFSTRUCT. У цій структурі зберігається інформація про відкритий файлі, що дозволяє використовувати її для повторного відкриття (чи видалення) того-ж файлу пізніше. Параметр fuMode зазвичай вказує режим доступу до відкритого файлу (прапори OF_READ, OF_WRITE, OF_READWRITE), виконувану операцію - відкрити або створити файл (OF_CREATE), обмеження доступу до файлу інших застосувань (OF_SHARE_. ..).
Крім відкриття файлу дана функція здатна виконувати деякі специфічні операції (задаються тим-же параметром fuMode):
видалення файлу OF_DELETE,
пошук файлу OF_SEARCH,
перевірка існування файлу OF_EXIST,
повторне відкриття файлу за інформацією, що зберігається у структурі OFSTRUCT OF_REOPEN,
ініціалізацію структури OFSTRUCT OF_PARSE,
порівняння дати і часу створення файла з даними в структурі OFSTRUCT OF_VERIFY,
а також повідомляти про неможливість відкрити файл (при цьому функція OF_PROMPT і не дозволяє ні вибрати інший файл, ні вказати нове ім'я файлу) OF_CANCEL
Ще кілька функцій Windows носять допоміжний характер. Так, наприклад, функції GetWindowsDirectory і GetSystemDirectory повертають інформацію про каталог, що містить win.com - каталозі Windows і системному каталозі (зазвичай windows \ system).
UINT GetWindowsDirectory (lpszSysPath, cbSysPath)
UINT GetSystemDirectory (lpszSysPath, cbSysPath);
Цікаво, що Windows не надає функції для отримання поточного каталогу - для цього треба використовувати або функції стандартної бібліотеки, або можна дізнатися повний шлях до виконуваного файлу, а з нього виділити шлях (в деяких випадках навіть важливіше знати саме каталог, в якому знаходиться виконуваний модуль , ніж поточний):
Int GetModuleFileName (hInstance, lpszFileName, cbFileName);
Ще пара функцій допомагає створювати тимчасові файли. Функція GetTempDrive повертає букву, що позначає диск, який використовується для зберігання тимчасових файлів (при цьому параметр функції chDriveLetter не використовується). При цьому передбачається, що для зберігання тимчасових файлів використовується перший жорсткий диск (зазвичай C), а якщо він не існує, то поточний диск. Друга функція - GetTempFileName допомагає побудувати унікальне ім'я тимчасового файлу, при цьому вона використовує або змінну оточення TEMP, або, якщо вона не визначена, то кореневий каталог зазначеного томи, або, якщо він не заданий, кореневий каталог першого жорсткого диска (зазвичай C).
BYTE GetTempDrive (chDriveLetter);
Int GetTempFileName (chDriveLetter, lpszPrefixString, uUnique, lpszTempFileName);
Однак ці функції потрібні тільки для формування унікального імені для тимчасового файлу. Власне тимчасовим цей файл не буде - для його видалення після закриття треба використовувати відповідну функцію, система за вас цього не зробить.
Функція SetHandleCount дозволяє змінити максимально допустиму кількість одночасно відкритих файлів одним додатком. За замовчуванням один додаток може відкрити до 20 файлів відразу.
UINT SetHandleCount (cHandles);
Остання функція повертає інформацію про тип дискового пристрою. На жаль ця інформація вкрай мізерна - Windows розпізнає тільки три види дискових пристроїв: фіксовані (DRIVE_FIXED), змінні (DRIVE_REMOVABLE) і віддалені (DRIVE_REMOTE):
UINT GetDriveType (nDriveNumber);
Параметр nDriveNumber задає номер диска: пристрій "A" має номер 0, "C" - 2 і так далі. Повертається функцією значення тип диска (DRIVE_FIXED, DRIVE_REMOVABLE, DRIVE_REMOTE), або, у разі помилки, 0.

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

Робота з каталогами
Аналогічно звичайному Windows API в Win32 API використовується поняття поточного каталогу, в якому повинні розміщуватися файли, якщо тільки в їх імені не міститься шлях до цих файлів. Іноді можливе завдання відносного шляху (докладніше див REF _Ref430419794 \ * MERGEFORMAT Робота з файлами, каталогами і томами в Windows API, стор _Ref430419854 1). Ви можете як дізнатися поточний каталог за допомогою функції GetCurrentDirectory, так і зробити поточним будь-якої іншої каталог, скориставшись функцією SetCurrentDirectory.
DWORD GetCurrentDirectory (cchDir, lpszDir);
BOOL SetCurrentDirectory (lpszDir);
Параметри функцій: lpszDir - буфер, який містить або одержує ім'я поточного каталогу, cchDir - максимальне число символів в імені поточного каталогу, які можуть вміститися у відведеному буфері. Не забувайте, що довжина імені (це повний шлях до поточного каталогу) може бути дуже великою, тому зручно спочатку дізнатися необхідний розмір буфера, виділити його і потім отримати ім'я:
DWORD nSize;
char * pszCurDir;
dwSize = GetCurrentDirectory (0, NULL); / / повертається потрібна довжина,
pszCurDir = new char [dwSize]; / / включаючи завершальний '\ 0'
GetCurrentDirectory (dwSize, pszCurDir);
При необхідності ви можете створювати або знищувати каталоги з допомогою функцій CreateDirectory і RemoveDirectory. Як і у випадку функцій бібліотеки, функція RemoveDirectory може видалити тільки порожній каталог, в іншому випадку функція повідомить про помилку.
BOOL CreateDirectory (lpszDir, lpsaSA);
BOOL RemoveDirectory (lpszDir);
Параметр lpSA є покажчиком на структуру SECURITY_ATTRIBUTES, за допомогою якого ви можете керувати доступом до файлів.
DWORD GetTempPath (cchBuffer, lpszTempPath);
Ця функція доповнює колишні GetTempDrive і GetTempFileName. Вона повертає шлях до каталогу, призначеному для зберігання тимчасових файлів.
Кілька інших функцій призначені для пошуку файлу або для отримання списку файлів в каталозі. Функція SearchPath шукає певний файл і повертає повний шлях до файлу, якщо тільки цей файл знайдений.
DWORD SearchPath (lpszPath, lpszFile, lpszExt, cchBuffer, lpszBuffer, plpszFileNamePart);
Параметр lpszPath вказує на каталог, в якому треба шукати файл. Якщо цей параметр дорівнює NULL, то Windows буде шукати файл в тому каталозі, звідки додаток запущено, в поточному каталозі, в системних каталогах Windows, і в самому каталозі Windows, а також у всіх каталогах, перерахованих на шляху пошуку, заданому параметром оточення PATH. Параметр lpszFile задає ім'я файлу для пошуку, lpszExt - розширення імені шуканого файлу (використовується, якщо зазначене ім'я не містить розширення). Параметри cchBuffer, lpszBuffer задають характеристики (розмір і адресу) буфера, що приймає ім'я файлу, а покажчик на покажчик plpszFileNamePart визначить позицію, починаючи з якої в буфері знаходиться ім'я файлу (тобто перший символ після останнього '\' в дорозі).
Три функції FindFirstFile, FindNextFile і FindClose приблизно відповідають функцій _dos_findfirst, _dos_findnext і _dos_findclose бібліотеки. Функція FindFirstFile вимагає завдання шаблону імен файлів і покажчика на спеціальну структуру WIN32_FIND_DATA, яка буде заповнюватися інформацією про поточний файл, відповідному шаблоном. Функція створює спеціальний об'єкт, який використовується в подальших викликах функції FindNextFile для продовження перебору файлів в каталозі. Після завершення перебору створений об'єкт необхідно видалити за допомогою функції FindClose.
HANDLE FindFirstFile (lpszFileName, lpFindFileData);
BOOL FindNextFile (hFindFile, lpFindFileData);
BOOL FindClose (hFindFile);
struct WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwSizeHigh;
DWORD dwSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName [MAX_PATH];
TCHAR cAlternateFileName [14];};
Приклад:
WIN32_FIND_DATA fd;
HANDLE hFD;
hFD ​​= FindFirstFile ("c: \ \ *.*", & fd);
if (hFD) {
do {
/ / Аналіз структури fd та виконання необхідних дій над файлами.
} While (FindNextFile (hFD, & fd));
FindClose (hFD);}
Останні розглядаються нами функції дозволяють отримувати інформацію про зміну каталогу файлів. Система створює спеціальний об'єкт, який функція FindFirstChangeNotification спочатку встановлює у зайняте стан, і який автоматично переводиться у вільний стан, як тільки в каталозі (або в підкаталогах) відбувається зазначену зміну. Очікуване зміна задається параметром fdwFilter при виклику функції FindFirstChangeNotification.
HANDLE FindFirstChangeNotification (lpszPath, fWatchSubTree, fdwFilter);
HANDLE FindNextChangeNotification (hChange);
BOOL FindCloseChangeNotifictaion (hChange); [4]
Функції FindFirstChangeNotification і FindNextChangeNotification повертають управління відразу після установки створеного об'єкта у зайняте стан. Ви можете використовувати функції WaitForSingleObject або WaitForMultipleObjects для очікування змін в каталозі.
Функція FindCloseChangeNotifictaion видаляє створений об'єкт.
Увага! Ці функції не реалізовані для Windows 3.x з Win32s. Проте, крім цього обмеження, існує ще кілька особливостей у застосуванні цих функцій. Так, при роботі в Windows-95 перейменування одного файлу в каталозі може призводити до двократного повідомленням про зміну вмісту каталогу. Ще серйозніше обмеження на використання віддалених каталогів (каталогів інших комп'ютерів, наданих в загальний доступ) - система взагалі не відслідковує зміни в них, навіть якщо ці зміни здійснені з вашого комп'ютера. Це справедливо і для Windows-95 і для Windows NT.

Отримання і зміна атрибутів файлів
У даному розділі ми розглянемо основні функції Win32 API для отримання і зміни атрибутів файлів. Для кожного файлу підтримується кілька атрибутів, універсальних для різних файлових систем. До них відносяться атрибути архівний (archive), прихований (hidden), системний (system), тільки для читання (read-only), тимчасовий (temporary) і каталог (directory), а також дата і час створення, дата і час останньої зміни та дата та час останнього звернення до файлу.
Деякі з цих атрибутів підтримуються обмежено. Так, наприклад, атрибут тимчасовий (temporary) використовується тільки для оптимізації доступу до файлу (система по можливості зберігає всі дані в оперативній пам'яті, не записуючи їх на диск), сам же файл при закритті не видаляється.
Різні файлові системи, які можуть використовуватися локальними або віддаленими дисками, можуть надавати дати і часи з різною точністю і в різному комплекті. Так, томи зі звичайною FAT під управлінням MS-DOS зберігають лише дату і час останньої зміни файлу з точністю до 2х секунд, томи з FAT, підтримувані системами типу Windows-95, Windows NT або OS / 2 зберігається дата і час створення і останньої зміни з точністю до 2х секунд і дату (без часу) останнього доступу до файлу. Як правило файлові утиліти показують тільки час останньої зміни файлу, так як ця інформація, по-перше, надана всіма використовуваними файловими системами, а по-друге, тому що саме ця дата зазвичай цікавить користувача.
Для отримання і зміни атрибутів призначені дві функції:
DWORD GetFileAttributes (lpFileName);
DWORD SetFileAttributes (lpFileName, dwFileAttributes);
Обидві функції вимагають ім'я файлу, атрибути якого повертаються (змінюються), а також вони пакують інформацію про атрибути у вигляді подвійного слова, окремі біти якого інформують про наявність тих чи інших атрибутів. Ви можете використовувати такі символи, які визначаються замість конкретних значень атрибутів:
FILE_ATTRIBUTE_ARCHIVE
FILE_ATTRIBUTE_DIRECTORY
FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_SYSTEM
FILE_ATTRIBUTE_TEMPORARY
Крім двох розглянутих функцій, атрибути файлів повертаються ще деякими іншими, як, наприклад, вже розглянута нами функції FindFirstFile і FindNextFile, що розміщують цю інформацію в структурі WIN32_FIND_DATA, або функція GetFileInformationByHandle, повертає їх у структурі BY_HANDLE_FILE_INFORMATION. Функція GetFileInformationByHandle не працює в системі Windows 3.x з Win32s.
BOOL GetFileInformationByHandle (hFile, lpFileInformation);
struct BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DWORD nFileIndexLow;};
Перед розглядом функцій, що працюють з датою і часом, треба зробити кілька застережень, що стосуються способів представлення дати і часу.
По-перше, дата і час можуть задаватися як загальне (UTC, coordinated universal time, час за Гринвічем) так і як локальний час. Різниця зводиться до обліку часового поясу і переходу на літній час. Докладніше - див. опис функцій SetTimeZoneInformation і GetTimeZoneInformation.
По-друге, та дата та час може бути записана різними способами і в різних форматах. Іноді дата і час об'єднуються в одну структуру, іноді вони поділяються за різними структурами. Способи упаковки даних в структури розрізняються як залежно від операційних систем, так і в залежності від тих функцій, які застосовують інформацію про дату і час.
При роботі з файлами в Win32 API дата і час записані як одне квадрослово (реально представлено у вигляді структури з двох подвійних слів) FILETIME.
struct FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;};
Це квадрослово показує число інтервалів по 0.000 000 1 секунді (100 наносекунд), які пройшли, вважаючи від 4 годин ранку 1 січня 1601. Така, на перший погляд дивна, початкова дата пояснюється просто: за правилами григоріанського календаря високосними вважаються року, номер яких без остачі ділиться на 4, крім тих, які діляться без остачі на 100, але включаючи ті, номер яких без остачі ділиться на 400. Тобто рік 1904 був високосним, 1908 теж, а 1900 - ні. Проте 2000 рік, так само як і 1600 рік був таким. Таким чином за початкову точку відліку часу був узятий перший день останнього 400-річного циклу.
Для кожного файлу визначено 3 моменту, інформація про які заноситься в систему: дата і час створення файлу, дата і час останньої зміни файлу та дата та час останнього звернення до файлу.
Крім уже розглянутих функцій FindFirstFile, FindNextFile і GetFileInformationByHandle, ви можете дізнатися або призначити для конкретного файлу ці часи за допомогою наступних функцій:
BOOL GetFileTime (hFile, lpftCreation, lpftLastAccess, lpftLastWrite);
BOOL SetFileTime (hFile, lpftCreation, lpftLastAccess, lpftLastWrite);
Де hFile є хендлов відкритого файлу (див. функцію CreateFile), lpftCreation, lpftLastAccess і lpftLastWrite - покажчиками на три структури типу FILETIME. Система використовує інформацію про дату і час завжди у вигляді загального часу.
При необхідності ви можете дізнатися, яка подія сталася раніше за допомогою функції
LONG CompareFileTime (lpft1, lpft2);
А також ви можете перетворити дату і час із загального в локальне (з урахуванням часового поясу) і навпаки з допомогою функцій:
BOOL FileTimeToLocalFileTime (lpftUTC, lpftLocal);
BOOL LocalFileTimeToFileTime (lpftLocal, lpftUTC);
Обидві ці функції використовують структури FILETIME для отримання часу і повернення результату.
Якщо ви переносите розроблену раніше програму під Win32 API, то може бути простіше не переписувати всі функції, що аналізують дату і час, а просто перетворити отримані дані у формат MS-DOS. Для цього призначені функції:
BOOL FileTimeToDosDateTime (lpft, lpwDOSDate, lpwDOSTime);
BOOL DosDateTimeToFileTime (wDOSDate, wDOSTime, lpft);
які перетворять дату і час з FILETIME у формат MS-DOS - два слова, в одному упаковується дата, а в іншому - час. Проте в інших випадках застосовувати ці функції не рекомендується. Простіше перетворити FILETIME в SYSTEMTIME, яка містить всю необхідну інформацію в окремих полях структури, що дозволяє її легко аналізувати.
BOOL FileTimeToSystemTime (lpft, lpst);
BOOL SystemTimeToFileTime (lpst, lpft);
struct SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMillisecond;};
Параметри lpft і lpst є покажчиками на структури FILETIME і SYSTEMTIME відповідно; поля структури SYSTEMTIME визначають рік, місяць (січень вважається 1), номер дня тижня (0 - неділя), день місяця (починаючи з 1), годину, хвилину, секунду і мілісекунду . Застосування цих функцій можна побачити в прикладі 3B.
Робота з файлами
Як вже говорилося, в Win32 API є все необхідне для роботи з файлами. Додаток не потребує розробки додаткового коду для виконання типових операцій над файлами. Для зручності розробників в Win32 API визначені спеціальні функції, здійснюють копіювання, переміщення, видалення та перейменування файлів. Для виконання цих операцій призначені 4 функції, декларовані в Win32 API:
BOOL CopyFile (lpszFileName, lpszNewFileName, fFailIfExists);
BOOL DeleteFile (lpszFileName);
BOOL MoveFile (lpszFileName, lpszNewFileName);
BOOL MoveFileEx (lpszFileName, lpszNewFileName, fdwFlags);
Функція CopyFile здійснює копіювання файлу, заданого параметром lpszFileName у файл, заданий параметром lpszNewFileName. Додатковий параметр функції fFailIfExist визначає реакцію системи в тому випадку, коли новий файл вже існує. Якщо fFailIfExist одно TRUE, то існуючий файл зберігається, а якщо цей параметр дорівнює FALSE, то файл буде перезаписаний.
Функція DeleteFile видаляє вказаний файл.
Функції MoveFile і MoveFileEx здійснюють переміщення або копіювання зазначеного файлу. Однак області застосування цих функцій істотно розрізняються. Згідно з описом, функція MoveFile може перейменовувати або переміщати файли або каталоги, причому каталог переміщається цілком, з усіма вхідними в нього файлами і підкаталогами. Функція повертає код помилки в тому випадку, коли підсумковий файл або каталог вже існує. Якщо переміщення здійснюється в межах одного тому, то процес переміщення зводиться тільки до виправлення записів в каталогах, а реального переміщення даних не відбувається. А якщо переміщення здійснюється до іншого тому, то функція MoveFile просто копіює файл і видаляє вихідний, але при цьому переміщення каталогу до іншого тому не здійснюється - каталог можна переміщати тільки в межах свого тому.
Функція MoveFileEx (не реалізована в Windows-95 і в Win32s!) Може перейменовувати, переміщувати або видаляти лише файли, як в межах одного тому, так і з тому на тому. За допомогою додаткового параметра fdwFlags можна уточнити, яким чином буде здійснюватися переміщення файлу. Прапор MOVEFILE_REPLACE_EXISTING визначає, що файл буде переміщений або перейменований, навіть якщо підсумковий файл вже існує. Якщо файл має бути переміщений на інший диск, то аналогічно функції MoveFile переміщення здійснюється як копіювання і видалення вихідного файлу, якщо не вказати прапор MOVEFILE_COPY_ALLOWED, то таке переміщення буде заборонено. Ще один прапор MOVEFILE_DELAY_UNTIL_REBOOT дуже зручний при розробці інсталяційних програм. Цей прапор говорить системі, що файл треба переміщати не зараз, при подальшій завантаженні системи. Таким чином з'являється можливість заміни файлів, що є компонентами системи або використовуваними системою в процесі роботи. Окремий випадок використання цієї функції - завдання параметра lpszNewFileName рівним NULL спільно з прапором MOVEFILE_DELAY_UNTIL_REBOOT (згідно з описом допустимо тільки таке поєднання) - призводить до видалення зазначеного файлу при перезавантаженні системи.
Приклад 3 B - робота з каталогами
Даний приклад реалізовано виключно для платформи Win32, так як в ньому демонструється застосування деяких функцій, специфічних саме для цієї платформи. У головному вікні програми відображається вміст поточного каталогу; додатково встановлюється механізм повідомлення програми про оновлення каталогу, так що при будь-яку зміну в каталозі - створення нового файлу, видалення існуючого, перейменування та ін - вміст вікна оновлюється.
У даному прикладі інформування програми про зміну вмісту каталогу здійснюється за допомогою функцій FindFirstChangeNotification і FindNextChangeNotification, специфічних для Win32 API. Схожого ефекту можна було-б добитися при використанні коштів File Manager Extension (заголовний файл fmext.h) в рамках Windows API. Схожої, але не такого ж. Наприклад, використання розширень диспетчера файлів у Windows API для отримання інформації про оновлення вмісту каталогу обмежена тільки однією програмою у системі - два запущених додатків вже не можуть отримувати такі повідомлення.
Функції FindFirstChangeNotification і FindNextChangeNotification використовують спеціальний об'єкт, який переводиться у зайняте стан до найближчого зміни каталогу. Напрошується найпростіший спосіб - викликати функцію FindFirstChangeNotification і потім відразу функцію WaitForSingleObject, так що додаток зупиниться до тих пір, поки воно не отримає повідомлення про зміну каталогу. Спосіб надійний, але зупинене додаток не зможе оновлювати свого вмісту у вікні (якщо його вікно на час перекриють інші вікна), ви навіть не зможете завершити ваш додаток, так як воно чекає повідомлення про оновлення каталогу.
З цієї ситуації можна запропонувати два виходи - один, виключно в стилі Win32 - створити окремий потік, який буде чекати оновлень. Напевно, це самий ефективний спосіб, але про створення багатопотокових додатків буде обговорено істотно пізніше. Інший шлях - схрестити кошти Windows API і Win32 API - замість очікування звільнення об'єкта, перевіряти його стан за таймером. У цьому прийомі практично не потрібно додаткового програмування, досить додати ініціалізацію таймера і обробку повідомлень WM_TIMER.
Власне, саме таким шляхом і вирішена дана задача. Вона побудована аналогічно прикладу 2B - зміст каталогу, сформований у вигляді тексту, передається спеціальному вікна-редактору, що закриває всю внутрішню область головного вікна програми. У віконну процедуру головного вікна програми додається обробка повідомлення WM_TIMER. Сам таймер ініціалізується і звільняється у функції WinMain. Оброблювач повідомлень таймера - Cls_OnTimer - перевіряє стан синхронізуючого об'єкта (створеного при створенні вікна функцією FindFirstChangeNotification) і, якщо він звільнився, вживає заходів для відновлення вікна і знову переводить об'єкт його у зайняте стан за допомогою функції FindNextChangeNotification. Звичайно, замість таймера ефективніше було б застосувати окремий потік, а проте наведений варіант порівняно легко адаптується під 16ти розрядну платформу, в якій багатопотокових додатки не реалізовані.
Функція, яка отримує зміст каталогу й формує відображається список винесена в обробник спеціального повідомлення MY_REFRESH. Номер цього повідомлення ми присвоюємо самі, так, що б він не конфліктував зі стандартними повідомленнями вікна. Для цього можна скористатися символом WM_USER, відповідним наймолодшому номеру повідомлення, не є стандартними. Всі номери повідомлень рівні або перевищують WM_USER стандартним вікном [5] не використовуються. Це ж дозволить продемонструвати визначення пакувальників для власного повідомлення.
Файл 3B.CPP
# Define STRICT
# Include <windows.h>
# Include <windowsx.h>
# Define UNUSED_ARG (arg) (arg) = (arg)
/ / Визначаємо власне повідомлення і його Розпакувальник
# Define MY_REFRESH WM_USER +1
/ * Void Cls_OnMyRefresh (HWND hwnd); * /
# Define HANDLE_MY_REFRESH (hwnd, wParam, lParam, fn) ((fn) (hwnd), 0L)
# Define FORWARD_MY_REFRESH (hwnd, fn) (void) (fn) ((hwnd), MY_REFRESH, 0,0 L)
/ / Резервуємо глобальні змінні
static char szWndClass [] = "File & folder operations";
static char * pszCurDir;
static HINSTANCE hInstance;
/ / Розробляємо функцію, оновлюючу вміст вікна перегляду
static void Cls_OnMyRefresh (HWND hwnd)
{Int nSize;
char * pszList, szTemp [1024];
char szCreate [20], szRead [20], szWrite [20], szAttr [6];
WIN32_FIND_DATA fd;
FILETIME ftLocal;
SYSTEMTIME stTime;
HANDLE hFD;
static char szFileMask [] = "*.*";
static char szTimeFmt [] = "% 02u-% 02u-% 04u% 02u:% 02u:% 02u";
hFD ​​= FindFirstFile (szFileMask, & fd);
if (hFD) {
/ / Оскільки список файлів може бути довгим, то визначаємо його дину
nSize = lstrlen (pszCurDir) + 82; / / довжина заголовка списку
do {
nSize + = lstrlen (fd.cFileName) + 76; / / довжина запису про кожен файл
} While (FindNextFile (hFD, & fd));
FindClose (hFD);
/ / Резервуємо необхідний простір, включаючи останній '\ 0' символ
pszList = new char [nSize + 1];
if (pszList) {
/ / Готуємося до отримання списку файлів
hFD ​​= FindFirstFile (szFileMask, & fd);
if (hFD) {
/ / Формуємо заголовок
pszList [0] = '[';
lstrcpy (pszList + 1, pszCurDir);
nSize = lstrlen (pszCurDir) + 1;
lstrcpy (
pszList + nSize,
"] \ R \ n creation | last write"
"| Last access | attrs | name");
nSize + = 81;
do {
/ / Перебираємо всі файли по одному
/ / Враховуємо перехід від Грінвіча до локального часу
FileTimeToLocalFileTime (& (fd.ftCreationTime), & ftLocal);
/ / Перетворимо до прийнятного для читання формату
FileTimeToSystemTime (& ftLocal, & stTime);
/ / Записуємо дату і час
wsprintf (
szCreate, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
/ / Аналогічно записуємо час зміни
FileTimeToLocalFileTime (& (fd.ftLastWriteTime), & ftLocal);
FileTimeToSystemTime (& ftLocal, & stTime);
wsprintf (
szWrite, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
/ / І час останнього доступу
FileTimeToLocalFileTime (& (fd.ftLastAccessTime), & ftLocal);
FileTimeToSystemTime (& ftLocal, & stTime);
wsprintf (
szRead, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
/ / Розшифровуємо атрибути файлу
szAttr [0] = fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE? 'A':'-';
szAttr [1] = fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN? 'H':'-';
szAttr [2] = fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM? 'S':'-';
szAttr [3] = fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY? 'R':'-';
szAttr [4] = fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY? 'D':'-';
szAttr [5] = '\ 0';
/ / І формуємо рядок для виводу і додаємо її до списку
wsprintf (
pszList + nSize,
"\ R \ n% 19.19s |% 19.19s |% 19.19s |% 5.5s |",
szCreate, szWrite, szRead, szAttr);
nSize + = 76;
lstrcpy (pszList + nSize, fd.cFileName);
nSize + = lstrlen (fd.cFileName);
} While (FindNextFile (hFD, & fd));
FindClose (hFD);
SetWindowText ((HWND) GetWindowLong (hwnd, 0), pszList);
/ / Після передачі тексту вікна перегляду
/ / Виділений для нього буфер більше не потрібен.
delete pszList;
return;
/ / Далі ми можемо виявитися тільки в разі помилки}
delete pszList;}}
SetWindowText ((HWND) GetWindowLong (hwnd, 0), "*** Found errors! ***");}
/ / А це обробник повідомлення WM_CREATE. Створюємо дочірнє вікно перегляду і / / синхронізуючий об'єкт, для одержання повідомлень про зміну каталогу
static BOOL Cls_OnCreate (HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{UNUSED_ARG (lpCreateStruct);
HWND hwndView;
RECT rc;
HANDLE hWait;
/ / Створюємо вікно перегляду
GetClientRect (hwnd, & rc);
hwndView = CreateWindow (
"EDIT", "",
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_MULTILINE | ES_READONLY,
0, 0, rc.right, rc.bottom, hwnd, (HMENU) 1, hInstance, NULL);
if (! IsWindow (hwndView)) {
MessageBox (NULL, "Cannot create viewer window!", NULL, MB_OK);
return FALSE;}
SetWindowFont (hwndView, GetStockObject (ANSI_FIXED_FONT), FALSE);
SetWindowLong (hwnd, 0, (LONG) hwndView);
/ / Инициализируем синхронізуючий об'єкт
hWait = FindFirstChangeNotification (
pszCurDir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
SetWindowLong (hwnd, 4, (LONG) hWait);
/ / Спочатку заповнюємо вікно перегляду
FORWARD_MY_REFRESH (hwnd, PostMessage);
return TRUE;}
/ / При зміні розмірів головного вікна треба змінити розміри дочірнього вікна перегляду
static void Cls_OnSize (HWND hwnd, UINT state, int cx, int cy)
{UNUSED_ARG (state);
HWND hwndView = (HWND) GetWindowLong (hwnd, 0);
if (IsWindow (hwndView)) MoveWindow (hwndView, 0,0, cx, cy, TRUE);}
/ / Коли головне вікно закривається знищуємо вікно перегляду і синхронізуючий об'єкт
static void Cls_OnDestroy (HWND hwnd)
{HWND hwndView = (HWND) GetWindowLong (hwnd, 0);
if (IsWindow (hwndView)) DestroyWindow (hwndView);
HANDLE hWait = (HANDLE) GetWindowLong (hwnd, 4);
if (hWait) FindCloseChangeNotification (hWait);
PostQuitMessage (0);}
/ / При отриманні головним вікном фокусу вводу, передаємо його вікна перегляду
static void Cls_OnSetFocus (HWND hwnd, HWND hwndOldFocus)
{UNUSED_ARG (hwndOldFocus);
HWND hwndView = (HWND) GetWindowLong (hwnd, 0);
if (IsWindow (hwndView)) SetFocus (hwndView);}
/ / По таймеру (у нас - кожні 2 секунди) перевіряємо стан синхронізуючого об'єкта
void Cls_OnTimer (HWND hwnd, UINT id)
{UNUSED_ARG (id);
HANDLE hWait;
hWait = (HANDLE) GetWindowLong (hwnd, 4);
if (hWait) {
if (WaitForSingleObject (hWait, 0) == WAIT_OBJECT_0) {
/ / Об'єкт звільнений - каталог змінився
MessageBeep (MB_OK); / / звуковий сигнал для залучення уваги
FORWARD_MY_REFRESH (hwnd, PostMessage); / / знову завантажити список
FindNextChangeNotification (hWait); / / і чекати далі ...}}}
LONG WINAPI _export WinProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{Switch (uMsg) {
HANDLE_MSG (hWnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG (hWnd, WM_DESTROY, Cls_OnDestroy);
HANDLE_MSG (hWnd, WM_SIZE, Cls_OnSize);
HANDLE_MSG (hWnd, WM_SETFOCUS, Cls_OnSetFocus);
HANDLE_MSG (hWnd, MY_REFRESH, Cls_OnMyRefresh);
HANDLE_MSG (hWnd, WM_TIMER, Cls_OnTimer);
default: break;}
return DefWindowProc (hWnd, uMsg, wParam, lParam);}
static BOOL init_instance (HINSTANCE hInst)
{WNDCLASS wc;
hInstance = hInst;
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 8;
wc.hInstance = hInst;
wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass (& wc) == NULL? FALSE: TRUE;}
int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{UNUSED_ARG (lpszCmdLine);
MSG msg;
HWND hWnd;
int nSize;
nSize = GetCurrentDirectory (0, NULL);
pszCurDir = new char [nSize];
GetCurrentDirectory (nSize, pszCurDir);
if (! hPrevInst) {
if (! init_instance (hInst)) return 1;}
hWnd = CreateWindow (
szWndClass, / / ​​class name
szWndClass, / / ​​window name
WS_OVERLAPPEDWINDOW, / / ​​window style
CW_USEDEFAULT, CW_USEDEFAULT, / / ​​window position
CW_USEDEFAULT, CW_USEDEFAULT, / / ​​window size
NULL, / / ​​parent window
NULL, / / ​​menu
hInst, / / ​​current instance
NULL / / user-defined parameters);
if (! hWnd) return 1;
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
/ / Инициализируем таймер так, що б повідомлення передавалися кожні 2 секунди
SetTimer (hWnd, 0, 2000, (TIMERPROC) NULL);
while (GetMessage (& msg, NULL, NULL, NULL)) {
TranslateMessage (& msg);
DispatchMessage (& msg);}
if (pszCurDir) delete pszCurDir;
return msg.wParam;}
Читання і запис файлів
HANDLE CreateFile (lpszName, fdwAccess, fdwShareMode, lpszSA, fdwCreate, fdwAttrsAndFlags, hTemplateFile);
INVALID_HANDLE_VALUE = (HANDLE) -1 !!!!!!!!!!
BOOL CloseHandle (hFile);
BOOL ReadFile (hFile, lpvBuffer, nBytes, lpdwReaded, lpOverlapped);
BOOL WriteFile (hFile, lpvBuffer, nBytes, lpdwWritten, lpOverlapped);
DWORD SetFilePointer (hFile, lDistanceLow, lDistanceHigh, dwMethod);
BOOL SetEndOfFile (hFile);
BOOL FlushFileBuffers (hFile);
BOOL LockFile (hFile, dwOffsetLow, dwOffsetHigh, cbLockLow, cbLockHigh);
BOOL UnlockFile (hFile, dwOffsetLow, dwOffsetHigh, cbLockLow, cbLockHigh);
BOOL LockFileEx (hFile, dwFlags, dwUnused, cbLockLow, cbLockHigh, lpsOverlapped); / / not in 95!
BOOL UnlockFileEx (hFile, dwFlags, dwUnused, cbLockLow, cbLockHigh, lpOverlapped); / / not in 95!
BOOL GetOverlappedResult (hFile, lpOverlapped, lpcbTransfer, fWait);
DWORD WaitForSingleObject (hObject, dwTimeOut);
BOOL ReadFileEx (hFile, lpvBuffer, nBytes, lpOverlapped, lpfnCompletionFunc);
BOOL WriteFileEx (hFile, lpvBuffer, nBytes, lpOverlapped, lpfnCompletionFunc);


[0] У більшості випадків використовуються повні імена файлів, що містять імена каталогів і, можливо, точки, що входять в ім'я каталогу, а не тільки розділяють ім'я файлу та його розширення.
[1] Детальніше про роботу з вікнами дивися у відповідних розділах.
[2] Реально тестування проводилося ще й на третьому комп'ютері з системою Windows-98, проте її поведінка нічим не відрізнялося від Windows-95, за винятком уже зазначеного нюансу в реалізації функцій DefineDosDevice і QueryDosDevice.
[3] Така поведінка типово для MSCDEX.EXE, який забезпечує доступ до дисків з CDFS в середовищі MS-DOS, при відсутності диска MSCDEX часто використовує дані, що залишилися в кеші від попереднього диску, може бути навіть від того, який був раніше вставлений, або взагалі від іншого. Це, до речі, може призводити до помилки - при спробі перевірити наявність якого-небудь конкретного файлу на томі з CDFS ви можете отримати позитивну відповідь, навіть якщо те взагалі відсутній у приводі! При перевірці наявності файлу на CD-ROM краще не просто перевіряти його наявність, а відкривати і виконувати читання невеликого фрагмента, що б переконатися в дійсному присутності файлу.
[4] Аналогічна група функцій існує і для отримання інформації про принтери (FindFirstPrinterChangeNotification, FindNextPrinterChangeNotification і FindClosePrinterChangeNotification), тільки вона реалізована виключно для Windows NT. Відповідно до опису для Windows-95 або Windows 3.x з Win32s ці функції не доступні.
[5] Тобто вікном, чия процедура побудована на функції DefWindowProc. Інші вікна, як, скажімо, редактори, кнопки, списки, діалоги та інше використовують повідомлення з номерами, великими WM_USER. Однак такі повідомлення вони тільки отримують самі, але головне вікно програми їх не отримає.
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Лекція
140.3кб. | скачати


Схожі роботи:
Файлова система 2
Файлова система FAT
Файлова система NTFS
Файлова система VS DOS
Файлова система Windows
Файлова система Unix
Файлова система UNIX
Файлова система для операційної системи Windows
Навчальний модуль рейтингова система оцінювання кредитно-модульна система
© Усі права захищені
написати до нас