כולנו ראינו בטלוויזיה את הפרסומת לאתר החדש של משרד הפנים - www.gov.il
אז נכנסתי ואפילו נסיתי למלאות טופס, ברשימת המדינות מצאתי את המדינה הבאה:
יהודה ושומרון
מצורף תצלום מסך

אצלנו בעובדה נתקלתי בתופעה מוזרה, אנשים אוהבים לכתוב מסמכים.
ואני לא.
לאחרונה התבצעה אצלנו עבודה לכתיבת מסמך סטנדרטים לפיתוח תוכנה, המסמך מכיל הגדרה למבנה מחלקות שמות מתודות וכו'.
במקביל לכתיבת המסמך חיפשתי את הדרך לגרום לסטנדרטים להיות חלק מצורת העבודה שלנו. ואז החלטתי לשנות את התבניות ברירת המחדל של הVisual Studio.
לצורך השינוי פתחתי את הספריה:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ItemTemplates
בספריה מצאתי את ספריה עבור כל סוג פרוייקט, אני בחרתי בCSharp ובשפה שבה אנחנו עובדים 1033 (אנגלית), בספריה יש קובץ ZIP עבור כל סוג Item שאפשר ליצור בVS.
לקחתי את קובץ Class.zip ובתוכו מצאתי את הקבצים:
Class.vstemplate
Class.cs
פתחתי את הקובץ Class.cs בVisual Studio ומשם הכל נראה פשוט, עדכנתי את הקובץ בשינוים שרציתי ופתחתי את הVisual Studio, ויצרתי מחלקה חדשה, אבל השינויים שלי לא נכנסו לתוקף, אז המשכתי לחפש ואז מצאתי את הספריה הבאה:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ItemTemplatesCache
שמכילה מבנה זה לספריה ItemTemplates אבל במקום קבצי הZIP יש ספריות.
אז מצאתי שוב את הקובץ Class.cs ושיניתי אותו. ושוב יצרתי מחלקה בVisual Studio,
והפעם זה עבד !!! המחלקה נוצרה לפי הTemplate שרציתי. אבל זה עדיין לא הספיק לי בגלל שלספריה יש את השם ItemTemplatesCache הבנתי שלא צריך לגעת בא.
ולאחר חיפוש באינטרנט מצאתי את שורת הפקודה הבאה:
"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" /InstallVSTemplates
שורה זו מבצעת את הבניה של ספרית הCache.
זהו מכאן השמיים הם הגבול.
לאחרונה נתקלתי בתופעה מוזרה, פתחתי אפליקצית .net נקיה וראיתי שהיא תופסת ~8MB. לאחר מכן השתמשתי במחלקות סטנדרטיות של .net והזיכרון שלי הגיע ל20MB.
התחלתי לנסות לברר למה זה והבנתי שמדובר בטעינה של הAssemblyים של .net.
אבל עדיין זה נראה לא סביר שאפליקציה לא משחררת את הזיכרון לאחר שהיא טוענת אותו.
לאחר חיפוש באינטרנט ראיתי את המאמר הבא: Minimize .NET Memory Consumption
המאמר מתאר שימוש בWinAPI לצורך צמצום הזיכרון שמוקצא לאפליקציה ע"י הwindows.
מה שבופעל קורה זה שבתהליך טעינת הAssembly הwindows מקצה הרבה זיכרון לאפליקציה, אבל לאחר השימוש בזיכרון הוא לא מוחזר למערכת הפעלה אלא נשאר אצל האפליקציה למקרה שהיא תצטרך אותו. בשביל שהwindows יקח בחזרה את הזיכרון נבצע קריאה למתודה: SetProcessWorkingSetSize, לפי תיעוד של מיקרוסופט שליחה של -1 יגרום לפעולת הTrim הרצויה.
להלן הקוד המלא:
[DllImport("kernel32.dll")]
static extern bool SetProcessWorkingSetSize(IntPtr hProcess, IntPtr
dwMinimumWorkingSetSize, IntPtr dwMaximumWorkingSetSize);
private static void FlushMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
SetProcessWorkingSetSize(
Process.GetCurrentProcess().Handle, new IntPtr(-1), new IntPtr(-1));
}
בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.
הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.
הפתרונות לבעיה מספר אחת:
עם הבעיה התמודדתי בהתחלה ע"י שימוש בספריה ManagedHooks שמאפשרת רישום לאירועי המערכת. כאשר האירוע מתקבל בתוכנה שלי, אני בודק האם המקש שנלחץ הוא המקש שאני מחכה לו. במידה וכן, אני מפעיל את בדיקת השגיאות.
לאחר חיפוש נוסף מצאתי את הפתרון השני, שהוא לרשום למערכת הפעלה את צירוף המקשים שלי כHotKey ע"י RegisterHotKey למידע בנושא ניתן לפנות לCodeProject. שיטה זו יותר נקיה ולא מתערבת בתוך מערכת הפעלה.
טריק יפה ששינתי מתוך הדוגמא שנמצאת בCodeProject הוא בצורה שבה אני מפעיל את הפונקציה RegisterHotKey בדוגמא שקראתי הייתה מתודה שידעה להמיר מהEnum של net.
המתודה נראית ככה:
/// <summary>
/// Calculates the character code of alphanumeric key of the Keys enum instance
/// </summary>
/// <param name="k">An instance of the Keys enumaration</param>
/// <returns>The character code of the alphanumeric key</returns>
public static byte CharCodeFromKeys(Keys k)
{
byte charCode = 0;
if ((k.ToString().Length == 1) || ((k.ToString().Length > 2) && (k.ToString()[1] == ',')))
charCode = (byte)k.ToString()[0];
else if ((k.ToString().Length > 3) && (k.ToString()[0] == 'D') && (k.ToString()[2] == ','))
charCode = (byte)k.ToString()[1];
return charCode;
}
לאחר דיבאג קצר ראיתי שהמתודה לא עובדת כאשר הKey שלי הוא לא אות.
במאמר שהופיע באתר, הוסבר שצריך לקחת מתוך הערך של k את ה4 סיביות הראשונות שהם מייצגות את המקש, ושאר הסיביות מיצגות את Shift Alt Ctrl .
אז החלפתי את הפונקיה בשורה אחת פשוטה:
/// <summary>
/// Calculates the character code of alphanumeric key of the Keys enum instance
/// </summary>
/// <param name="k">An instance of the Keys enumaration</param>
/// <returns>The character code of the alphanumeric key</returns>
public static byte CharCodeFromKeys(Keys k)
{
return (byte)((int)k & 0xFFFF);
}
ע"י שימוש פעולות בינאריות לקחתי רק את ה4 סיביות הראשונות של המספר.
דוגמאת קוד לשימוש בRegister ניתן למצוא בקוד המלא של CheckSpellingEveryWhere.
הצצה קצרה לקוד:
RegisterHotKey(this.Handle, id, ShortcutInput.Win32ModifiersFromKeys(cmd.HotKeys), ShortcutInput.CharCodeFromKeys(cmd.HotKeys));
בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.
הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.
והפעם קבלה של חלון שעכשיו המשתמש נמצא בתוכו.
הבעיה השניה נראתה לי פשוטה. מנסיוני, ידעתי שWindows API מכיל את המתודה GetFocus
בגלל זה ניסיתי את הקוד הפשוט הבא:
[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
public void TryGetFoucsWindow()
{
MessageBox.Show(GetFocus().ToString());
}
אבל בכל פעם שבדקתי תמיד חזר לי הערך 0.
לאחר מחקר קצר בMSDN הבנתי שGetFocus וGetActiveWindow עבודות כאשר החלון נמצא בתוך הThread שלי, ואז מצאתי את המודה בAttachThreadInput. מקריאה בMSDN ראיתי שבכדי להפעיל את המתודה צריך להעביר לה את הThread Id.
Thread Id ? מאיפה זה הגיע?! ואז מצאתי את המתודות הבאות:
1. GetWindowThreadProcessId - מחזירה את הThreadId של חלון.
2. GetCurrentThreadId - מחזירה את הThreadId של האפליקציה שלי.
לסיום כתבתי את הקוד הבא:
[DllImport("user32.dll")]
public static extern
IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
public static extern
IntPtr AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, int fAttach);
[DllImport("user32.dll")]
public static extern
IntPtr GetFocus();
[DllImport("user32.dll")]
public static extern
IntPtr GetActiveWindow();
[DllImport("user32.dll")]
public static extern
IntPtr GetForegroundWindow();
[DllImport("Kernel32.dll")]
public static extern
IntPtr GetCurrentThreadId();
public void TryGetFoucsWindow()
{
IntPtr activeWindow = GetForegroundWindow();
IntPtr threadID1 = GetCurrentThreadId();
IntPtr threadID2 = GetWindowThreadProcessId(activeWindow, IntPtr.Zero);
if (threadID1 != threadID2
{
AttachThreadInput(threadID1, threadID2, 1);
IntPtr focusWindow = GetFocus();
AttachThreadInput(threadID1, threadID2, 0);
MessageBox.Show(focusWindow);
}
}
החתימות הnet. לכל המתודות מתוך הWin32 Api הגיעו מתוך Pinvoke.net
בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.
הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.
והפעם קריאה של הטקסט מתוך החלון.
לכאורה, עוד משימה פשוטה במיוחד, כיוון שקיימת פונקצית API בשם GetWindowText
השם מבטיח, אבל עם הבטחות לא הולכים למכולת.
ניסיתי לבצע את הקריאה ל GetWindowText, עבור רוב החלונות זה באמת עבד. כאשר עמדתי על כפתור, קיבלתי את הערך שלו, גם כאשר עמדתי בתוך חלון רגיל, אבל בתוך תיבת טקסט זה פשוט לא עבד.
לאחר חיפוש באינטרנט קראתי על המתודה GetDlgItemText אבל גם היא לא נתנה את הפתרון,לכן פניתי לפתרון הבסיסי ביותר, שימוש בשליחת הודעות ברמת מערכת הפעלה מקריאה בMSDN ידעתי שניתן לבצע שליחה של הודעה WM_GETTEXT לחלון ולקבל את תוכן החלון כתשובה.
בMSDN הופיעה החתימה הבא:
Syntax:
To send this message, call the SendMessage function as follows.
lResult = SendMessage(
// returns LRESULT in lResult
(HWND) hWndControl,
// handle to destination control
(UINT) WM_GETTEXT,
// message ID
(WPARAM) wParam,
// = (WPARAM) () wParam;
(LPARAM) lParam
// = (LPARAM) () lParam;
);
Parameters
wParam
Specifies the maximum number of TCHARs to be copied, including the terminating null character.
Windows NT/2000/XP:ANSI applications may have the string in the buffer reduced in size (to a minimum of half that of the wParam value) due to conversion from ANSI to Unicode.
lParam
Pointer to the buffer that is to receive the text.
Return Value
The return value is the number of TCHARs copied, not including the terminating null character.
זה נראה פשוט לבצע את הקריאה מתוך קוד ++C שזאת הדרך המקורית להשתמש בAPI אבל מתוך NET. זה לא כזה טריוויאלי.
לאחר חיפוש באינטרנט מצאתי שכל מה שצריך לעשות זה ליצור את החתימה הבאה:
[DllImport("user32.dll")]
public static extern
IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, StringBuilder sb);
ולאחר קריאה למתודה הStringBuiler יכיל את התוכן של החלון ומכן הכל פשוט.
נקודה יפה נוספת היא כאשר רוצים לקבל את הסימון של הטקסט בתוך חלון, ניתן לשלוח לו את הודעה EM_GETSEL ולקבל את StartIndex ואת הEndIndex של הסימון.
בהמשך לפוסט על בודק השגיאות בVS הגיעו אלי פניות מגורמים רבים שסיפרו לי שהם משתמשים בבודק השגיאות, וכמות שגיאות הכתיב ירדו, אבל עדיין הם נתקלים בהרבה חלונות שבהם הם לא יכולים לבדוק את השגיאות, בגלל זה יש להם שמות קבצים עם שגיאות, ואפילו נוצרים פרוייקטים חדשים עם שגיאות כתיב.
אז החלטתי לפתור את הבעיה אחת ולתמיד ולהוסיף את היכולת לבדיקה של שגיאות כתיב לכל חלון במחשב.
הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים, כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.
כמו שלמדנו בעבר לכל בעיה יש פתרון, לכן אני מפרסם פוסט ייחודי לכל נושא.
בכל מקרה מצורפת התקנה של התוכנה לשימוש חופשי.
וכל הקוד של התוכנה כנ"ל לשימוש עצמי.
בתחילת הפיתוח של הפרוייקט שלנו הגדרנו בsolution אחד את הפרויקטים השונים כאשר היה פרויקט WEB ולכל שיכבה היה פרויקט משלה בנוסף הגדרנו פרויקט infrastructure שהכיל קוד שלא היה קשור ספציפית לפרויקט שלנו, לאחר שהצוות התחיל לפתח מערכת נוספת במקביל.
הוחלט להוציא את התשתיות של שני הפרויקטים לsolution נפרד ושני המערכות יבצעו reference לפרויקטי התשתית.
ובאמת זה מה שעשינו והגדלנו והוספנו רישום אוטומטי לGAC של DLL התשתית בשביל למנוע בלגן עם ריבוי גרסאות והעתקות של הDLLים. ואז ביום בהיר(למרות שהיה יום גשום נהוג לכתוב יום בהיר) ניסיתי לבצע דבאג למתודה שכרגע סיימתי לכתוב, אני רגיל לכתוב את המתודה ולהשתמש בה מתוך הפרויקט עצמו ואז ולבצע בדיקות למתודה דרך המערכת ולבצע דבאג עם צריך. אבל אבוי אני מנסה לבצע step into ולא מצליח.
לאחר כמה דקות של מחשבה הבנתי שאני לא יכול לבצע step into בגלל שלDLL אין קובץ PDB שיאפשר ביצוע דבאג, אבל מה עושים שאלתי קודם את עצמי ואחר כך את שותפי לדרך שני ואבי, וקיבלתי את התשובה הברורה לך יואב לך על האינטרנט ומצא תשובתך,
וכך עשיתי באינטרנט מצאתי משהו שנתקל בדיוק באותה הבעיה והוא מציע להעתיק את קובץ הPDB לתוך הספרייה המתאימה בGAC הפתרון הזה יגרום לזה שכשהVS ינסה לדבאג הוא ימצא את הקובץ הPDB ואת קובץ הDLL ביחד.
אבל הפתרון הזה דורש ממני להוסיף פעולת העתקה ידנית של קובץ באירוע post build, לאחר מחשבה נוספת הבנתי שיש פתרון פשוט בהרבה כל מה שצריך לעשות זה, מתי שרוצים לדבאג לבצע העתקה של קובץ הPDB והDLL לספריית הBIN של הפרויקט ואז הDLL יטען משם ולא מהGAC ובסיום הדבאג למחוק את הקבצים מהBIN ולהמשיך לעבוד מול הGAC.
לפני כ4 חודשים עברתי לצוות שמפתח מערכות GIS על גבי התשתיות של חברת ESRI. אנחנו בפרויקט שלנו עובדים מול הArcGis Server שהוא מוצר הדגל של ESRI, הAPI לכל הפעולות נקרא Arc Objects שזה בעצם אוסף של רכיבי COM שחיים על השרת ומאפשרים לך לעשות כמעט הכל מול כלי ESRI.
במסגרת החפיפה העבירו לי ולחברי לצוות סדנה שעסקה בכתיבת קוד Arc Objects בNET.
מרבית הסדנה עסקה בכמה שלא נוח לעבוד עם Arc Objects ודאגו להזכיר לנו שאי אפשר לבצע new למחלקות של Arc Object וחייבים לבצע יצירה של אובייקטים ע"י קריאה לServerContext.CreateObject, לי בתור תוכניתן asp החתימה נראתה מוכרת כי היא הייתה בדיוק כמו בעבודה מול רכיבי COM בASP, אבל למרות זאת אני מעדין לעבודStrong typed.
בתיעוד של ESRI ראיתי שהמתודה מקבלת מחרוזת שמזהה את סוג האובייקט esriGeomerty.Point או esriCatro.Map וכו'... ראיתי שטעויות בהפעלת הפונקציה לא משפיעות על קומפילציה ולכן אני הולך לבזבז הרבה מאוד זמן על התעסקות מרגיזה עם מחרוזות מציקות.
אז התחלתי לחפש פתרון לבעיה, הפתרונות שאני מצאתי הם:
- להגדיר enum עבור האובייקטים שאנחנו משתמשים בהם ביום יום ובכך להוריד את הצורך בהקלדה חוזרת שלהם.
- לעטוף את המתודה CreateObject באובייקט משלי שידע לקבל טיפוס נתונים ויתפקד כמו new.
לאחר בחינה קצרה החלטתי ללכת על הפתרון השני והגדרתי מחלקה גנרית עם מתודה בחתימה הבאה:
T ArcObjectFactory.CreateObject<T>();
כאשר T הוא טיפוס הנתונים שאנחנו רוצים ליצור.
המתודה תבצע את הבניה של המחרוזת עבור T ואחרי זה תבצע הפעלה של CreateObject.
בניית המחלקה הסתיימה לפני כ4 חודשים מאז אני מתכנת מול ArcObjects ואני יכול להגיד שאין לי מושג איך הייתי מסתדר בלי זה. אני כותב קוד קריאה יותר מהר יותר ומתעסק יותר בבעיות האמיתיות של הקוד ולא במלחמות במחרוזות.
לפני שבוע קיבלתי טלפון בעצבים מאחד המפתחים בצוות שעובד מול כלי ESRI, הוא אמר שהוא לא מוצא בשום מקום את המחלקה ArcObjectFactory מסתבר שהוא קיבל קוד לכתוב בArcObjects וכתבו לו להשתמש בArcObjectFactory אבל הוא לא מצא אותו בEDN וגם לא בMSDN אז הוא הלך לGOOGLE אבל גם שם הוא לא מצא אותו אז אני מבטיח לך יוסי מהיום הArcObjectFactory מופיע באינטרנט.
אחת הבעיות בעבודה בצוות זה שמשהו צריך לקרוא את הקוד שלך.
הרבה נכתב על כללים לכתיבת קוד קריאה, אני תמיד מנסה לכתוב קוד ברור אבל מאז ומעולם אני כותב בשגיאות כתיב, לי זה לא מפריע כי אני מבין מה שאני כותב אבל לא כולם כמוני.
בצוות הקודם שלי שגיאות הכתיב שלי הפריעו מאוד לחברי הצוות, אז הייתי מחזיק תמיד WORD פתוח ובודק שם את המילים שלא הייתי בטוח בהם, אבל זה לא הספיק בגלל שלא תמיד הייתי בודק בWORD והיו חיבורים מילים שהWORD לא ידע לפרק.
ואז באחד הערבים הקרים החלטתי לכתוב את הAdd in הראשון שלי לVS היה ברור לי שאני רוצה להשתמש במנוע של הWORD בשביל לבדוק את השגיאות ושאני רוצה לבצע שבירה של מילים לפי כללי הNaming הסטנדרטים.
אז התחלתי בהוספת reference לרכיב הcom של WORD 11, ובגלל שלא ראיתי תיעוד נורמלי, אז התחלתי לעבור על רשימת המתודות ומצאתי את GetSpellingSuggestions שמקבלת בערך 20 פרמטרים שונים ומחזירה את הצעות לתיקון של המילה המועברת.
ואז פניתי למשימה של שבירת המילים בגלל שWORD יודע לקבל מילה בודדת, אז קודם כל שברתי את הטקסט לפי רווחים ו"_" ועבור כל מילה ביצעתי שבירה לפי אותיות גדולות קטנות, כל זה טוב ויפה אבל לא מספיק כי זה חלק מAdd in לVS אז בתוך הקוד של Add in הוספתי את הקוד לקריאה של הטקסט המסומן ע"י הקוד הבאה:
string selectedText = ((TextSelection)_applicationObject.ActiveDocument.Selection).Text;
ואז כל מה שנשאר לי זה לחבר את הכל ביחד וקיבלתי את התוסף המושלם לVS שבודק לי את השגיאות ועוזר לחבריו הצוות שלי לקרוא את הקוד שלי.
לסיכום אני ממליץ לכל אחד ממכם לנסות לעבוד מול API של הWORD אתם תמצאו שם מתודות מעניינות כמו Dummy ועוד...
בעולם שבו הזמן שמוקדש למשימות קידוד הולך ויורד היכולת לשפר את סביבת העבודה שלנו, מאפשר לנו להגיע לתוצר איכותי בלו"ז קצר.
ולנסות לכתוב Add in ל VS.
מצורף הפרוייקט כהשראה למה שאפשר לעשות בVS.
קצת על עצמי,
במהלך חיי יצא לי לפתח במגוון רחב של שפות החל בASP וVB ובהמשך בC++ לWindows בMFC ובשנתיים וחצי האחרונות אני מפתח בסביבת ה NET.
בחודשים האחרונים התחלתי לעבוד גם מול הכלים של ESRI.
מידי יום אנחנו בצוות מתמודדים עם אתגרים מקצועים
וחיפשתי את המקום להציג את האתגרים ואת הפתרונות
שאני מצאתי להם.
אני יציג בבלוג דוגמאות קוד וסיפורים מסביב לפרוייקטים.
אני מקווה שהבלוג הזה יהווה מוקד ידע ויתרום למי שיכנס עליו.
בברכת ברוכים הבאים,
יואב.
נ.ב.
תמיד כולם שואלים אותי איפה זה קיבוץ שמיר אז החלטתי לצרף מפה