הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
מאמרים בנושא C++.
על תמיכה בלוקליזציה ב־C++ ועל תקן שבור
ככל שעובד הזמן אנחנו מתקרבים יותר ויותר לתקן C++ החדש שבהחלט מעשיר את השפה בהרבה כלים חיוניים -- הן מבחינת ליבת השפה והן לבחינת הספריה הסטנדרטית.
אבל נשאר מקום אחד שבו C++0x כמעט ולא נגע, אפילו שהתקן שבור לחלוטין -- לוקליזציה...
לצורך הדגמה בואו נכתוב תכנה טרוייואלית שמדפיסה מספר לקובץ טקסט number.txt:
#include <iostream>
#include <fstream>
#include <locale>
int main()
{
// Set global locale to system default;
std::locale::global(std::locale(""));
// open file "number.txt"
std::ofstream number("number.txt");
// write a number to file and close it
number<<13456<<std::endl;
}
תכנה פשוטה אפילו טריוויאלית. עכשיו בואו נעשה אותו דבר ב־C.
#include <stdio.h>
#include <locale.h>
int main()
{
setlocale(LC_ALL,"");
FILE *f=fopen("number.txt","w");
fprintf(f,"%'i\n",13456);
fclose(f);
return 0;
}
גם פשוט ביותר.
בואו נריץ את התכנות כאשר הלוקל הוא en_US.UTF-8
... ונקבל בקובץ number.txt את המספר: 13,456, עכשיו נעשה אותו דבר, רק נגדיר לוקל נפוץ אחר ru_RU.UTF-8
: כך LC_ALL=ru_RU.UTF-8 ./a.out
ונקבל:
13<?>456
כאשר אם נריץ תכנה ב־C נקבל דווקא משהו נכון: 13 456
אופס... מה קרה כאן? כשאני עושה את אותו הדבר דרך C ודרך C++ אני מקבל תוצאה שונה, יותר מזה, הפלט של תכנה שכתובה ב־C++ מצביעה על טקסט UTF-8 לא תקין! (אפשר לבדוק בקלות עם iconv).
מה בעצם קראה כאן?
לפי הגדרות הלוקל הרוסי. התו שמפריד בין אלפים ומאות הוא תו U2002 -- EN SPACE -- סוג של רווח שונה מרווח ASCII רגיל U0020. כלומר בגלל שמדובר בתו Unicode שהוא לא בתחום ASCII הוא צריך להיות מקודד כמחרוזת ארוכה מבית 1.
אז למה ספריית C מתמודדת יפה מאוד עם בעיה זו וספריית C++ לא?
עכשיו בואו נסתכל בהגדרות הסביבה להצגת מספרים: המחלקה std::numpunct
זאת הייתה דוגמה של בעיוה הטריוויאלית לשחזור. קיימים עוד בעיות עקרוניות רבות כאלה בתקן לוקליזציה הקיים ב־C++ היום. חבל שהוועדה שמטפלת ב־C++0x כלל לא התייחסת לנושא זה --- אולי הגיע זמן להעלות את הנושא?
ייסורי בינאום ולוקליזציה בשנת 2009
מה יותר פשוט מאשר להציג תאריך ושעה בהתאם למקובל באיזור? בארה"ב תכתוב 6/15/2008 8:30 PM ובישראל תכתוב 15/6/2008 20:30. לא מסובך, נכון? כמובן strftime עושה את העבודה הנאמנה בצורה טובה. אכן, יש אופציות "%x" ו־"%X" להצגת תאריך ושעה בהתאם ללוקל מקומי.
עכשיו בואו נעשה את הדרך ההפוכה: יש לנו טופס HTML בו אנחנו רוצים להציג תאריך ולתת למשתמש אפשרות לערוך אותו? במילים אחרות אני רוצה אופציה של פענוח תאריך ושעה לפי התבנית המקובלת לאיזור ובשפה הספציפיים... ואכן, יש פונקציה הפוכה ל־strftime --- strptime. שעושה את העבודה ההפוכה.
כמובן גם ב־C++ יש מקבילות std::facet
מסוג: std::time_put
ו־std::time_get
עושות את העבודה וגם מאפשרות להשתמש ביותר מ־locale אחד בתהליך, שמאפשר, למשל לייצר טקסט שונה למשתמשים שונים בחוטים שונים. ב־C ובשירותי מערכת ההפעלה הסטנדרטיים אין כלי כזה, יש רק strftime_l
ו־strptime_l
הלא מתועדים ולא מוגדרים לפי סטנדרט שעושים את העבודה.
מכאן, הכל פשוט... על הנייר. במציאות המצב הרבה יותר מסובך:
- הגדרות סטנדרט C++ של
std::time_get
מכילות באג לוגי שמקשה מאוד על יצירת כלים לפענוח תאריך ושעה בצורה נכונה וגם כשעובדים, הם עושים זאת בצורה חלקית --- כי אין מקבילה של strptime המלאה של C. - גם API של C, מסתבר כלא תמיד עובד כמו שצריך.
לצורך ההשוואה, בניתי תכנה פשוטה, שמייצרת מחרוזת עם תאריך ושעה בעזרת כלֵי C, C++ וספריית ICU. ולאחר מכן מפענחת אותה אם אותם הכלים. בדקתי את זה עבור מספר לוקלים. בטבלה למטה מוצגים: לוקל, שעה ותאריך כפי שמייוצרים ע"י כלים שונים והפענוח של אותם תאריך ושעה המוצגים בפורמט בינלאומי.
en_US.UTF-8
strftime 10/05/2009 11:36:38 PM to 2009-10-05 23:36:38
std::facet 10/05/2009 11:36:38 PM to 2009-10-05 00:00:00
icu::DateFormat Oct 5, 2009 11:36:38 PM to 2009-10-05 23:36:38
he_IL.UTF-8
strftime 05/10/09 23:36:38 to 2009-10-05 23:36:38
std::facet 05/10/09 23:36:38 to 1909-10-05 23:36:38
icu::DateFormat 23:36:38 05/10/2009 to 2009-10-05 23:36:38
de_DE.UTF-8
strftime 05.10.2009 23:36:38 to 2009-10-05 23:36:38
std::facet 05.10.2009 23:36:38 to 2009-10-05 23:36:38
icu::DateFormat 05.10.2009 23:36:38 to 2009-10-05 23:36:38
en_GB.UTF-8
strftime 05/10/09 23:36:38 to 2009-10-05 23:36:38
std::facet 05/10/09 23:36:38 to 1909-10-05 23:36:38
icu::DateFormat 5 Oct 2009 23:36:38 to 2009-10-05 23:36:38
ja_JP.UTF-8
strftime 2009年10月05日 23時36分38秒 to 2009-10-05 23:36:38
std::facet 2009年10月05日 23時36分38秒 to 2009-10-05 23:36:38
icu::DateFormat 2009/10/05 23:36:38 to 2009-10-05 23:36:38
ar_EG.UTF-8
strftime 05 أكت, 2009 IST 11:36:38 to 2009-10-05 00:00:00
std::facet 05 أكت, 2009 IST 11:36:38 to 2009-10-05 11:36:38
icu::DateFormat ٠٥/١٠/٢٠٠٩ ١١:٣٦:٣٨ م to 2009-10-05 23:36:38
tr_TR.UTF-8
strftime 05-10-2009 23:36:38 to 2009-10-05 23:36:38
std::facet 05-10-2009 23:36:38 to 2009-10-05 23:36:38
icu::DateFormat 05.Eki.2009 23:36:38 to 2009-10-05 23:36:38
ru_RU.UTF-8
strftime 05.10.2009 23:36:38 to 2009-10-05 23:36:38
std::facet 05.10.2009 23:36:38 to 2009-10-05 23:36:38
icu::DateFormat 05.10.2009 23:36:38 to 2009-10-05 23:36:38
zh_CN.UTF-8
strftime 2009年10月05日 23时36分38秒 to 2009-10-05 23:36:38
std::facet 2009年10月05日 23时36分38秒 to 2009-10-05 23:36:38
icu::DateFormat 2009-10-5 下午11:36:38 to 2009-10-05 23:36:38
כפי שניתן לראות:
- std::facet לא מתמודד עם שעון של 12 שעות המכיל ציון AM/PM.
- std::facet לא מוסוגל לשחזר שנה בעזרת שתי ספרות בלבד וזורק אותנו משנת 2009 ל־1909 ב־2 מתוך 9 דוגמאות.
- strptime לא מצליח לשחזר שעה בכלל ו־std::facet וטועה ב־12 שעות בלוקל מצרי.יש לציין שזה באג בייצוג locale כי הפורמט המוגדר הוא "%Z %I:%M:%S" במקום "%p %I:%M:%S" --- להציג איזור זמן במקום סימון AM/PM המתאים (م).
במילים אחרות... מימוש של std::facet גרוע, יש בעיות בייצוג locale באופן כללי.
עצוב שזה המצב בשנת 2009...
CppCMS פוגש Comet
פרסמתי כאן כתבה על טכנולוגיית Comet (או Server Push) הנתמכת בגרסה הבאה של CppCMS.
הצגתי כדוגמה קלאסית: מימוש של יישום Chat, בצד הלקוח ובצד השרת, בכ־50 שורות קוד בכל אחד מהם, עם שימוש ב־XHR Long Polling.
חדשות CppCMS...
לאחרונה אני עובד בענף ה־refactoring של CppCMS ומבצע שינויים גדולים. אביא כאן סקירה קצרה של השינוי שכבר נמצאות בגרסה ניסיונית:
אחד השינויים הגדולים והחשובים זה להיפתר מכל התלויות המיותרות או מגבילות. אחרי שכל השינויים יסתיימו, התלויות היחידות שיישארו הן גרסה עדכנית של Boost ו־Python לצורכי הפיתוח בלבד.
חלק מהספריות הוסרו בגלל אי תאימות לדרישות החדשות:
- libfcgi ירד כי ה־API שלו לא מאפשר גישה א־סינכרונית, במקומו, מימשתי את הפרוטוקול בעצמי על בסיס Boost.Asio.
- ספריית CgiCC ירדה בגלל האיכות הירודה שלה וחוסר יכולת סבירה לתקשר עם המפתח של אותה הספרייה הבעייתית.
כך שבגרסה הבאה, תהיה לי אפשרות להכין debים ו־rpmים בקלות...
עד היום, פיתוח מול CppCMS דרש שימוש בשרת חיצוני, למרות שיש לי סקריפט הפעלה אוטומטית של lighttpd, nginx ו־Apache, עכשיו מימשתי שרתי HTTP פנימי פשוט שמקל על הפיתוח ובנוסף, עתיד יקל על שיבוץ התשתית ביישומים שדורשים ממשק web ובמערכות משובצות מחשב.
כך שכיום, CppCMS החדש תומך בשלושה ממשקים FastCGI, SCGI ו־HTTP.
התווספה תמיכה מסודרת בלוקליזציה עם שימוש ב־std::locale, כך שתצוגת התאריכים, מספרים, מחרוזות וכד' יתבצעו בהתאם למקובל באותה שפה.
בגלל שימוש מקיף ב־Boost -- ספריה בלתי תלויה בפלטפורמה, אני סוף־סוף אוכל להכריז על Windows כפלטפורמה שנתמכת באופן (חצי) רשמי.
יש עוד הרבה עבודת אינטגרציה של רכיבים שיצאו באופן זמני, כמו ניהול sessions, שינויים ב־cache, שכתוב תמיכה בטפסים ועוד.
אבל לאט־לאט ההשפעה של השינויים הארכיטקטוניים מתחילים להתבהר.
לתמוך או לא לתמוך, זאת השאלה (ב־Win32)?
כפי שפרסמתי בעבר, אני עובד על שינויים בפנימיים עמוקים ב־CppCMS שבין השאר יאפשרו:
- תמיכה באירועי צד השרת או Comet.
- תמיכה בבינאום ולורקליזציה.
- תמיכה ב־ABI לאחור.
- ניקיון יסודי של קוד תוך הקטנה משמעותית בשימוש בספריות צד ג'.
בגלל שאני עושה שינויים כל־כך מהותיים, חשבתי, אולי להוסיף גם תמיכה בעוד פלטפורמה בנוסף לתמיכה ב־Linux, FreeBSD, Solaris ו־Cygwin... להוסיף תמיכה ב־Windows.
זה באמת, לא אמור להיות עד כדי כך מסובך, יצא לי כבר פעם לבנות CppCMS עבור Mingw (כמובן נאלצתי לוותר על חלק גדול מהמודולים).
אבל, בין לבנות משהו שרץ עד לפלטפורמת פיתוח אמתית יש תהום.
המשך...