Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 18 Programmiertechniken
Pfeil 18.1 Drag
Pfeil 18.1.1 Ablauf der Operation
Pfeil 18.1.2 Start der Operation
Pfeil 18.1.3 Ereignisse des Empfängers
Pfeil 18.1.4 Programmbeispiele
Pfeil 18.2 API-Aufrufe mit PInvoke
Pfeil 18.2.1 Das Attribut DllImport
Pfeil 18.2.2 Datentypen von API-Funktionen
Pfeil 18.3 Windows-Dienstanwendungen entwickeln
Pfeil 18.3.1 »Windows-Dienst«-Projekte in der Entwicklungsumgebung
Pfeil 18.3.2 Methoden eines Dienstes
Pfeil 18.3.3 Eigenschaften eines Dienstes
Pfeil 18.3.4 Installation eines Windows-Dienstes
Pfeil 18.3.5 Das Beispielprogramm FileWatchService
Pfeil 18.3.6 Dienste mittels Programmcode steuern
Pfeil 18.4 Die Zwischenablage
Pfeil 18.4.1 Speichern und Abrufen von Daten
Pfeil 18.4.2 Weitere Zugriffsmethoden
Pfeil 18.4.3 Mehrere Datenformate gleichzeitig
Pfeil 18.4.4 Eigene Datenformate schreiben
Pfeil 18.4.5 Leeren der Zwischenablage
Pfeil 18.4.6 Beispielprogramm Menü »Bearbeiten«


Rheinwerk Computing - Zum Seitenanfang

18.3 Windows-Dienstanwendungen entwickeln Zur nächsten ÜberschriftZur vorigen Überschrift

Windows-Dienste sind Anwendungen, die im Hintergrund ohne Benutzeroberfläche starten, oft automatisch, wenn der Computer gebootet wird. Sie werden vom Anwender nicht wahrgenommen und führen kontinuierlich Aufgaben aus, zum Beispiel die zur Überwachung.

Die Verwaltung aller Dienste wird von einer Komponente namens Service Control Manager (SCM) übernommen. Die Dienste eines Rechners werden in der Microsoft Management Console (MMC) angezeigt, die Sie sich über SystemsteuerungVerwaltungDienste anzeigen lassen können. Eine weitere Möglichkeit bietet das Visual Studio 2008 mit dem in Abbildung 18.1 gezeigten Server-Explorer, der standardmäßig am linken Rand der IDE angeordnet ist und nicht nur bei der Entwicklung von Windows-Diensten eine Erleichterung darstellt, weil nicht immer zwischen verschiedenen Fenstern gewechselt werden muss.

Abbildung 18.1 Der »Server-Explorer« von Visual Studio 2008

Ein installierter Dienst trägt sich in die Registrierungsdatenbank ein. Wird das System hochgefahren, werden die installierten Dienste geladen und stehen danach dem Service Control Manager zur Verfügung. Windows-Dienste haben ein besonderes Merkmal: Einen Dienst zu laden bedeutet nicht, dass er gleichzeitig auch gestartet wird und seine Aufgabe ausführt.

Das Startverhalten ist eine Eigenschaft, die jedem Dienst eigen ist und drei Konfigurationswerte annehmen kann:

  • manuell
  • automatisch
  • deaktiviert

Ein automatisch gestarteter Dienst ist von Anfang an aktiv. Ein manuell zu startender Dienst kann entweder über seine Eigenschaften im Dienste-Dialog oder programmiertechnisch aus einer anderen Anwendung heraus gestartet werden. Deaktivierte Dienste lassen sich weder vom Benutzer noch von einem anderen Programm starten. Ein gestarteter Dienst kann während seiner Ausführungszeit angehalten oder beendet werden.

Ein Dienst, der beendet wird, wird gleichzeitig auch entladen. Demgegenüber verbleibt ein angehaltener Dienst im Speicher. Sowohl beendete als auch angehaltene Dienste können erneut gestartet werden. Allerdings muss ein beendeter Dienst dazu neu geladen werden.

Abbildung 18.2 Das Eigenschaftsfenster des Nachrichtendienstes

In Abbildung 18.2 ist der Eigenschaftsdialog des Nachrichtendienstes zu sehen, dessen standardmäßig automatisches Starten auf manuelles Starten umgestellt worden ist. Um dieses Dialogfenster zu öffnen, markieren Sie im Dienste-Dialog den Dienst, öffnen das Kontextmenü und wählen Eigenschaften. Der Eigenschaftsdialog dient auch administrativen Aufgaben, denn Sie können hier nicht nur das Startverhalten des Dienstes umstellen, sondern einen Dienst auch starten, beenden, anhalten oder fortsetzen. Dienste, die in der Lage sind, Startparameter entgegenzunehmen, zeigen ein aktiviertes Eingabefeld am unteren Rand der Registerkarte Allgemein. Ausschließlich zu lesen ist die Angabe der Datei, die den Dienst bereitstellt, hier services.exe.


Rheinwerk Computing - Zum Seitenanfang

18.3.1 »Windows-Dienst«-Projekte in der Entwicklungsumgebung Zur nächsten ÜberschriftZur vorigen Überschrift

Nach dieser Einführung in die Welt der Windows-Dienste wollen wir nun sehen, wie wir einen Dienst mit dem Visual Studio programmieren. Dazu wählen Sie zunächst die Projektvorlage Windows-Dienst. Als Projektbezeichner wird der Name FileWatchService vergeben.

Wir sollten zunächst einen Blick auf das Grundgerüst des automatisch erzeugten Programmcodes werfen, der sich von dem einer Konsolen- oder Windows-Anwendung in wesentlichen Punkten unterscheidet. Das Listing enthält weder Attribute noch Kommentare, die automatisch generiert wurden.


