טעויות בקוד דוגמה של ServiceBase
בשבוע שעבר כתבתי דוגמת קוד לשימוש ב ServiceBase על מנת לממש Windows Service תחת .Net.
ServiceBase ממש ועוטף את כל מה שנדרש מנת לממש Service תחת .Net אבל לא כל הפונקציונליות של ה Service Control Manager או בקיצור SCM נחשפת ואנחנו צריכים לקרוא ל SCM API באופן ישיר דרך PINVOKE עבור חלק משירותים. אחד מהשירותים הוא היכולת של Service לעדכן את הSCM לגבי מצבו ב Startup/Shutdown כגון כמה זמן על ה SCM להמתין עד שהService יסיים את הפעולה.
בMSDN קיימת דוגמת קוד אשר מראה כיצד לקרוא לפונקציה SetServiceStatus על מנת לעדכן את ה SCM במצב ה Service
הדוגמה מגדירה Struct בשם public struct SERVICE_STATUS אשר מכיל את כל השדות הנדרשים ו public enum State אשר מכיל את הערכים אשר ה SCM מצפה לקבל.
CODE
1 [StructLayout(LayoutKind.Sequential)]
2 public struct SERVICE_STATUS
3 {
4 public int serviceType;
5 public int currentState;
6 public int controlsAccepted;
7 public int win32ExitCode;
8 public int serviceSpecificExitCode;
9 public int checkPoint;
10 public int waitHint;
11 }
12
13 public enum State
14 {
15 SERVICE_STOPPED = 0x00000001,
16 SERVICE_START_PENDING = 0x00000002,
17 SERVICE_STOP_PENDING = 0x00000003,
18 SERVICE_RUNNING = 0x00000004,
19 SERVICE_CONTINUE_PENDING = 0x00000005,
20 SERVICE_PAUSE_PENDING = 0x00000006,
21 SERVICE_PAUSED = 0x00000007,
22 }
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int SetServiceStatus(IntPtr hServiceStatus, SERVICE_STATUS lpServiceStatus);
protected override void OnStart(string[] args)
{
IntPtr handle = this.ServiceHandle;
try
{
myServiceStatus.currentState = (int) State.SERVICE_START_PENDING;
SetServiceStatus(handle, ref myServiceStatus);
// Do our Service Init staff here
...
...
}
catch (Exception ex)
{
ServiceEventLog.WriteEntry(String.Format("Failed to start Time Logger Service, Exception is {0} on {1} at {2}",
ex.ToString(),
ex.TargetSite,
ex.StackTrace), EventLogEntryType.Error);
myServiceStatus.currentState = (int) State.SERVICE_STOPPED;
SetServiceStatus(handle, ref myServiceStatus);
throw;
}
}
כאשר ננסה להפעיל את ה Service לאחר ההתקנה נקבל את ההודעה הבאה ב EventLog
Service cannot be started. System.AccessViolationException:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at ServiceSample.SimpleService.SetServiceStatus(IntPtr hServiceStatus, SERVICE_STATUS lpServiceStatus)
at ServiceSample.SimpleService.OnStart(String[] args)
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)"
BOOL WINAPI SetServiceStatus(
__in SERVICE_STATUS_HANDLE hServiceStatus,
__in LPSERVICE_STATUS lpServiceStatus
);
עד כאן טוב ויפה, זה נראה זהה להגדרה שלי ב DLLImport ועדיין אני מקבל את התעופה.
אז לאחר כמה דקות על Google נמצאה הבעיה. ההגדרה של ה DLLImport מוטעת ההגדרה הנכונה היא
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
כאשר אנחנו קוראים לSetServiceStatus מקוד Native אין שום בעיה מאחר ועובר מצביע לעותק Struct והפונקציה הנקראת לא יכולה לשנות את ה Struct המקורי אבל אף אחד לא באמת בודק מה היא עושה איתו, מתברר שהיא כן כותבת לשם וכאשר אנחנו רצים בסביבת Managed של .Net הבעיה מתחילה ואנחנו מקבלים את ה Exception.
לאחר זה ישבתי עם WinDBG על מנת לנסות להבין את הבעייה וראיתי את ה Machine Code של הפונקציה ומצאתי את הנקודה שכותבת ל Struct. זאת כנראה הייתה הדרך בה הייתי נוקט אם לא הייתי מוצא שום דבר חיפוש אחר הבעייה.
לסיכומו של עניין, מה שכנראה כבר ידענו מזמן, גם בתיעוד של MSDN יש טעויות.