ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Програмування служб: подробиці
         

     

    Інформатика, програмування

    Програмування служб: подробиці

    Сергій холодиль

    Наша служба и опасна и трудна

    І на перший погляд ніби не видно

    Ю. Ентін

    У статті описані деякі деталі, пов'язані з програмування служб Windows NT/2000/XP. Вона не претендує на повноту або унікальність. Дещо не охоплено, багато чого (хоча і не всі) з охопленого ви зможете знайти в MSDN або іншій літературі. Якщо ви написали свою першу службу і хочете рухатися далі, ця стаття вам допоможе.

    Для розуміння написаного нижче ви повинні бути знайомі зі службами. Глибоких знань не потрібно, достатньо уявляти собі архітектуру служби (з точки зору програміста) і пам'ятати зразкову призначення кількох API-функцій.

    Більша частина що містяться в статті тверджень описує реакцію Windows на якісь дії з боку служби. Повноцінна перевірка таких тверджень є неможливою. Тим більше, що деякі з них не задокументовані.

    Я зробив так:

    У Windows 2000 Server SP1 я постарався перевірити все. В інших версіях тільки дещо. Можливо, деякі отримані факти я витлумачив невірно. Але поки що помилок я не знайшов.

    Якщо твердження є в MSDN і/або іншому джерелі, я перевіряв його два-три рази, якщо всі сходилися, вважав його правильним.

    Якщо твердження суперечить тому, що написано в MSDN і/або інших джерелах, тривалість тестування залежала від його важливості (з моєї точки зору). У цьому випадку у статті зазначені й мої результати, і інформація з MSDN або інших джерел. Якщо я вважаю затвердження важливим, крім цього зазначено, які моменти могли бути втрачені під час тестування. Ця версія статті не містить важливих спірних тверджень.

    Якщо твердження не зустрілося мені ні в одному джерелі, я чинив аналогічно попередньому пункту.

    Загальні особливості служб

    У цій частині статті розібрані питання, що мають безпосереднє відношення до будь-якої службі. Поділ на «безпосередні» і «Непрямі» умовно і суб'єктивно. Принцип, якого я дотримувався, такий: якщо проблема/можливість властива служб із-за особливостей їх архітектури, вона описана в цій частині. Інакше - в наступному.

    Встановлення/видалення

    Робота з будь-якою програмою починається з установки і закінчується видаленням. Служби - не виняток. Відмінність полягає в тому, що при установці службу необхідно зареєструвати. Можна, звичайно, покласти цю завдання на інсталятор, але, як на мене, правильніше і простіше писати служби, які вміють встановлюватися/віддалятися в напівавтоматичному режимі.        

    ПРИМІТКА   

    Про всяк випадок: деякі розумні люди,   які знають, як правильно писати інсталятори, вважають, що в цьому питанні   я помилявся.     

    Наприклад, так:        

    int _tmain (int argc, TCHAR * argv [])   

    (   

    // Якщо в командному рядку що-то є -   

    // імовірно, запускає користувач.   

    if (argc == 2)   

    (   

    // lstricmp - порівняння без урахування регістру.   

    if (lstrcmpi (argv [1],   TEXT ( "/ install "))== 0)   

    (   

    CmdLine:: Install ();   

    )   

    else if (lstrcmpi (argv [1],   TEXT ( "/ uninstall "))== 0)   

    (   

    CmdLine:: Uninstall ();   

    )   

    else   

    (   

    CmdLine:: DisplayHelp ();   

    )   

    return 0;   

    )   

    ...             

    ПРИМІТКА   

    TEXT () і _tmain - для   підтримки Unicode (а можна сказати «для підтримки ANSI»). Детальніше в розділі   «Unicode».   

    CmdLine - простір   імен. Я їх ніжно люблю і часто використовую.   

    Взагалі-то, те, що в   командному рядку «щось є» нічого не доводить, див. «Дрібниці».       

    Функції, що виконують власне установку/видалення, виглядають приблизно так:        

    void CmdLine:: Install ()   

    (   

    відкриваємо SCM (OpenSCManager)   

    створюємо службу (CreateService)   

    закриваємо все, що відкрили   

    )      

    void CmdLine:: Uninstall ()   

    (   

    відкриваємо SCM (OpenSCManager)   

    відкриваємо   службу (OpenService)   

    видаляємо   службу (DeleteService)   

    закриваємо все, що відкрили   

    )     

    Відлік пішов ...

    На деяких етапах виконання служба повинна виконати певні дії за певний термін. В MSDN з різним ступенем конкретності перераховані п'ять вимог. У книзі Джеффрі Ріхтера і Джейсона Кларка «Програмування серверних додатків в Windows 2000» наведено шосте. Нижче перераховані самі вимоги і мої коментарі до них.

    Служба повинна викликати StartServiceCtrlDispatcher НЕ пізніше, ніж через 30 секунд після початку роботи, інакше виконання служби завершиться. Практика підтвердила. Крім того, в розділ Event Log'а System буде додано запис про помилку (джерело - «Service Control Manager»). Якщо служба запускається вручну з програми Services, користувач отримає повідомлення (MessageBox).

    Функція ServiceMain повинна викликати RegisterServiceCtrlHandler [Ex] негайно. Що буде в іншому випадку - не зазначено. Недотримання цього правила - один з випадків «порушень під час ініціалізації »(термін мій), описаних нижче у цьому ж розділі.

    Функція ServiceMain повинна викликати SetServiceStatus перший раз «майже відразу» після RegisterServiceCtrlHandler [Ex], після чого служба повинна продовжувати викликати її до тих пір, поки ініціалізації не закінчиться. Неправильне використання SetServiceStatus - другий випадок «Порушень під час ініціалізації».

    При обробці повідомлення служба має повернути управління з функції Handler [Ex] протягом 30 секунд, інакше SCM згенерує помилку. Практика підтверджує, запис у Event Log додається. Але ніяких репресивних дій по відношенню до служби я не дочекався.

    При отриманні повідомлення SERVICE_CONTROL_SHUTDOWN служба повинна закінчити роботу за час, що не перевищує число мілісекунд, вказане в параметрі WaitToKillServiceTimeout ключа HKLMSystemCurrentControlSetControl, інакше буде завершена примусово. Практика підтвердила.

    Після завершення роботи в якості служби (тобто після надсилання службою повідомлення про це) процесу дається 20 секунд на очищення/збереження/ще щось, після цього процес завершується. Детальніше в розділі «Коректне завершення».

    Якщо ваша служба швидко ініціалізується і миттєво обробляє повідомлення, внаслідок чого автоматично задовольняє всім пунктам, вам пощастило. Якщо ні, можна використовувати декілька потоків. Наприклад, так:

    Додатковий потік виконує необхідну ініціалізацію, а основний потік викликає StartServiceCtrlDispatcher.

    Я не зміг придумати, навіщо робити що-небудь до виклику RegisterServiceCtrlHandler [Ex], але якщо треба, можна зробити так само, як у (1).

    Один з потоків надсилає повідомлення про просування процесу, друга виконує ініціалізацію. Функція першого потоку може бути такий:        

    DWORD WINAPI SendPending (LPVOID dwState)   

    (   

    sStatus.dwCheckPoint = 0;   

    sStatus.dwCurrentState =   (DWORD) dwState;   

    sStatus.dwWaitHint = 2000;      

    for (;;)   

    (   

    if   (WaitForSingleObject (eSendPending, 1000)! = WAIT_TIMEOUT) break;   

    sStatus.dwCheckPoint ++;   

    SetServiceStatus (ssHandle,   & sStatus);   

    )      

    sStatus.dwCheckPoint = 0;   

    sStatus.dwWaitHint = 0;   

    return 0;   

    )     

    Повідомлення надсилаються за допомогою функції SetServiceStatus. sStatus - глобальна змінна типу SERVICE_STATUS, що описує стан служби, в dwState передається стан, про який необхідно повідомляти, eSendPending - подія, встановлення якого означає закінчення роботи цього потоку.

    Забавно, що за такого підходу для служб, що запускаються вручну, видимий результат не змінюється (див. нижче про порушення під час ініціалізації).

    Ідея в тому, що, якщо обробка повідомлення може затягнутися, функція Handler [Ex] ініціює її і завершується, не чекаючи закінчення. Якщо робочий потік служби в циклі очікує якихось подій, обробку може виконати він, Handler [Ex] повинна тільки проінформувати його про парафії повідомлення, якщо робочий потік постійно зайнятий, можна породити ще один потік. При подібної реалізації необхідно врахувати, що таке повідомлення може прийти протягом обробки попереднього, тобто до того, як служба пошле повідомлення про закінчення обробки. За допомогою Services цього не зробити, але користувач може використовувати утиліту Net.exe (синтаксис запуску: net команда імя_служби) або написати свою.

    Обмеження, що накладаються вимогами (5) і (6) обійти не вдається. Але, на відміну від (5), в (6) момент посилки повідомлення про завершення регулюєте ви. Тому можна виконувати всю необхідну очищення/збереження/ще щось наперед.

    Тепер про «порушення в процесі ініціалізації». Варіанти порушень:

    Затримка перед викликом RegisterServiceCtrlHandler [Ex].

    Затримка перед першим викликом SetServiceStatus.

    Занадто великі паузи між викликами SetServiceStatus.

    Не змінюється поле dwCheckPoint структури, що передається SetServiceStatus.

    У всіх перерахованих випадках реакція системи буде однаковою. А саме:

    A) Служба запускається автоматично.

    Через кілька хвилин (якщо за цей час «порушення» не припиниться і служба не почне працювати нормально) в Event Log-е з'явиться запис «The ... service hung on starting. "

    Якщо хоч одна служба «зависла», користувач отримає повідомлення «At least one service or driver failed during system startup. Use Event Viewer to examine the event log for details. "Таке відчуття, що це повідомлення з'являється в той момент, коли запускається перша «зависла» служба (сам розумію, що звучить нелогічно, але що робити ...).

    B) Служба запускається вручну з Services.

    хвилини три система почекає.

    З'явиться повідомлення про помилку.

    У програмі Services у стовпці Status служба буде позначена словом «Starting».

    У будь-якому випадку служба, врешті-решт, запуститься.

    Ця інформація не дуже важлива (і, до речі, не документована), тому що навіть таких порушень краще не допускати. Але уявляти, що буде, якщо з якихось причин, ваша служба злегка пригальмує, корисно.

    Хто буде працювати?

    Це питання виникло у мене, коли я писав свою першу службу. Якщо чіткіше сформулювати, то звучить він так: який з потоків можна використовувати як робочий? На перший погляд задіяно три потоки: один виконує main/WinMain, другий - ServiceMain, третє - Handler [Ex] (не зовсім так, див. «Дрібниці»). Очевидно, що перший і третій потоки не підходять. Про другий потік нічого не відомо і, цілком можливо, функція ServiceMain повинна повертати керування. Я вчинив просто: створив у ServiceMain додатковий потік, який виконував роботу. Закінчення функції виглядало так:        

    ...   

    //   Створює робочий потік і повертає управління   

    Begin ();   

    )     

    Це працює. Ніяких додаткових проблем при такому підході не виявлено.

    Після уважного прочитання MSDN з'ясувалося, що взагалі-то для роботи призначений потік, що виконує ServiceMain. Більш того, в описі написано: «A ServiceMain function does not return until its services are ready to terminate. »Повертати управління з ServiceMain відразу рекомендується тільки службам, що не потребують в потоці для виконання роботи (наприклад, вся робота може полягати в реакції на повідомлення). Я раджу дотримуватись рекомендацій Microsoft.

    Коректне завершення

    Якщо ваша служба успішно виконала свою місію або, навпаки, остаточно провалилася (неважливо, під час виконання або ініціалізації), її потрібно завершити. Кілька варіантів того, «як робити не треба »:

    Завершити всі робочі потоки, потік, що виконує Handler [Ex] не чіпати. У цьому випадку SCM «нічого не помітить» і служба продовжить виконуватися. Це не смертельно, але й не дуже добре, тому що ресурси-то використовуються.

    Завершити всі робочі потоки, потік, що виконує Handler [Ex] завершити викликом ExitThread при обробці першого наступного повідомлення. SCM генерує помилку і додає запис про неї в Event Log.

    Завершити процес викликом ExitProcess. Результат аналогічний попередньому, навіть помилка така сама. Дивно, що код завершення процесу не зберігається.

    А тепер про те, як треба. Про закінчення роботи служба повинна повідомити. Як завжди, для повідомлення про зміну стану використовується функція SetServiceStatus. У даному випадку з усіх полів переданої в неї структури SERVICE_STATUS інтерес представляють dwCurrentState, dwWin32ExitCode і dwServiceSpecificExitCode. dwCurrentState в будь-якому випадку має бути встановлено в SERVICE_STOPPED, значення решти залежать від ситуації.

    Служба завершилася успішно. dwWin32ExitCode = NO_ERROR, в Event Log нічого записано не буде.

    Сталася фатальна помилка, і це одна з стандартних помилок Windows. dwWin32ExitCode = ERROR_ ..., в Event Log буде додано запис, що описує помилку, числове значення помилки зазначено не буде.

    Сталася фатальна помилка, специфічну до вашої служби. dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR, в dwServiceSpecificExitCode код помилки. Так як систему кодування помилок ви придумали самі, розшифрувати значення коду можете теж тільки ви. У Event Log буде додано запис наступного змісту: «The ... service terminated with service-specific error ... »(у місцях крапок - ім'я служби і код помилки).

    Якщо для завершення служби необхідно виконати тривалі дії, в процесі їх виконання має сенс посилати повідомлення SERVICE_STOP_PENDING. Але це не обов'язково.

    Ще один тонкий момент: що буде з вашою службою після виклику SetServiceStatus? Всі потоки припинять виконуватися відразу і остаточно, чи їм дадуть «вмерти природною смертю»? Я спробував з'ясувати це, і отримав наступне (це вірно для будь-яких варіантів завершення служби, при яких викликається SetServiceStatus з відповідними параметрами, крім випадку з SERVICE_CONTROL_SHUTDOWN):

    Якщо exe-файл містить декілька служб і хоч одна з них (не рахуючи завершується) запущена, нічого цікавого не відбудеться. Те їсти, ні процес, ні один з потоків не будуть завершені насильно.

    Після завершення останньої служби функція ServiceControlDispatcher повертає керування в main/WinMain. Якщо main/WinMain самостійно закінчується протягом 20-ти секунд, то, як і у нормального програми, виконується функція ExitProcess () і завершуються всі потоки.        

    ПОПЕРЕДЖЕННЯ   

    Так як ServiceControlDispatcher   повертає керування в main/WinMain відразу після виклику SetServiceStatus,   main/WinMain може викликати ExitProcess раніше, ніж ваш робочий потік (або   потоки, якщо у вас їх декілька) закінчать виконання. У результаті, наприклад,   можуть виявитися невизваннимі деструктори стекові об'єктів. Щоб уникнути   цього можна вчинити так:   

    - отримати описувач робочого потоку   (наприклад, з допомогою DuplicateHandle) і зберегти його в глобальній змінній      

    - в main/WinMain дочекатися завершення   робочого потоку   

    Інші (але теж сумні) можливі   наслідки передчасного виклику ExitProcess описані в MSDN в Q201349:   "PRB: ServiceMain thread May Get Terminated in DllMain () when a Service   Process Exits Normally ".   

    Велике спасибі Dima2 за це зауваження.     

    Через 20 секунд після завершення останньої служби процес знищується.

    звальним гріх

    В один exe-файл можна помістити кілька служб. Назва розділу характеризує моє ставлення до таких проектним рішенням. Це ускладнює кодування і налагодження, а єдиний відомий мені виграш -- економія ресурсів на комп'ютері користувача (якщо ви пишете кілька залежних один від одного служб, напевно, з'являються й інші виграші; я цим жодного разу не займався). Але, тим не менше, на моїй машині в service.exe знаходяться служби Alerter, AppMgmt, Browser, Dhcp, dmserver, Dnscache, Eventlog, lanmanserver, lanmanworkstation, LmHosts, Messenger, PlugPlay, ProtectedStorage, seclogon, TrkWks, W32Time і Wmi. Навряд чи їх писали люди дурніший мене.

    Інтерактивність

    інтерактивності в службах слід уникати. Служби призначені для безперервної роботи у відсутності користувачів, тому чекати, доки оператор натисне "OK", можна дуже довго. Але, тим не менше, можливості є.        

    ПОПЕРЕДЖЕННЯ   

    Зауважте, що всі описані в цьому   розділі методи (а про існування інших я не чув) дозволяють   взаємодіяти тільки з консольної сесією (служби запускаються в   консольної сесії, оскільки в ній запускається SCM). Тому інтерактивна (в   широкому сенсі цього слова) служба буде некоректно працювати в Windows   NT/2000 Terminal Service і, що набагато важливіше, в Windows XP при   використанні можливості Fast User Switching. Тобто, все буде коректно,   але, можливо, не зовсім так, як ви очікували. Рекомендую почитати про Terminal   Service (це круто!) І ніколи не використовувати інтерактивність в службах.     

    Найпростіше - відобразити повідомлення (MessageBox). Це може будь-яка служба, які б прапори не стояли. Для цього потрібно у функцію MessageBox [Ex] крім інших прапорів передати MB_SERVICE_NOTIFICATION або MB_DEFAULT_DESKTOP_ONLY. Перший прапор змусить функцію вивести повідомлення на екран, навіть якщо користувач ще не увійшов в систему. Виглядає кумедно. Уявіть: на екрані запрошення ввести пароль та десяток повідомлень, вітаю користувача з 1 квітня. Але для цього доведеться написати десять служб, так як процес не може відображати на екрані кілька таких повідомлень одночасно, судячи з усього, вони ставляться в чергу (до MB_DEFAULT_DESKTOP_ONLY це теж відноситься). Якщо встановлений другий прапор, повідомлення з'явиться тільки на "Нормальному" робочому столі. Більш строго, MB_SERVICE_NOTIFICATION змушує повідомлення з'явитися на поточнем активному desktop-е, а MB_DEFAULT_DESKTOP_ONLY тільки на "нормальному". Цими прапорами можна користуватися, якщо визначений макрос _WIN32_WINNT І його значення більше або дорівнює 0x0400.        

    ПРИМІТКА   

    Для реалізації цієї   можливості залучені неслабкі кошти. У Spy + + видно, що вікна   (MessageBox) належать одному з потоків CSRSS.EXE. Це має кумедний   побічний ефект: повідомлення може висіти на екрані навіть після завершення   додатки. Зберіть і запустіть таку програмку:   

    .   

    # define _WIN32_WINNT 0x0500   

    # include   

    int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)   

    (   

    MessageBox (NULL, "try to kill me", "undead",   MB_SERVICE_NOTIFICATION);   

    return 0;   

    )   

    .   

    А тепер спробуйте вбити   процес з Task manager'а. Якщо зробити декілька потоків, можна перевірити   ExitProcess, вона в цій ситуації теж не допомагає.     

    Якщо користувальницький інтерфейс вашої служби повинен бути багатшими, існує два виходи. У першому випадку повинні виконуватися наступні умови:

    Служба запущена в контексті безпеки LocalSystem.

    Служба повинна бути позначена як інтерактивна.

    Значення параметра NoInteractiveServices ключа HKLMSYSTEMCurrentControlSet ControlWindows має дорівнювати 0.

    Якщо все це так, служба може виводити на екран що завгодно. Інакше, служба може спробувати самостійно відкрити та використовувати потрібний їй desktop. Подібне про об'єкти «desktop» і «window station» дивіться в MSDN.

    Подробиці програмування

    Нижче розглянуті питання, що мають непряме відношення до служб, але пряме до їх програмування. Тобто, при розробці вашої конкретної служби все це може вам і не знадобитися, але взагалі розбиратися в це треба. Більшість тем мають відношення не тільки до служб, але і до звичайних додатків. Розгляд не дуже докладно, але введення дано (крім питання про системі безпеки).

    Налагодження

    Є кілька причин, через які налагоджувати служби складніше, ніж звичайні програми.

    У службі буде як мінімум два потоки.

    Службу запускає SCM.

    Якщо в exe-файлі декілька служб, налагодження буде ще неприємніше.

    Однак найчастіше вдається написати програму, повністю відтворює робочу частину служби, налагодити його і помістити в обгортку з деякого стандартного коду, що перетворює його в службу. Якщо ядро програми налагоджено, і код, який реалізує стандартні функції служби, перевірений, при стикуванні великих проблем бути не повинно. Але (як це не дивно) з'являються середні і дрібні. Крім «налагодження без відладчика» залишається наступне.

    Приєднати відладчик до запущеної службі.

    Використовувати DebugBreak.

    У HKLMSOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Options додати ключ «Імя_ісполяемого_файла.exe» (без шляху), в ньому створити рядковий параметр Debugger і записати в нього повне ім'я відладчика. При запуску вказаного файла, він буде запущений під відладчиком.

    Використовувати SoftIce.

    При налагодженні коду запуску слід пам'ятати, що обмеження на час (30 секунд) нікуди не зникає, якщо ви не укладетеся, служба завершиться.

    Адміністрування

    Зазвичай у служби є якісь параметри, які можна налаштовувати. Іноді потрібно мати можливість визначати і коригувати поточне стан служби.        

    ПРИМІТКА   

    Зміна стану і зміна   параметрів - це не одне й те саме. Наприклад, ви написали якусь мережеву   службу. Якщо є можливість змінювати стан, адміністратор може,   переглянувши перелік поточних з'єднань, якісь з них розірвати. Зміною   параметрів цього не досягти.     

    Зупинимося на разі не інтерактивної служби, так як в інтерактивній службі проблему можна вирішити так само, як і в звичайному додатку.

    Для адміністрування пишеться окремий додаток (далі - конфігуратор), що якимось чином взаємодіє зі службою. Можу запропонувати наступні варіанти:

    Параметри зберігаються в реєстрі (зазвичай в HKLMSystemCurrentControlSetServicesімя_службиParameters). Конфігуратор їх змінює, служба, використовуючи функцію RegNotifyChangeKeyValue, відслідковує ці зміни.

    Параметри зберігаються де завгодно. Після їх зміни конфігуратор посилає службі повідомлення. Для цього потрібно відкрити службу функцією OpenService з правом доступу SERVICE_USER_DEFINED_CONTROL та надіслати повідомлення функцією ControlService. Звичайно, у службі необхідно передбачити відповідну обробку.

    Використовуються іменовані канали, сокети, DCOM або будь-який інший засіб комунікації між процесами. У цьому випадку крім передачі параметрів можна отримувати і змінювати стан служби.

    Найпростіший варіант - служба реагує на зміну параметрів тільки після перезапуску.

    Безпека

    Це дуже велика тема, про яку я дуже мало знаю. Але вона має пряме відношення до служб, якщо ви хочете займатися їх розробкою, вам (і мені, звичайно) доведеться з нею розібратися.

    Event Log

    Інтерактивність - це недобре. Але служба повинна якимось чином повідомляти адміністратора про помилки й інші важливі події. Служб, що генерує кілька десятків (або більше) повідомлень на день, доцільно використовувати для цього файли у форматі який-небудь СУБД. Для решти краще рішення - Event Log. Він досить гнучкий, стандартний, адміністратор може переглянути його утилітою Event Viewer.        

    ПРИМІТКА   

    Event Log може використовуватися будь-якою   додатком. Оскільки стаття присвячена службам, я використовую слово «служба».   

    Можливості Event Log-а не   вичерпуються тим, що описано в цьому розділі, але зазвичай нічого більшого і не   потрібно. Якщо вам потрібно більше, раджу звернутися до вже згадуваної   книзі Джеффрі Ріхтера і Джейсона Кларка «Програмування серверних додатків   в Windows 2000 »або до MSDN.     

    Якщо ви хочете записати повідомлення в Event Log, потрібно:

    створити файл повідомлень (при розробці служби);

    зареєструвати у реєстрі нове джерело повідомлень (при інсталяції);

    власне записати повідомлення.

    Файл повідомлень

    Файл повідомлень - це exe-або dll-файл, що містить ресурс «таблиця повідомлень». Для отримання потрібно скомпілювати message compiler'ом (mc.exe) правильно написаний текстовий файл (нижче), що вийшов файл ресурсів включити в проект і скомпонувати. Mc.exe не інтегрована у Visual Studio і запускається з командного рядка. Ключами можна не користуватися, досить написати «mc filename.mc». На виході буде filename.h, filename.rc, filenameXXXX.bin (у деяких випадках кілька штук). filename.rc - той самий файл ресурсів, він посилається на filenameXXXX.bin. filename.h містить код повідомлення і використовується службою.

    «Правильно написаний текстовий файл» має наступну структуру:        

    [Заголовок]   

    сообщеніе_1   

    ...   

    сообщеніе_N     

    Заголовок необов'язковий, він може бути відсутнім або бути присутнім частково. Зазвичай використовується тільки поле LanguageNames. Синтаксис нескладний:        

    LanguageNames =   (обозначеніе_для_язика_1 = LangID_1: імя_bin_файла_1)   

    ...   

    LanguageNames =   (обозначеніе_для_язика_n = LangID_n: імя_bin_файла_n)     

    «обозначеніе_для_язика» і «імя_bin_файла» можуть бути довільними, таблиця LangID є в MSDN (дивіться GetLocalInfo ()).

    Зміст цього поля - перерахування підтримуваних мов. Якщо файл повідомлень підтримує декілька мов, у різних локалізаціях Windows (російської, англійської, ...) Event Log буде відображати різні версії повідомлень.

    Саме повідомлення виглядає так:        

    MessageId = 0x1   

    Severity = Success   

    Facility = Application   

    SymbolicName = MSG_COOL_HACK      

    Language = English   

    Hi! I hack you! You login:% 1, you password:% 2.   

    .      

    Language = Russian   

    Привіт! Я тебе зламав!   Твій логін:% 1, твій пароль:% 2.   

    .     

    Поля «MessageId», «Severity», «Facility» і «SymbolicName» складають заголовок повідомлення.        

    MessageId         

    Код повідомлення. Чи не   обов'язковий, у разі відсутності інкрементіруется MessageId попереднього   повідомлення (для першого повідомлення MessageId - 0).             

    Severity         

    Тип повідомлення, визначені   типи «Success», «Informational», «Warning», і «Error», їх назви можна   перевизначити у заголовку, але на назву типу та іконку, які відображаються Event   Viewer'ом, це не вплине. Не обов'язково, у разі відсутності успадковується   від попереднього повідомлення, за замовчуванням - значення «Success».             

    Facility         

    Дозволяє задати категорію   повідомлення. Визначено значення «System» і «Application», можна визначити (в   заголовку файлу) ще близько чотирьох тисяч. Не обов'язково, в разі відсутності   успадковується від попереднього повідомлення, перше повідомлення за замовчуванням має   значення «Application».             

    SymbolicName         

    Назва відповідного   повідомленням макросу у генерований h-файлі.     

    Тіло повідомлення починається після рядка «Language = XXXX »і закінчується рядком, що не містить нічого, крім точки і перекладу рядка. На кожний визначений у заголовку мовою повинно бути по одному «тіла» (якщо ви не визначили ні однієї мови, використовуйте «English»). Замість «% 1» ... «% 99» будуть вставлені рядки, які додаток передасть при записі повідомлення. Врахуйте, що цей механізм призначений для передачі імен файлів, IP-адрес, якихось чисел і т.д. Але не для передачі тексту. Можна, звичайно, зробити так:        

    Language = English   

    % 1   

    .     

    але, з моєї точки зору, це погана ідея. Справа в тому, що у файлах Event Log-а зберігається ім'я джерела, номер повідомлення, передані рядки і прикріплені дані, але не сам текст. Тому, якщо записати повідомлення, а потім змінити dll або значення параметра EventMessageFile в реєстрі, текст зміниться. Наскільки я знаю, це потрібно, щоб, коли користувач з Китаю, у якої все на китайському, посилає свій файл із логом (лог, що описується в цьому розділі, знаходиться в WinNTSystem32configAppEvent.Evt) розробнику з Нігерії, той міг би, використовуючи свою dll, прочитати ті ж повідомлення на нігерійському.

    Реєстрація джерела повідомлень

    У ключі реєстру HKLMSystemCurrentControlSetServicesEventlogApplication потрібно створити ключ з будь-яким ім'ям (це ім'я буде ім'ям джерела повідомлень), в цьому ключі створити рядковий параметр EventMessageFile і записати в нього повний шлях до файлу повідомлень.

    Назва джерела повідомлень відображається Event Viewer-го в колонці «Source», вона ж використовується для отримання описувача (handle) при записи в Event Log.

    Запис

    Для початку потрібно отримати описувач.        

    ПРИМІТКА   

    На запитання «описувач чого?» я відповісти   не можу. Просто   описувач. ... У MSDN сказано так: «If the function succeeds, the return value is a   handle that can be used with the ReportEvent function. "     

    Цю операцію виконує функція RegisterEventSource. Вона виглядає так:        

    HANDLE   RegisterEventSource (   

    LPCTSTR lpUNCServerName,   

    LPCTSTR lpSourceName   

    );     

    Параметри:        

    lpUNCServerName         

    Ім'я сервера, на якому знаходиться лог.   Для запису в лог поточної машини передавайте NULL.             

    lpSourceName         

    Ім'я зареєстрованого джерела   повідомлень.     

    Для закриття описувача використовується функція DeregisterEventSource.        

    BOOL DeregisterEventSource (   

    HANDLE   hEventLog   

    );     

    Запис повідомлення виробляє функція ReportEvent.        

    BOOL   ReportEvent (   

    HANDLE hEventLog,   

    WORD wType,   

    WORD wCategory,   

    DWORD dwEventId,   

    PSID pUserSid,   

    WORD wNumOfStrings,   

    DWORD dwDataSize,   

    LPCTSTR * pStrings,   

    LPVOID   pRawData   

    );     

    Параметри:        

    hEventLog         

    Описувач, отриманий від   RegisterEventSource.             

    wType         

    Тип повідомлення, повинен   збігатися з типом, записаним у файлі повідомлень. Варіанти:   EVENTLOG_SUCCESS, EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE,   EVENTLOG_ERROR_TYPE.             

    wCategory         

    Передавайте 0.             

    dwEventId         

    Код повідомлення. Чи не   рівний «MessageId». Береться з створюваного mc.exe заголовки.             

    pUserSid         

    Передавайте NULL.             

    wNumOfStrings         

    Кількість переданих   рядків.             

    dwDataSize         

    Розмір переданих даних.             

    pStrings         

    Масив рядків. Якщо строк   менше, ніж позицій, в зайвих позиціях буде «% n», де n - номер позиції.             

    pRawData         

    Дані, що прикріплюються до   повідомленням.     

    Unicode

    Unicode - кодування, в якій одному символу відповідають два байти. У результаті виходить 65536 різних символів. Використовувати Unicode в програмах для Windows NT/2000/XP корисно і просто. Корисно тому, що, по-перше, це підвищує продуктивність (чому -- нижче), по-друге, дозволяє вивести на екран або у файл саме те, що ви хочете, незалежно від локалізації ОС користувача і, по-третє, іноді це набагато зручніше. А просто тому, що вся необхідна підтримка забезпечена.

    Більшість API-функцій, що приймають як параметрів рядка, існують у двох варіантах - ANSI і Unicode. ANSI-варіант має суфікс «A», Unicode-варіант - суфікс «W» (від wide - широкий). У Windows NT/2000/XP ANSI-функції просто перетворять передані рядка в Unicode і викликають відповідну Unicode-функцію. Unicode - «рідна» кодування для цих ОС. Для Win 9x «рідна» кодування - ANSI, в ОС цієї групи повністю реалізовано лише кілька Unicode-функцій, решта відразу повертають помилку. Тому програма, що використовує Unicode, в Windows NT/2000/XP буде працювати швидше, а в Win 9x не буде працювати взагалі. Оскільки в Win 9x служба все одно не зможе працювати, це не має вас хвилювати.

    Якщо ви не стикалися з Unicode раніше й не вивчали заголовки з оголошеннями API-функцій, попередній абзац може вас спантеличити. Швидше за все, ви неодноразово використовували API-функції, що приймають рядки і точно пам'ятаєте, що у них не було ніяких суфіксів. А виявляється -- є. Нижче наведена частина файлу winbase.h:        

    WINADVAPI   

    BOOL   

    WINAPI   

    EncryptFileA (   

    LPCSTR lpFileName   

    );   

    WINADVAPI   

    BOOL   

    WINAPI   

    EncryptFileW (   

    LPCWSTR lpFileName   

    );   

    # ifdef UNICODE   

    # define EncryptFile EncryptFileW   

    # else   

    # define EncryptFile EncryptFileA   

    # endif//! UNICODE     

    Якщо макрос UNICODE визначений, ви будете використовувати EncryptFileW, в іншому випадку - EncryptFileA. Так можна змінювати використовувану версію API-функцій. Залишилося навчитися регулювати тип переданої рядка. Це теж нескладно, достатньо користуватися типом TCHAR при оголошенні строкових і символьних змінних і укладати відповідні константи всередину макросу TEXT. І TCHAR, і TEXT визначені в tchar.h. Крім них, у цьому файлі визначені макроси для функцій стандартної бібліотеки С. Наприклад, макрос _tscanf розгортається або як wscanf, або як scanf, залежно від макросу UNICODE.

    При послідовному вживанні TCHAR, TEXT, _tscanf, .. можна простим зміною налаштувань перемикатися між ANSI і Unicode версіями проекту. Навряд чи ви будете часто користуватися такою можливістю, але те, що вона є - добре.        

    ПРИМІТКА   

    Ніхто не примушує вас використовувати   одну й ту ж кодування скрізь, досить просто бути послідовним.   Наприклад, модуль, що здійснює запис в лог-файл, може явно викликати   ANSI-функції (із суфіксом «A») і передавати їм char-рядка. З таким модулем   можна працювати, але треба пам'ятати, що його функцій не варто передавати   TCHAR-рядка. Інакше в ANSI-версії проекту це буде працювати, а в   Unicode-версії навіть не скомпіліруется. В основній частині служби   краще використовувати Unicode.     

    Дрібниці

    Тут зібрані факти, які корисно знати, але не необхідно.

    Служба не обов'язково є консольним додатком.

    У параметрі ImagePath ключа HKLMSystemCurrentControlSetServicesімя_служби можна задати командний рядок (можна навіть «/ uninstall»), але, на мою думку, цією можливістю краще не користуватися.

    Починаючи з Windows 2000 в параметрі Description ключа HKLMSystemCurrentControl SetServicesімя_служби можна задати опис служби. Воно відображається Services у стовпці «Description». Для встановлення цього параметра можна скористатися RegSetValueEx або ChangeServiceConfig2. Краще користуватися ChangeServiceConfig2, але простіше RegSetValueEx ...

    Судячи з усього, поки служба не викличе StartServiceCtrlDispatcher, SCM не може запустити наступну. Це ще один причина не поміщати ініціалізацію в main/WinMain.

    Після виклику StartServiceCtrlDispatcher основний потік додатки не простоює. Як мінімум, він виконує обробники повідомлень всіх служб exe-файлу. Тому «задіяно» не три потоки, а два.

    Коли функція MessageBox викликається з прапором MB_SERVICE_NOTIFICATION або MD_SERVICE_DEFAULT_DESKTOP_ONLY, у розділ Event Log'а System додається запис. Джерело - «Application Popup», всередині -- вміст повідомлення. Час створення запису відповідає часу виклику функції MessageBox, а не часу відображення повідомлення.

    Повідомлення можуть приходити абсолютно безсистемно. Те є, наприклад, не дивлячись на те, що ваша служба не стоїть на паузі, користувач може (утилітою net.exe або який-небудь своєї) надіслати їй повідомлення SERVICE_CONTROL_CONTINUE. Якщо в результаті ваша служба впаде, він буде дуже радий, але поваги до вас у нього не додасться.

    Функція CreateProcessW має одну особливість - її другий параметр має тип LPWSTR, а не LPCWSTR, причому, якщо цей параметр буде покажчиком на константу, проізойдет виняток. Незважаючи на це, в функцію CreateProcessA можна спокійно передавати покажчик на константу, так як при перетворенні з ANSI в Unicode вона виділяє буфер і передає CreateProcessW покажчик на нього.

    Код

    Як прикладу я написав невеличку службу, конфігуратор і файл повідомлень. Служба майже повністю складається зі стандартної (для мене) обгортки навколо робочого потоку і може використовуватися як заготовка. Короткий опис структури проекту:        

    Файл         

    Опис             

    Stddef.h         

    Крім традиційного   включення windows.h, містить оголошення наступних макросів: ServiceName --   «Внутрішнє» ім'я служби; DisplayName - «коротке» ім'я служби; EventSource   - Ім'я джерела повідомлень; MsgFileName - шлях до файлу повідомлень з кореня   служби.             

    main.cpp         

    Містить функцію main --   точку входу програми. Main перевіряє командний рядок, і залежно від   її вмісту виконує наступні дії:/install - намагається   інсталювати службу;/uninstall - намагається видалити службу; щось інше --   виводить довідкове повідомлення. Якщо в командному рядку нічого немає,   імовірно додаток запущено SCM-му. У цьому випадку main викликає   функцію для виконання якоїсь глобальної ініціалізації, викликає   StartServiceCtrlDispatcher, після повернення управління викликає функцію для   виконання глобальної очищення.             

    Cmdline.h, cmdline.cpp         

    Функції, що викликаються при   обробці командного рядка. Це установка/видалення служби, виведення довідкового   повідомлення.             

    Stdfunc.h, stdfunc.cpp         

    «Стандартні» функції   служби. ServiceMain, ServiceHandler та функції, що посилають SCM повідомлення типу   «Процес йде». Назовні виставляються ServiceMain (вказівник на неї передається   в StartServiceCtrlDispatcher) і FatalError, що використовується для інформування   SCM про раптове (тобто не викликаний повідомленням SERVICE_CONTROL_SHUTDOWN або   SERVICE_CONTROL_STOP) завершення роботи служби.             

    Report.h, report.cpp         

    Інтерфейс до Event Log-у.             

    Parameters.h,   parameters.cpp         

    Читає з реєстру параметри   служби.             

    Work.h, work.cpp         

    Робоча частина служби.   Має функції: GlobalInit - глобальна ініціалізація; GlobalEnd --   глобальна очищення; Init - ініціалізація конкретної служби; Run - функція,   виконує основну роботу; Stop, Pause, Continue, ParametersChanged --   викликаються з ServiceHandler під час отримання відповідного повідомлення від SCM.     

    Щоб створити свою службу, використовуючи цей шаблон, потрібно внести наступні зміни:        

    Файл         

    Зміна, коментарі             

    Stddef.h         

    Змінити значення макросів   ServiceName і т.п.             

    Cmdline.cpp         

    Якщо під час інсталяції/видалення   необхідно виконати якісь специфічні дії (записати щось у   реєстру, і т.п.), потрібно реалізувати ці самі дії.             

    Stdfunc.cpp         

    У цей файл вносяться   зміни, якщо служба повинна реагувати на якісь повідомлення від SCM. У цьому   разі зміни незначні: у функції ServiceHandler потрібно реалізувати   відповідні обробники, а

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status