'...\Programmiertechniken\FileWatchService\Service1.Designer.vb

Imports System.ServiceProcess

Partial Class Service1 : Inherits System.ServiceProcess.ServiceBase

    Protected Overrides Sub Dispose(ByVal disposing As Boolean) 
      ' components.Dispose() 
    End Sub

    Shared Sub Main() 
        Dim ServicesToRun() As System.ServiceProcess.ServiceBase 
        ServicesToRun = New System.ServiceProcess.ServiceBase() _ 
          {New Service1()} 
        System.ServiceProcess.ServiceBase.Run(ServicesToRun) 
    End Sub

    Private components As System.ComponentModel.IContainer

    Private Sub InitializeComponent() 
        components = New System.ComponentModel.Container() 
        Me.ServiceName = "Service1" 
    End Sub

End Class

'...\Programmiertechniken\FileWatchService\Service1.vb

Public Class Service1 
    Protected Overrides Sub OnStart(ByVal args() As String) 
    End Sub

    Protected Overrides Sub OnStop() 
    End Sub 
End Class

Ohne Umbenennung ist der Klassenname Service1. Die Klasse ist von ServiceBase abgeleitet, die die Basisklasse der Windows-Dienste ist und zum Namensraum System.ServiceProcess gehört. Wie jedes andere Programm wird auch ein Dienst durch den Aufruf der Methode Main gestartet. Darin wird zuerst ein Array vom Typ ServiceBase deklariert und diesem die Referenz auf das Dienst-Objekt vom Typ Service1 übergeben. Damit ist es auch möglich, innerhalb einer Windows-Dienst-Anwendung mehrere Dienste gleichzeitig bereitzustellen, die alle von ServiceBase abgeleitet sind. Die Initialisierung des Arrays muss dann nur durch die entsprechenden Objektreferenzen ergänzt werden. Im letzten Schritt wird die statische Methode Run der Klasse ServiceBase aufgerufen, und alle Dienste werden geladen. Ist die Methode ausgeführt, übernimmt der Service Control Manager die Steuerung der Dienste.

Zwei geschützte Methoden der Basisklasse werden bereits überschrieben bereitgestellt: OnStart und OnStop. Sie werden ausgeführt, wenn der Dienst gestartet beziehungsweise beendet wird. OnStart enthält damit die Funktionalität des Dienstes, in OnStop implementieren Sie den Code, der bei Dienstende ausgeführt werden soll.

Bei OnStart und OnStop handelt es sich genau genommen um Ereignisse, die aber von der Basisklasse nicht direkt veröffentlicht werden.


Hinweis
Wenn OnStart länger als 30 Sekunden braucht, bricht der Service Control Manager (SCM) den Start ab und schreibt eine Fehlermeldung in das Ereignisprotokoll. Alles, was länger dauert, muss in einem eigenen Thread laufen, der von OnStart gestartet wird. Für sich wiederholende Operationen können Sie das Timer-Steuerelement verwenden.


Angestoßen wird OnStart durch den Service Control Manager. Beim Booten des Systems ist das der Fall, wenn im Eigenschaftsdialog des Dienstes unter Starttyp Automatisch eingestellt ist (siehe Abbildung 18.2, »Das Eigenschaftsfenster des Nachrichtendienstes«). Mit Manuell wird der Dienst beim Booten zwar in den Speicher geladen, wartet aber auf ein äußeres Signal zum Start. Das kann durch Klicken auf die Schaltfläche Starten im Dialog erfolgen oder durch Programmcode. Analog verhält sich auch die Methode OnStop. Sie erhält einen Anstoß von außen, wenn der Dienst beendet wird – entweder durch Klicken auf die Schaltfläche Beenden oder mittels Programmcode.

Wird ein Dienst gestartet, können ihm Startparameter übergeben werden, die im Dienste-Dialog im untersten Eingabefeld eingetragen werden (siehe Abbildung 18.2, »Das Eigenschaftsfenster des Nachrichtendienstes«). An die OnStart-Methode werden diese Parameter als String-Array übergeben, und sie dienen zur Kontrolle des Laufzeitverhaltens des Dienstes.


Protected Overridable Sub OnStart(ByVal args As String())

Die Methode OnStop hingegen ist parameterlos, ebenso wie alle anderen OnXxx-Methoden, die im folgenden Abschnitt beschrieben werden.


Rheinwerk Computing - Zum Seitenanfang

18.3.2 Methoden eines Dienstes Zur nächsten ÜberschriftZur vorigen Überschrift

Beim Beenden eines Dienstes wird automatisch die Methode OnStop aufgerufen. Wird der Dienst nur angehalten, ist es OnPause. Ein unterbrochener Dienst kann natürlich auch wieder gestartet werden. Dann wird automatisch die Methode OnContinue aufgerufen. Bei Beendigung des Systems kommen zwei weitere Methoden ins Spiel. Während das System herunterfährt, wird OnShutDown aufgerufen, während OnPowerEvent bei Änderung der Stromversorgung (StandBy, Aufwachen, Netzkabel ein-/ausgesteckt) dran ist. Tabelle 18.6 fasst die Methoden zusammen.


Tabelle 18.6 Geschützte Methoden der Klasse »ServiceBase«

Methode Ausführung

OnContinue

Wenn der Dienst nach dem Anhalten wieder fortgesetzt wird

OnPause

Wenn der Dienst angehalten wird

OnPowerEvent

Wenn sich die Stromversorgung ändert (Standby oder Netzkabel)

OnStart

Wenn der Dienst gestartet wird

OnStop

Wenn der Dienst beendet wird

OnShutDowm

Wenn das System heruntergefahren wird


