Up Next

קריאה וכתיבה מקבצים בחלונות

מטרת תרגיל זה לתרגל כתיבת והרצת תוכנית פשוטה בחלונות ולתרגל שימוש בקריאות מערכת בסיסיות לגישה לקבצים.

מבנה תכנית c רגילה בחלונות (שלוש נקודות מציינות קטעים חסרים):

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
...
int _tmain(int argc, LPTSTR argv[])
{  
  if (argc < 2) { /* assuming the program needs 1 argument */ 
    _tprintf( _T("usage: %s <arg>\n"),
              argv[0]); 
    exit(1); /* stop the program; report error */  }  
  ...  
  return 0; /* successful return */
}
אפילו תוכנית פשוטה כל כך מפגינה שני צדדים מורכבים מעט של פיתוח בסביבת חלונות. המורכבות הראשונה נובעת מכך שבחלונות ניתן ליצור תוכניות שבהן תוים יכולים להיות בני 8 או 16 סיביות. כאשר תוים מיוצגים רק ב--8 סיביות, המשמעות של תו תלויה בקידוד של הקובץ: למשל, בקובץ טקסט עברי תו מספר 224 מייצג את האות אלף, אבל בקובץ טקסט צרפתי אותו תו מייצג אות לטינית. כאשר תוים מיוצגים ב--16 סיביות, מערכת ההפעלה משתמשת תמיד בקידוד בסטנדט בשם יוניקוד (Unicode) שבו יש יצוג נפרד לכל אות בכל שפה שהיא. בקידוד יוניקוד מחרוזת אחת יכולה להכיל טקסט בעברית, צרפתית, יפנית וערבית, למשל. על מנת שניתן יהיה לייצר מאותו קובץ מקור גם תוכניות שבהן תוים מיוצגים ב--8 סיביות וגם תוכניות שבהן תוים מיוצגים ב--16 סיביות, מייקרוסופט הגדירה טיפוס נתונים של תוים מוכללים, TCHAR. כאשר בונים את התוכנית ושני משתני ה-preprocessor שמוגדרים כאן בשורות הראשונות, UNICODE ו--_UNICODE, מוגדרים, משתנים מטיפוס TCHAR יתפסו שני בתים (16 סיביות), אבל כאשר המשתנים הללו לא מוגדרים, משתנים מטיפוס TCHAR יתפסו רק בית אחד. גם המשמעות של המקרו _T ושל פונקציות כגון _tprintf תשתנה בהתאם. כאן אנו מעוניינים בקידוד יוניקוד, אבל אם נסיר מהתוכנית את שתי השורות הראשונות נקבל תוכנית תקינה שבה תוים תופסים בית אחד בלבד.

על מנת לכתוב תוכניות תומכות יוניקוד (וכן תומכות בתוים בני בית אחד), יש להקפיד על הנקודות הבאות: על מנת להשלים את הדיון, כדאי לציין שגם רוב מערכות יוניקס ולינוקס תומכות ביוניקוד, אבל בגישה שונה. הגישה ביוניקס ולינוקס מבוססת בדרך כלל על קידוד של תווי יוניקוד במספר משתנה של בתים, בפורמט הנקרא UTF-8. היתרון בגישה זו הוא שמחרוזות שמועברות אל ומאת מערכת ההפעלה ממשיכות להיות מחרוזות של בתים בודדים (מערכים של char), וכן שהקידוד של מחרוזות ASCII אינו משתנה (כלומר של מחרוזות של ספרות, סימני פיסוק, ותוים לטיניים ללא סימני עזר). החסרון העיקרי של גישה זו לעומת הגישה בחלונות הוא שמספר הבתים במחרוזת אינו מלמד מה מספר האותיות במחרוזת ולהיפך.

