לשרוד פיתוח תואם "Windows" בעידן Unicode...

ב־13.6.2010, מאת ארתיום; פורסם תחת: תכנה חופשית, פיתוח, תכנה ומחשבים, C++‎‏, Unicode, Boost‏; ‏8 תגובות

אם פיתחתם מעט עבור Windows אתם בוודאי מכירים את המושג שנקרא Wide-API. קרי לכל פונקציית המערכת יש שתי גרסאות: "ANSI" ו־"Wide", למשל: DeleteFileA ו־DeleteFileW, כאשר אחת מהן מקבלת char const *‎ והשניה wchar_t const *‎.

נחמד לא? יש לך שני סוגי API שנוח לך, או לעבוד עם המחרוזות הפשוטות או לעבוד עם מחרוזות מבוססות "תווים־רחבים". אבל, לא בדיוק.

למעשה, לפי מדיניות של Microsoft, כדי לגשת לכל הכוח של מערכת ההפעלה אתה חייב להשתמש ב־Wide API אחרת... אתה אפילו לא תוכל ליצור קובץ "שלום.txt". נחמד, לא?

הבעיה שלא C99 ולא, C++‎, וגם לא C++0x לא מכירים במושג העמעום של wide-path ולמעשה, לפי התקן אין, דרך לפתוח קובץ שהשם לו הוא "שלום.txt" מקודד כמחרוזת של wchar_t (רק char). כמובן חבר'ה ב־MS הם "ידידותיים" למפתחים והם הציע API חילופי: ‎_wfopen(wchar_t const *,wchar_t const *);‎ וגם הוסיפו הרחבה לסטנדרט ב־Visual Studio‏: std::fstream::open(wchar_t const *,...);‎ הכל לטובת המפתח (הם כנראה לא שמעו בכלל על UTF-8)...

כאן, מתחיל הסיוט, למעשה למפתח יש שתי אופציות:

  • להתעלם מהטמטום של Wide-API ולעבוד רק עם "ANSI-API" ו... התוכנה תפסיק לעבוד באופן אקראי על קבצים אקראיים, והמשתמשים המסכנים ישברו את הראש מה לא עובד.
  • להתחיל לשכפל את כל הקוד שלך למקרה wide ולמקרה נורמלי.

סיוט. לא פלא, שרבים בורחים באופציה הראשונה, ולכן בשנת 2010, אנחנו נתקלים יישומים כמו Thunderbird, שלא עובד כשתיקיית המשתמש שלך בחלונות מכילה תוים עבריים.

כך גם, אני כשהתחלתי תמיכה בחלונות ב־CppCMS החלטתי להתעלם מהעובדה ש־fopen או std::fstream::open לא יעבדו לי. אבל בסוף ייסורי מצפון הזיזו את כף־המאזניים: מספיק לכתוב קוד גרוע... והכנתי ספריית עזר קטנה, קראתי לה booster::nowide. כל מה שהיא עושה זה: להעביר ל־namespace שלך את הכלים סטנדרטיים שקיימים ב־stdio וב־STL בכל מערכות ההפעלה נורמליות. ובחלונות, היא פשוט עוטפת אתה API‏ ‎_w*‎ עם פונקציות משלה שממירות UTF-8 ל־UTF-16 ואז קוראות לפונקציות המתאימות.

בנוסף יצרתי מחלקות תואמות ל־std::fstream שעובדות מעל stdio (ועל הדרך סוף־סוף הבנתי כיצד לממש streambuf משלך).

התוצאה? אם אתה מתכוון לתמוך בחלונו, במקום לכתוב

std::ofstream f("שלום.txt")

שמייצרת ג'יבריש במקרה הטוב אתה כותב:

booster::nowide::ofstream f("שלום.txt")

וזה עובד בצורה שקופה, כנ"ל, std::fopen, std::freopen, std::remove, std::rename - רק החלף std::‎ ב־booster::nowide::‎.

במילים אחרות: no-more-wide-crap!

הספרייה היא חלק מ־booster של CppCMS ומופצת תחת רישיון Boost (משהו סגנון MIT). אם מישהו ירצה ספריה בלתי תלויה ב־booster, אז תגידו ואני אגזור אחת (לא מסובך בכלל).

קריאה נוספת: "Should UTF-16 be considered harmful?‎‏"

גרסת הספרית nowide התלויה בקומפיילר C++‎ לבלבד ניתנת להורדה כאן:
http://art-blog.no-ip.info/files/nowide.zip