Damit steht Ihnen eine Reihe von Methoden zur Verfügung, um das Verhalten eines Dienstes in mannigfaltigen Situationen festzulegen. Welche Methoden überschrieben werden müssen, hängt von der Arbeitsweise des Dienstes ab. Mit Sicherheit werden Sie aber immer OnStart implementieren, damit der Dienst überhaupt etwas macht.


Rheinwerk Computing - Zum Seitenanfang

18.3.3 Eigenschaften eines Dienstes Zur nächsten ÜberschriftZur vorigen Überschrift

Jeder Dienst wird durch Signale des SCM gesteuert. Beim Starten ist es ein Signal, das die Ausführung von OnStart im Dienst nach sich zieht, beim Beenden ein Signal, infolge dessen OnStop aufgerufen wird. Analog gilt das auch für die anderen Methoden. Auf einem anderen Blatt steht jedoch, ob der Dienst überhaupt in der Lage ist, das Signal zu empfangen und an die passende Methode weiterzuleiten. Diese Festlegung erfolgt in den Eigenschaften der von ServiceBase abgeleiteten Klasse (siehe Tabelle 18.7). Um sich die Eigenschaften im Eigenschaftsfenster anzuzeigen zu lassen, müssen Sie die Dienstklassendatei in der Designeransicht öffnen.


Tabelle 18.7 Eigenschaften der Klasse »ServiceBase«

Eigenschaft Beschreibung

AutoLog

Gibt an, ob das Starten, Beenden, Anhalten und Fortsetzen im Ereignisprotokoll Anwendung aufgezeichnet wird.

CanHandlePowerEvent

Der Dienst reagiert auf Änderung der Stromversorgung.

CanHandleSessionChangeEvent

Der Dienst reagiert auf Wechsel einer Terminal-Session.

CanPauseAndContinue

Der Dienst kann angehalten und wieder fortgesetzt werden.

CanShutDown

Der Dienst wird beim Herunterfahren des Systems informiert.

CanStop

Gibt an, ob der Dienst nach dem Starten beendet werden kann.

EventLog

Zugriff auf Systemprotokolle

ExitCode

Beendigungscode des Dienstes

ServiceName

Bezeichnung des Dienstes


Insgesamt vier Eigenschaften legen die Empfangsbereitschaft eines Dienstes fest:

  • CanHandlePowerEvent
  • CanPauseAndContinue
  • CanShutDown
  • CanStop

Bis auf CanStop sind alle genannten Eigenschaften mit False vorinitialisiert. Die Konsequenzen lassen sich am besten erkennen, wenn Sie sich den Eigenschaftsdialog eines Dienstes ansehen. In Abbildung 18.2, »Das Eigenschaftsfenster des Nachrichtendienstes«, sind die beiden Schaltflächen Anhalten und Fortsetzen nur dann aktiv, wenn der Dienst gestartet und die Eigenschaft CanPauseAndContinue=True festgelegt ist. Auch wenn das nur selten angewendet wird, ist es möglich, einen Dienst zu starten, ohne ihn jemals beenden zu können. Dazu muss CanStop=False gesetzt werden.

Belassen Sie den Standardwert True von AutoLog, schreibt ein Dienst seine Zustandsänderungen in das in Abbildung 18.3 gezeigte Anwendungsereignisprotokoll des Systems.

Abbildung 18.3 Anwendungsereignisprotokoll

Wie Abbildung 18.3 zeigt, wird im Protokoll in der Spalte Quelle der Bezeichner ausgegeben, der durch die Eigenschaft ServiceName der Dienstklasse angegeben ist. Ein Doppelklick auf einen Eintrag öffnet das Eigenschaftsfenster des Ereignisses, in dem über die allgemeinen Angaben zum Ereignis hinaus auch noch eine Beschreibung angezeigt wird. Soll sie nicht leer sein, müssen Sie im Programmcode mit der Methode WriteEntry der Eigenschaft EventLog selbst in das Protokoll schreiben. Erlaubte Ziele der Ausgabe werden durch EventLog bestimmt. Eine Erweiterung ist nicht möglich, EventLog ist schreibgeschützt. Das folgende Codefragment nutzt die einfachste Überladung der Methode.

Protected Overrides Sub OnStart(args As String()) 
  Me.EventLog.WriteEntry("DiskWatcher gestartet") 
  ' weitere Anweisungen 
End Sub

Die Ausgabe des Beispiels sehen Sie in Abbildung 18.4. Ein lauffähiges Beispiel, das Write-Entry benutzt, folgt in Abschnitt 18.3.5, »Das Beispielprogramm FileWatchService«.

Abbildung 18.4 Beschreibungstext im Ereignisprotokoll


Hinweis
EventLog kann auch in andere Systemprotokolldateien schreiben. In der Toolbox wird Ihnen zur Erleichterung Ihrer Arbeit für diese Klasse auch ein Control angeboten.



Rheinwerk Computing - Zum Seitenanfang

18.3.4 Installation eines Windows-Dienstes Zur nächsten ÜberschriftZur vorigen Überschrift

Damit haben wir die Beschreibung der Definition eines Windows-Dienstes abgeschlossen, und es bleibt nur noch, den Dienst zu installieren. Damit der SCM den Dienst auch finden kann, muss er in die Registrierungsdatenbank eingetragen werden. Obwohl diese Aufgabe im ersten Moment schwierig erscheint, unterstützt das Visual Studio uns in dieser Hinsicht geradezu vorbildlich, denn dazu sind nur wenige Klicks und Einträge erforderlich.