התוכנית הפשוטה הזו מראה על עוד צד מורכב בתכנות בחלונות: טיפוסי משתנים רבים שהוגדרו מראש, ויש צורך להבין את משמעותם. הטיפוסים החשובים ביותר הם: כמעט כל תוכנית שדורשת גישה לקריאות המערכת של Win32 צריכה לכלול את הקבצים windows.h ו--tchar.h. מעתה ואילך לא נזכיר אותם, אבל הם תמיד דרושים.

חלק גדול מקריאות המערכת בחלונות מחזיר ערך בוליאני. הערך TRUE מציין הצלחה והערך FALSE מציין כישלון. ניתן לברר את סיבת הכישלון בעזרת קריאת המערכת GetLastError(), שמחזירה קוד שגיאה מטיפוס DWORD. שגרת הספריה FormatMessage מחזירה מחרוזת שמתארת את משמעות קוד השגיאה.

פתיחת קובץ: קריאת המערכת CreateFile פותחת קובץ קיים ו/או יוצרת קובץ חדש. היא מחזירה מזהה שבעזרתו ניתן לקרוא ולכתוב מהקובץ (ולבצע מספר פעולות נוספות), או את הערך INVALID_HANDLE_VALUE במקרה של כשלון.

HANDLE CreateFile(
       LPCTSTR filename, 
       DWORD   access_flags,
       DWORD   share_mode_flags,
       LPSECURITY_ATTRIBUTES sa,
       DWORD   create_flags,
       DWORD   attributes_and_flags,
       HANDLE  template_file);
הארגומנט הראשון הוא שם הקובץ שרוצים לפתוח, למשל ..\data או C:\Temp\xyz.dat.

הארגומנט השני מתאר האם רוצים לפתוח את הקובץ לקריאה, כתיבה, או שתיהן. כל אחת מהפעולות מצויינת על ידי קבוע סימבולי,GENERIC_READ ו--GENERIC_WRITE. ניתן לבקש לבצע את שתיהן על ידי פעולת or של שני הקבועים.

הארגומנט השלישי מתיר או אוסר על פתיחת הקובץ לקריאה או כתיבה בזמן שהקובץ פתוח. הערך 0 אוסר על מערכת ההפעלה לפתוח את הקובץ (מתכניות אחרות או אפילו פעם נוספת מהתוכנית הזו) עד שאנו נסגור אותו, והערכים FILE_SHARE_READ ו--FILE_SHARE_WRITE מתירים לתוכניות אחרות לפתוח אותו לקריאה ו/או כתיבה בזמן שהוא פתוח על ידינו.

הארגומנט הרביעי קשור בהרשאות ובינתיים נשתמש בערך NULL, שמציין שיש להשתמש בברירות המחדל.

הארגומנט החמישי מתאר מה לעשות אם הקובץ קיים, או אם הוא איננו: הארגומנט השישי מציין תכונות של קובץ חדש (במקרה שאכן נוצר קובץ חדש) ומאפשר לבקש או להמליץ על מנגנוני גישה מיוחדים. בינתיים נשתמש בערך ברירת המחדל שהוא FILE_ATTRIBUTE_NORMAL. בין היתר, ניתן לבקש בעזרת ארגומנט זה שהקובץ החדש ימחק כאשר נסגור אותו, שקריאות מערכת שכותבות לקובץ לא יחזרו עד שהמידע לא יכתב פיזית לדיסק, ולהכריז שגישות לקובץ הן סדרתיות בעיקר או שאינן סדרתיות.

הארגונמט השביעי מאפשר ליצור קובץ חדש עם מאפיינים של קובץ קיים פתוח. אנו לא נשמש בינתיים ביכולת זו ונעביר את הערך NULL.

סגירת קובץ או החזרת משאב אחר המיוצג על ידי handle:  

BOOL CloseHandle(HANDLE h);
הפרמטר הוא המזהה שהוחזר על ידי CreateFile, או על ידי פונקציה אחרת שהחזירה מזהה מטיפוס זה. הפונקציה מחזירה קוד שגיאה. בדרך כלל סיבת הכישלון האפשרית היחידה היא שהועבר מזהה לא חוקי, כלומר מזהה שלא מייצג קובץ פתוח או משאב אחר שהוקצה על ידי מערכת ההפעלה.