תגובות

elcuco, ב־13.6.2010, 23:31

באופו מאוד מיקרי, בדיוק דיברתי על זה היום עם מישהו (נחש מי...?)

מזל טוב, אתה עוד צעד אחד קרוב יותר לממש עוד חלק ב-Qt4. באימא שלי, אתה כזה עקשן, בסוף תגלה שכל הבעיות אתה פותר נפתרו בטרוללטק לפני 7 שנים (נניח עם Qt 2.0). ברגע שתבין שאני צודק זה יהיה פחות כואב!

ארתיום, ב־14.6.2010, 7:03

דיאגו... בניגוד ל־Qt4 אני מתרחק כמה שאפשר מחרא של UTF-16.

בנוסף, אני הולך עם סטנדרט, כך למשל booster::nowide::basic_filebuf יורש מ־std::basic_streambuf ועובד שקוף עם C++‎ הסטנדרטי.

אגב, לידיעתך Qt3 לא יודעת להתמודד עם תוים מחוץ ל־BMP (אבל גם Windows)

elcuco, ב־14.6.2010, 10:54

תקני, בדיוק. אבל יש לך שכבה מעל התיקני שמתקנת את הבעיות שאתה מוצא, יש לך תלות במלא ספריות חיצוניים, ה-API שלך לא אחיד, אין לך תאימות קוד או בינארית. צודק - אתה הולך בדרך הישר.

ברצינות עכשיו: אני מבין שכשאתה תלוי ב-API צד שלישי (MFC לדוגמה, או win32api) לכתוב ב-Qt4 אפילו דברים כמו QString, QFile, QList או משהו כזה לא בא בחשבון. מובן לחלוטין.

ארתיום, ב־14.6.2010, 11:19

תקני, בדיוק. אבל יש לך שכבה מעל התיקני שמתקנת את הבעיות שאתה מוצא, יש לך תלות במלא ספריות חיצוניים, ה-API שלך לא אחיד, אין לך תאימות קוד או בינארית. צודק - אתה הולך בדרך הישר.

באמת, לא הבנתי למה אתה מתכוון: התלויות היחידות בספריות החיצוניות שלי הן pcre, zlib ו־ICU שכולם לגמרי מוסתרים עמוק בפנים. ו־booster הוא חלק מה־CppCMS מאין util עבורו, שכתוב בסגנון Boost אבל עם תאימות בינארית לאחור.

אגב, אני לא משתמש ב־booster::nowide::fstream באופן ישיר ב־API שלי, אם כי אני נותן אפשרות להשתמש בו (מי שירצה).

ב־API שלי אני פשוט מגדיר: בחלונות std::string הוא utf-8 בכל הקשור לטיפול ב־path.

בכל מקרה, איפה אני תלוי ב־API צד ג':
http://art-blog.no-ip.info/cppcms_ref_v0_99_1/

elcuco, ב־14.6.2010, 15:59

אני אקשיב לעיצה שלך ואסתכל טוב טוב על הקוד שלך :)

פעם קודמת שניסיתי כמעט הקאתי בגלל שלוש exceptions ו־13 casts (שהיו בסגנון של c ולא C++‎).

ארתיום, ב־14.6.2010, 20:52

פעם קודמת שניסיתי כמעט הקאתי בגלל שלוש exceptions ו־13 casts (שהיו בסגנון של c ולא C++‎).

באיזה קוד הסתכלת? של CppCMS 0.0.x או של CppCMS 1.x.x. אני באמת, לא כל־כך גאה בשני שליש של הקוד שכתבתי. אבל CppCMS 1.x.x המצב קצת שונה.

חוץ מזה exceptions ישנם ויהיו ב־CppCMS כי זאת דרך בריאה לדווח על אירועים חריגים באמת.

אני מבין שאת רגיל ל־Qt שלא זורק exceptions, אבל שם זה מאוד הגיוני (זה סיוט להשתמש event driven programming וב־exceptions בו זמנית).

אההה, גם qt4 הוא לא exception safe ;-)

Shlomi Fish, ב־14.6.2010, 23:31

רק תיקון טעות: זה "ייסורי מצפון" - לא "יישורי מצפון".

ארתיום, ב־15.6.2010, 6:49

תודה, תוקן

הוסף תגובה:

 
 כתובת דוא"ל לא תוצג
 

ניתן לכתוב תגובות עם שימוש בתחביר Markdown.

חובה לאפשר JavaScript כדי להגיב.

דפים

נושאים