Öffnen Sie die Dienstklasse in der Designeransicht, finden Sie unten im Eigenschaftsfenster den Link Installer hinzufügen. Klicken Sie auf den Link, wird eine neue Quellcodedatei hinzugefügt, die zwei Installationsobjekte bereitstellt: Das erste ist vom Typ ServiceProcessInstaller und übernimmt die Installation der Installationsdatei des Dienstes auf dem System. Das zweite ist vom Typ ServiceInstaller trägt die Dienstkomponente in die Registry unter

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services

ein, In beiden Objekten sind wenige Eigenschaften bis zur endgültigen Fertigstellung der installationsfähigen Windows-Dienstanwendung festzulegen. Im ServiceProcessInstaller-Objekt ist das die Eigenschaft Account, mit dem die Benutzerrechte des Dienstes bestimmt werden. An den meisten Fällen ist LocalSystem am besten geeignet.


Hinweis
Weitere Informationen zu den drei weiteren Alternativen, um den Sicherheitskontext eines Dienstes festzulegen, entnehmen Sie bitte der Online-Dokumentation.


Im ServiceInstaller-Objekt sollten wir noch drei Eigenschaften setzen. Zuerst wird mit StartType festgelegt, wie sich der Dienst nach dem Laden verhalten soll. Bekanntlich kann ein Dienst automatisch oder manuell gestartet werden. Mit der dritten Möglichkeit, der Deaktivierung, kann er weder vom Benutzer noch durch ein Programm gestartet werden.

Einen weniger gravierenden Einfluss haben die Eigenschaften DisplayName und ServiceName. Trotzdem sollten auch diese Werte sorgfältig vergeben werden. Der Text, den Sie unter DisplayName eintragen, wird im Dialog Dienste angezeigt, und der Wert von ServiceName wird als Schlüssel in der Registry benutzt.

Damit sind alle vorbereitenden Arbeiten erledigt, und der Installer ist konfiguriert. Das Projekt muss anschließend nur noch kompiliert werden. Das Ergebnis ist eine EXE-Datei, die Installationsdatei des Windows-Dienstes.

Jetzt kommt es zur Installation auf dem Zielsystem. Dazu bieten sich zwei Alternativen an:

  • eine Installationsroutine mit einem Weitergabeprojekt
  • das Tool InstallUtil.exe

An dieser Stelle wollen wir die zweite Möglichkeit nutzen. In Abschnitt 8.4, »Weitergabe mit MS-Installer«, haben wir uns mit Weitergabeprojekten beschäftigt. Hier wollen wir das Tool InstallUtil.exe benutzen, das im Verzeichnis

\Windows\Microsoft.NET\Framework\v<Versionsnummer>

zu finden ist. Zum Installieren rufen Sie das Tool an der Konsole auf und übergeben als Parameter die Installationsdatei des Windows-Dienstes, also beispielsweise:

InstallUtil FileWatchService.exe

Ähnlich einfach kann ein Dienst auch deinstalliert werden. Dazu wird der Aufruf des Tools um den Optionsschalter /u ergänzt:

InstallUtil /u FileWatchService.exe

Wenn die Installation erfolgreich abgelaufen ist, werden Sie den selbst geschriebenen Windows-Dienst nun im Dienste-Dialog zu sehen bekommen.


Hinweis
Gegebenenfalls müssen Sie die Vertrauenswürdigkeit anpassen, siehe zum Beispiel Abschnitt 2.3.2, »Start und Test«. Eine Installation aus dem Netzwerk heraus kann scheitern.



Rheinwerk Computing - Zum Seitenanfang

18.3.5 Das Beispielprogramm FileWatchService Zur nächsten ÜberschriftZur vorigen Überschrift

Nun wollen wir unsere Kenntnisse einsetzen und einen Windows-Dienst vollständig implementieren. Dieser Dienst, dessen Name FileWatchService lautet, soll in der Lage sein, alle Änderungen am Dateisystem in einer Datei zu protokollieren, beispielsweise das Löschen, Ändern und Hinzufügen einer Datei. Außerdem soll der Dienst auch angehalten und erneut gestartet werden können.

Weil der Dienst permanent auf Änderungsmitteilungen lauscht und die OnStart-Methode das Zeitlimit von 30 Sekunden nicht überschreiten darf, wird die gesamte Funktionalität in der separaten Klasse Worker implementiert und in einem eigenen Thread ausgeführt.


'...\Programmiertechniken\FileWatchService\Service1.vb

Imports System.Threading 
Public Class Service1

  Private th As Thread 
  Private path As String = ""

  Protected Overrides Sub OnStart(ByVal args() As String) 
    If args.Length <> 0 Then Me.path = args(0) 
    Dim w As New Worker(Me.path) 
    th = New Thread(New ThreadStart(AddressOf w.StartWorker)) 
    th.Start() 
    Me.EventLog.WriteEntry("FileWatchService wurde erfolgreich gestartet") 
  End Sub

  Protected Overrides Sub OnStop() 
    th.Abort() 
    th = Nothing 
    Me.EventLog.WriteEntry("FileWatchService wurde beendet") 
  End Sub

  Protected Overrides Sub OnPause() 
    th.Suspend() ' der Einfachheit halber; Problem, wenn Thread aktiv 
    Me.EventLog.WriteEntry("FileWatchService wurde angehalten") 
  End Sub

  Protected Overrides Sub OnContinue() 
    th.Resume() 
    Me.EventLog.WriteEntry("FileWatchService wurde fortgesetzt") 
  End Sub

End Class