קריאה וכתיבה:  

BOOL ReadFile (HANDLE  h,
                  LPVOID  buffer,
                  DWORD   number_of_bytes_to_read,
                  LPDWORD number_of_bytes_actually_read,
                  LPOVERLAPPED overlapped);
BOOL WriteFile(HANDLE  h,
                  LPVOID  buffer,
                  DWORD   number_of_bytes_to_read,
                  LPDWORD number_of_bytes_actually_read,
                  LPOVERLAPPED overlapped);
האגומנט הראשון הוא מזהה של קובץ פתוח והמזהה השני הוא מצביע למערך שאותו יש להעביר אל או מתוך הקובץ. הארגומנט השלישי הוא מספר הבתים שהתוכנית מבקשת להעביר, והרביעי הוא מצביע למשתנה שיכיל, לאחר חזרת קריאת המערכת, את מספר הבתים שהועברו בפועל. המספר הזה עשוי להיות קטן יותר מהמספר שביקשנו להעביר אם הגענו לסוף הקובץ בבקשת קריאה, או שאין מקום בדיסק בבקשת כתיבה. הארגומנט האחרון משמש לצורת גישה לקבצים שלא נדון בה כעת, ונעביר בינתיים את הערך NULL.

לכל קובץ פתוח יש מצביע שמגדיר את המקום בקובץ (בבתים) שבו תתבצע הקריאה או הכתיבה הבאה. כל פעולת קריאה וכתיבה מקדמת את המצביע במספר הבתים שהועברו. כאשר הקובץ נפתח המצביע מצביע לתחילת הקובץ.

הערות לגבי fopen, fread, וכדומה. הפונקציות fopen, fread, fwrite, fseek מספקות שירותים דומים לקריאות המערכת שמתוארות כאן אך הן חלק מהספריה הסטנדרטית של c ולא קריאות ישירות למערכת ההפעלה. שימוש בהן הופך תוכנית ליותר פורטבילית אך לא ניתן לבצע דרכן כל פעולה שניתן לבצע דרך הממשק הישיר למערכת ההפעלה (בגלל שוני בין מערכות הפעלה). בספר זה נשתמש בממשק הישיר למערכת ההפעלה ולא בספריות עזר.

התרגיל. כתוב/כיתבי תכנית שמעתיקה קובץ. התכנית צריכה לצאת ולהדפיס הודעת שגיאה אם אין מספיק פרמטרים לתכנית, אם קובץ הקלט אינו קיים, ואם קובץ הפלט קיים. התכנית צריכה להתשמש בקריאות המערכת המתוארות בתרגיל. ההעתקה צריכה להתבצע תוך שימוש בחוצץ שגודלו n בתים, כלומר התכנית קוראת n בתים בקריאה אחת, כותבת אותם וחוזר חלילה. לתכנית שלושה פרמטרים, שם קובץ הקלט, הפלט, ו--n. על מנת להפוך את המחרוזת המכילה את הייצוג של n לשלם אפשר להשתמש ב-_stscanf(argv[3],_T("%d"),&n). יש להשתמש ב--malloc על מנת להקצות את הזיכרון לחוצץ:

#include <stdlib.h>
...
char* buffer;
...
buffer = (char*) malloc(n);
מידדו את זמן הריצה של התוכנית בעזרת הפקודה processtimes (באתר) כאשר אתם משתמשים בתוכנית להעתיק קובץ בגודל מליון בתים או יותר. מידדו את זמני הריצה תוך שימוש בחוצץ בגדלים הבאים (בבתים): 1, 64, 512, 1024, 8192, ו--63556. אם יש שוני ניכר בין זמני הריצה עם חוצץ בגדלים שונים, יש להסביר את הסיבות לשוני.

Copyright Sivan Toledo 2004
Up Next