In OnStart wird zuerst ein Objekt vom Typ Worker erzeugt. Dem Konstruktor der Klasse wird das zu protokollierende Verzeichnis übergeben. In diesem und allen untergeordneten Verzeichnissen sollen die Änderungen protokolliert werden. Im Eigenschaftsdialog des Dienstes kann der Anwender das Verzeichnis im Eingabefeld Startparameter selbst bestimmen, ansonsten werden sämtliche Änderungen im Laufwerk C:\ festgehalten. Nach der Initialisierung des Arbeitsthreads und dem Aufruf der Startmethode schreibt OnStart nur noch eine Mitteilung in das Anwendungsereignisprotokoll.

In den anderen Methoden wird nur noch das Verhalten des Threads gesteuert: In OnPause wird der Thread in den Wartezustand versetzt, in OnContinue wird er bereitgeschaltet. Das Stoppen des Dienstes durch Aufruf der Methode OnStop zerstört den Thread.

Sehen wir uns nun an, wie die Klasse Worker konstruiert ist:


'...\Programmiertechniken\FileWatchService\Worker.vb

Public Class Worker 
  Private protocolFile As String = IO.Path.Combine( _ 
    IO.Path.GetDirectoryName( _ 
      Process.GetCurrentProcess().MainModule.FileName), "FileWatchLog.log") 
  Private path As String = "c:\\" 
  Public Sub New(ByVal path As String) 
    If Not String.IsNullOrEmpty(path) Then Me.path = path 
  End Sub

  ' Startmethode des Threads 
  Public Sub StartWorker() 
    Dim fsw As New IO.FileSystemWatcher(Me.path) 
    fsw.IncludeSubdirectories = True 
    fsw.EnableRaisingEvents = True 
    AddHandler fsw.Changed, AddressOf ChangeFile 
    AddHandler fsw.Deleted, AddressOf DeleteFile 
    AddHandler fsw.Created, AddressOf CreateFile 
    AddHandler fsw.Renamed, AddressOf RenameFile 
    Try 
      ' in der Schleife wird auf etwaige Änderungen im Dateisystem gewartet 
      While True 
        fsw.WaitForChanged(IO.WatcherChangeTypes.All) 
      End While 
    Catch : End Try 
    fsw.EnableRaisingEvents = False 
  End Sub

  ' Änderungen der Protokolldatei nicht aufzeichnen 
  Private Function IsLog(ByVal path As String) As Boolean 
    Dim fi As New IO.FileInfo(path) 
    Return fi.Name.ToLower().Equals("FileWatchLog.log".ToLower()) 
  End Function

  ' Datei wurde umbenannt 
  Private Sub RenameFile(sender As Object, e As IO.RenamedEventArgs) 
    Dim entry As String = "[" & Now.ToShortTimeString() & "] " & _ 
      String.Format("Pfad: {0}; Datei umbenannt: von {1} nach {2}", _ 
        IO.Path.GetDirectoryName(e.FullPath), e.OldName, e.Name) 
    WriteToLogFile(entry) 
  End Sub

  ' Datei wurde gelöscht 
  Private Sub DeleteFile(sender As Object, e As IO.FileSystemEventArgs) 
    Dim entry As String = "[" & Now.ToShortTimeString() & "] " & _ 
      String.Format("Pfad: {0}; Datei gelöscht: {1}", _ 
        IO.Path.GetDirectoryName(e.FullPath), e.Name) 
    WriteToLogFile(entry) 
  End Sub

  ' Datei wurde neu erzeugt 
  Private Sub CreateFile(sender As Object, e As IO.FileSystemEventArgs) 
    Dim entry As String = "[" & Now.ToShortTimeString() & "] " & _ 
      String.Format("Pfad: {0}; Datei erstellt: {1}", _ 
        IO.Path.GetDirectoryName(e.FullPath), e.Name) 
    WriteToLogFile(entry) 
  End Sub

  ' Datei wurde geändert 
  Private Sub ChangeFile(sender As Object, e As IO.FileSystemEventArgs) 
    If IsLog(e.FullPath) Then Return 
    Dim entry As String = "[" & Now.ToShortTimeString() & "] " & _ 
      String.Format("Pfad: {0}; Datei geändert: {1}", _ 
        IO.Path.GetDirectoryName(e.FullPath), e.Name) 
    WriteToLogFile(entry) 
  End Sub

  ' Logdatei lesen 
  Private Function ReadFromLogFile() As Specialized.StringCollection 
    Dim strCol As New Specialized.StringCollection() 
    Dim sr As New IO.StreamReader(Me.protocolFile) 
    Dim line As String = "" 
    Dim count As Integer = 1 
    line = sr.ReadLine() 
    While line IsNot Nothing AndAlso count < 999 
      strCol.Add(line) 
      count += 1 
      line = sr.ReadLine() 
    End While 
    sr.Close() 
    Return strCol 
  End Function

  ' Schreibt eine Änderung an den Anfang der Protokolldatei 
  Private Sub WriteToLogFile(ByVal entry As String) 
    Dim changedEntriesCol As Specialized.StringCollection 
    If IO.File.Exists(Me.protocolFile) Then 
      changedEntriesCol = ReadFromLogFile() 
    Else 
      changedEntriesCol = New Specialized.StringCollection() 
    End If 
    ' neuen Eintrag an erster Stelle einfügen 
    changedEntriesCol.Insert(0, entry) 
    ' Einträge in eine Log-Datei, neue erzeugen / alte überschreiben 
    Dim fs As New IO.FileStream(Me.protocolFile, IO.FileMode.Create) 
    Dim sw As New IO.StreamWriter(fs) 
    For Each str As String In changedEntriesCol 
      sw.WriteLine(str) 
    Next 
    sw.Close() 
  End Sub 
End Class

Tipp
Einen Windows-Dienst müssen Sie auch zum Testen installieren. Um aus dem Test keine Installations- bzw. Deinstallationsorgie zu machen, sollten Sie dienstunabhängige Funktionalität, hier die Klasse Worker, in einem normalen Projekt zuvor ausgiebig testen.


Der gesamten Funktionalität des Dienstes liegt eine Klasse aus dem Namensraum System.IO zugrunde: FileSystemWatcher. Auch diese Klasse wird Ihnen als Steuerelement in der Toolbox zur Verfügung gestellt. Objekte dieses Typs lösen Ereignisse aus, wenn eine Datei oder ein Verzeichnis verändert wird. Das zu überwachende Verzeichnis wird dem Konstruktor übergeben. Die ausgelösten Ereignisse heißen Changed, Created, Deleted und Renamed.

In der bei Dienststart aufgerufenen OnStart-Methode wird zuerst ein Objekt vom Typ der Klasse Worker erzeugt. Es wird das zu überwachende Verzeichnis übergeben und im Feld path gespeichert. In der Startmethode des Threads namens StartWorker wird das FileSystem-Watcher-Objekt erzeugt. Mit der Eigenschaft IncludeSubdirectories schließen wir auch alle Unterverzeichnisse in die Überwachung ein, und mit EnableRaisingEvents wird die Ereignisauslösung aktiviert. Nach dem Binden der Handler an die Ereignisse kommt der Kern des gesamten Dienstes: der Aufruf der Methode WaitForChanged des FileSystemWatcher-Objekts. Der Methodenaufruf wird erst nach dem Eintritt einer Änderung beendet und löst die entsprechenden Ereignisse aus. Damit nicht nur eine Änderung erfasst wird, packen wir den Aufruf in eine Endlosschleife. Sie wird bei Beendigung des Dienstes durch die Ausnahme ThreadAbortException beendet, die durch den Aufruf von Abort in der Methode OnStop ausgelöst wird.

Aufgezeichnet werden die Veränderungen in einer Protokolldatei, die sich im gleichen Verzeichnis wie die ausführbare Datei des Dienstes befindet. In allen vier Ereignishandlern werden entsprechende Textinformationen über den Aufruf der benutzerdefinierten Methode WriteToLogFile in die Protokolldatei geschrieben. Allerdings müssen wir bei der Überwachung von Änderungen vorsichtig sein. Befindet sich nämlich die Protokolldatei im überwachten Verzeichnis, wird sie natürlich selbst auch verändert und löst damit das Change-Ereignis erneut aus – der klassische Fall einer Endlosschleife. Daher wird im Ereignishandler ChangeFile zuerst geprüft, ob eine Änderung der Protokolldatei das Ereignis ausgelöst hat. In diesem Fall muss sich die Protokolldatei nicht selbst protokollieren.

Der in die Protokolldatei zu schreibende Eintrag wird der Methode WriteToLogFile übergeben. Sie überprüft zuerst, ob die Protokolldatei bereits existiert. Wenn ja, wird sie in der Methode ReadFromLogFile eingelesen. Jeder Eintrag in der Protokolldatei nimmt eine Zeile in Anspruch. Damit die Datei nicht zu groß wird, ist die Maximalgröße im Programmcode auf 1000 Einträge beschränkt. Das erreichen wir durch Einlesen von maximal 999 Einträgen. Zur einfacheren Verwaltung wird jeder Dateieintrag einem StringCollection-Objekt übergeben. Der neue Eintrag wird danach mit Insert der Auflistung vorangestellt. Damit ist garantiert, dass ein Anwender nach dem Öffnen der Protokolldatei die aktuellsten Einträge immer oben findet.


Rheinwerk Computing - Zum Seitenanfang

18.3.6 Dienste mittels Programmcode steuern topZur vorigen Überschrift

In den letzten Abschnitten haben Sie erfahren, wie ein Windows-Dienst entwickelt wird, der unbemerkt vom Anwender im Hintergrund seine Aufgaben erledigt. Den Status eines Dienstes können Sie mit der SCM-Komponente (Service Control Manager) kontrollieren: Mit SCM kann ein Dienst gestartet, beendet und – wenn ein Dienst auch diese Möglichkeit zulässt – auch angehalten und später wieder fortgesetzt werden.

Manchmal kommt es vor, dass ein Dienst aus einer Anwendung heraus gesteuert werden soll, ähnlich wie es der SCM in der Microsoft Management Console (MMC) auch macht. Zu diesem Zweck stellt das .NET Framework eine eigene Klasse bereit: ServiceController. Diese Klasse hat mehrere Aufgaben:

  • Eine statische Methode liefert die Referenzen installierter Dienste.
  • Ihre Instanzeigenschaften beschreiben einen Dienst, beispielsweise ob er angehalten oder beendet werden kann.
  • Alle installierten Gerätetreiber können abgerufen werden.

Um die Klasse ServiceController nutzen zu können, müssen Sie zuerst die Datei System. ServiceProcess.dll unter Verweise einbinden. Außerdem sollten Sie auch noch mit

Imports System.ServiceProcess

den Namensraum bekannt geben.

Installierte Windows-Dienste ermitteln

Mit der überladenen klassengebundenen Methode GetServices ermitteln Sie alle Dienste des lokalen oder sogar eines entfernten Rechners:


Public Shared Function GetServices() As ServiceController() 
Public Shared Function GetServices(machineName As String) _ 
  As ServiceController()

Der Rückgabewert ist ein Array vom Typ ServiceController. Jedes Element des Arrays beschreibt einen Dienst, unabhängig von seinem aktuellen Laufzustand.

Im Array sind die Gerätetreiberdienste nicht enthalten. Wollen Sie diese ermitteln, steht Ihnen mit GetDevices eine analoge klassengebundene Methode zur Verfügung:


Public Shared Function GetDevices() As ServiceController() 
Public Shared Function GetDevices(machineName As String) 
  As ServiceController()

Eigenschaften eines Dienstes abfragen

Gestartet werden kann jeder Dienst. Das Anhalten und Fortsetzen ist aber schon eine Option, die nicht jeder Dienst hat. Manche Dienste sind sogar von so elementarer Natur, dass sie sich noch nicht einmal beenden lassen. Andere Dienste wiederum werden beim Herunterfahren des Systems benachrichtigt und können darauf spezifisch reagieren.

Anwendungen mit der Fähigkeit, Dienste zu steuern, müssen deren Verhaltensweisen kennen und darüber hinaus auch noch den aktuellen Zustand ermitteln können. Die in Tabelle 18.8 gezeigten Eigenschaften der Klasse ServiceController liefern diese Informationen.


Tabelle 18.8 Eigenschaften von »ServiceController« (R = ReadOnly)

Eigenschaft Beschreibung

CanPauseAndContinue

Gibt an, ob ein Dienst angehalten und wieder fortgesetzt werden kann.

R

CanShutdown

Der Dienst wird beim Herunterfahren des Systems informiert.

R

CanStop

Gibt an, ob der Dienst beendet werden kann.

R

DependentServices

Dienste, die dieser Dienst bei seinem Ende stoppt

R

DisplayName

Der angezeigte Name des Dienstes (z. B. im SCM)

MachineName

Der Name des Rechners, auf dem der Dienst ausgeführt wird

ServiceHandle

Korrespondierende Datenstruktur im Betriebssystem

R

ServiceName

Der Name des Dienstes, unter dem er im SCM identifiziert wird

ServicesDependedOn

Von dem Dienst benötigte andere Dienste

R

ServiceType

Die Art des Dienstes (aufgelistet in der Enumeration ServiceType)

R

Status

Der Zustand des Dienstes

R


Die Eigenschaft Status, die den aktuellen Zustand eines Dienstes beschreibt, sehen wir uns noch etwas genauer an. Die anderen sind halbwegs selbsterklärend.


Public ReadOnly Property Status As ServiceControllerStatus

Der in Tabelle 18.9 gezeigte Typ ServiceControllerStatus ist eine Enumeration, deren Konstanten beschreiben, ob ein Dienst gestartet, angehalten oder beendet wurde bzw. ob er sich derzeit in einem Zustandswechsel befindet.


Tabelle 18.9 Die Enumeration »ServiceControllerStatus«

Konstante Beschreibung

ContinuePending

Es wird versucht, den angehaltenen Dienst fortzusetzen.

Paused

Der Dienst ist angehalten.

PausePending

Der Dienst wird angehalten.

Running

Der Dienst wird ausgeführt.

StartPending

Der Dienst wird gestartet.

Stopped

Der Dienst ist beendet.

StopPending

Der Dienst wird beendet.


Methoden eines Dienstes

Mit den Instanzmethoden Start, Stop, Pause und Continue eines ServiceController-Objekts wird der Zustand eines Dienstes geändert. Diese Methoden waren eigentlich auch zu erwarten, aber neben einer Reihe weiterer ragt noch eine andere Methode heraus: WaitForStatus.


Public Sub WaitForStatus(desired As ServiceControllerStatus) 
Public Sub WaitForStatus(desired As ServiceControllerStatus, _ 
                         timeout As TimeSpan)

Diese Methode unterbricht den weiteren Programmablauf so lange, bis der Dienst den im Parameter angegebenen Zustand aufweist. Die Methode ist besonders nützlich beim Start und Ende eines Dienstes, denn diese beiden Aktionen nehmen etwas Zeit in Anspruch. Die maximale Ausführungszeit kann in einem optionalen zweiten Parameter angegeben werden. Mit

myService.WaitForStatus(ServiceControllerStatus.Running, _ 
                        New TimeSpan(0, 0, 15))

geben Sie zum Beispiel an, dass der Start des unter myService referenzierten Dienstes maximal 15 Sekunden dauern darf. Bei Überschreitung dieser Zeit wird eine Ausnahme ausgelöst, die abgefangen werden muss.


Tipp
Eine zu kurz gewählte Zeitspanne verhindert eine erfolgreiche Ausführung auf langsameren Rechnern. Wenn Sie die Zeitspanne aus einer Konfigurationsdatei oder der Registry auslesen, kann der Benutzer den Wert gegebenenfalls anpassen.


In Tabelle 18.10 finden Sie noch einmal alle angesprochenen Methoden wieder.


Tabelle 18.10 Methoden der Klasse »ServiceController« (S = Shared)

Methode Beschreibung

Close

Gibt von der ServiceController-Instanz reservierte Ressourcen frei.

Continue

Setzt einen angehaltenen Dienst fort.

ExecuteCommand

Aufruf eines der in OnCustomCommand selbst definierten Befehle

GetDevices

Ruft ein ServiceController-Array aller Gerätetreiberdienste ab.

S

GetServices

ServiceController-Array aller Dienste ohne Gerätetreiberdienste

S

Pause

Hält einen laufenden Dienst an.

Refresh

Aktualisiert alle Eigenschaftswerte.

Start

Startet den Dienst.

Stop

Beendet den Dienst.

WaitForStatus

Wartet so lange mit der weiteren Ausführung des Programms, bis der Dienst einen bestimmten Zustand eingenommen hat.


Ein Beispielprogramm

Im folgenden Beispiel ListServices werden alle lokal installierten Dienste in einer Listbox angezeigt. Über vier Schaltflächen kann der jeweils ausgewählte Dienst gestartet, gestoppt, angehalten oder fortgesetzt werden (siehe Abbildung 18.5). Bitte achten Sie beim Testen darauf, dass Sie sich unter einem Windows-Benutzerkonto mit ausreichenden Rechten einloggen.

Abbildung 18.5 Benutzeroberfläche des Beispielprogramms »ListServices«

Die Listbox wird im Load-Ereignis der Form gefüllt. Angezeigt wird die Zeichenfolge, die von der Eigenschaft DisplayName des referenzierten Dienstes zurückgeliefert wird.


'...\Programmiertechniken\Dienste\ListServices.vb

Private Sub Laden(sender As Object, e As EventArgs) Handles MyBase.Load 
  For Each ctrl As ServiceController In ServiceController.GetServices() 
    Dienste.Items.Add(ctrl.DisplayName) 
  Next 
  Me.SetButtons() 
End Sub

Die Auswahl eines Dienstes löst das Ereignis SelectedIndexChanged der Listbox aus. Im Ereignishandler wird im ServiceController-Array zuerst der ausgewählte Dienst anhand des angezeigten Namens gesucht. Wird die Routine fündig, wird die Referenz auf den Dienst in der Variablen dienst festgehalten, die auf Klassenebene deklariert ist.

Private dienst As ServiceController

Private Sub Auswahl(sender As Object, e As EventArgs) _ 
Handles Dienste.SelectedIndexChanged 
  Me.dienst = Nothing 
  For Each ctrl As ServiceController In ServiceController.GetServices() 
    If Dienste.Text = ctrl.DisplayName Then 
      Me.dienst = ctrl 
      Me.SetButtons() 
      Return 
    End If 
  Next 
End Sub

Sobald der markierte Dienst im Array gefunden wird, können auch die vier Schaltflächen sowohl an den Zustand des Dienstes als auch an dessen Fähigkeiten angepasst werden. Ist der Dienst beispielsweise gestartet, muss die entsprechende Schaltfläche zum Starten deaktiviert angezeigt werden. Bei einem Dienst, der nicht angehalten werden kann, sind die beiden Schaltflächen Fortsetzen und Anhalten grundsätzlich deaktiviert. Die private Methode SetButtons setzt den Aktivierungszustand der Schaltflächen abhängig vom Zustand des Dienstes mit der Methode SetButtonStatus.

' Schaltflächen aktivieren/deaktivieren 
Private Sub SetButtons() 
  If dienst Is Nothing Then 
    SetButtonStatus(False, False, False, False) 
  ElseIf dienst.Status = ServiceControllerStatus.Stopped Then 
    SetButtonStatus(True, False, False, False) 
  ElseIf dienst.Status = ServiceControllerStatus.Running Then 
    If dienst.CanPauseAndContinue Then 
      SetButtonStatus(False, True, True, False) 
    Else 
      SetButtonStatus(False, True, False, False) 
    End If 
  ElseIf dienst.Status = ServiceControllerStatus.Paused Then 
    SetButtonStatus(False, True, False, True) 
  End If 
End Sub

Private Sub SetButtonStatus(ByVal st As Boolean, ByVal sp As Boolean, _ 
                            ByVal ht As Boolean, ByVal ft As Boolean) 
  Start.Enabled = st : Stopp.Enabled = sp 
  Anhalten.Enabled = ht : Fortsetzen.Enabled = ft 
End Sub

Kommen wir zum Schluss noch zur Implementierung des Programmcodes der Schaltflächen in der Methode Handlung. Um den Anwender über die möglicherweise länger andauernde Operation zu informieren, wird während der gesamten Ausführungszeit als Mauszeiger eine Sanduhr angezeigt. Der ausgewählte Dienst bestimmt die im Delegate gespeicherte Methode, die in der Methode Handlung aufgerufen wird. Der eingeleiteten Operation werden 15 Sekunden zugestanden, um den Dienst erfolgreich zu kontrollieren. Bei Zeitüberschreitung wird eine Ausnahme ausgelöst. Daher ist der Aufruf der WaitForStatus-Methode in einem Try-Block codiert. Nach Beendigung des Kontrolle wird erneut die Methode SetButtons aufgerufen, um den Aktivierungszustand der Schaltflächen an den möglicherweise neuen Dienstzustand anzupassen. Die vier Schaltflächen rufen Handlung dann mit den passenden Kontrollfunktionen und gewünschten Zuständen auf.

' Dienstkontrolle 
Private Delegate Sub aktion()

Private Sub Handlung(ByVal was As aktion, ByVal st As ServiceControllerStatus) 
  Me.Cursor = Cursors.WaitCursor 
  Try 
    was.Invoke() 
    dienst.WaitForStatus(st, New TimeSpan(0, 0, 15)) 
  Catch ex As Exception 
    MessageBox.Show(ex.Message, "Fehler") 
  Finally 
    Me.Cursor = Cursors.Default 
    Me.SetButtons() 
  End Try 
End Sub

Private Sub Starten(sender As Object, e As EventArgs) Handles Start.Click 
  Handlung(AddressOf dienst.Start, ServiceControllerStatus.Running) 
End Sub

Private Sub Weiter(sender As Object, e As EventArgs) Handles Fortsetzen.Click 
  Handlung(AddressOf dienst.Continue, ServiceControllerStatus.Running) 
End Sub

Private Sub Pause(sender As Object, e As EventArgs) Handles Anhalten.Click 
  Handlung(AddressOf dienst.Pause, ServiceControllerStatus.Paused) 
End Sub

Private Sub Ende(sender As Object, e As EventArgs) Handles Stopp.Click 
  Handlung(AddressOf dienst.Stop, ServiceControllerStatus.Stopped) 
End Sub


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual Basic 2008
Visual Basic 2008
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Visual Basic 2012






 Visual Basic 2012


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern