vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
DTA-Dateien erstellen inkl. BLZ-/Kontonummernpr?fung  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   RSS-Feeds  | Newsletter  | Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2017
 
zurück
Rubrik: Verschiedenes   |   VB-Versionen: VB5, VB615.12.02
Dienst mit VB erstellen

Unter einem Dienst versteht man ein Programm, das im Hintergrund ohne Interaktion mit dem Benutzer ausgeführt wird, auch wenn kein Benutzer am PC angemeldet ist. Dieser Workshop verrät Ihnen die Grundlagen, um unter WinNT4/2000/XP einen solchen "Dienst" mit VB6 zu erstellen.

Autor:  Wolfgang ChristBewertung:     [ Jetzt bewerten ]Views:  49.652 

Neue Version! sevEingabe 3.0 (für VB6 und VBA)
Das Eingabe-Control der Superlative! Noch besser und noch leistungsfähiger!
Jetzt zum Einführungspreis       - Aktionspreis nur für kurze Zeit gültig -

Unter einem Dienst versteht man ein Programm, das im Hintergrund ohne Interaktion mit dem Benutzer ausgeführt wird, auch wenn kein Benutzer am PC angemeldet ist. Dieser Workshop verrät Ihnen die Grundlagen, um unter WinNT4/2000/XP einen solchen "Dienst" mit VB6 zu erstellen.

Allgemeines

Um mit VB einen Dienst zu erstellen, wird von Microsoft das Tool ntsvc.ocx benötigt. Leider gestaltet die Suche auf der Microsoft Webpage nach diesem Tool sehr schwierig, weshalb wir Ihnen diese Arbeit abgenommen haben. Über nachfolgenden Link können Sie sich das Tool inkl. VB-Samplecode downloaden. Nach dem Entpacken in einen Ordner Ihrer Wahl kopieren Sie bitte die OCX-Datei (Ordner SRC\Release) in das System32-Verzeichnis und registrieren die ActiveX-Komponente mit "REGSVR32.EXE ntsvc.ocx".

Download:
 ntsrvocx.zip (94 KB)

Was ist ein Dienst?
Unter einem Dienst versteht man ein Programm, das im Hintergrund ohne Interaktion mit dem Benutzer ausgeführt wird, auch wenn kein Benutzer am PC angemeldet ist. Ein Dienst muss so programmiert werden, dass keine Fehler auftreten bzw. alle Fehler abgefangen werden, da niemand da ist, der auf Fehlermeldungen reagieren und entsprechende Schaltflächen anklicken kann. Gerade aus diesem Grund muss man sich Gedanken über eventuelle Fehlermeldungen / Meldungen machen. Wie kann das Programm dem Benutzer mitteilen, dass es läuft oder dass ein Fehler aufgetreten ist? Eventuell wird das Programm auf einem Server eingesetzt, an dem nie ein Benutzer sitzt. Möglicherweise ist das Programm aber auch so wichtig, dass im Falle eines Fehlers sofort einige Alarmglocken läuten müssen.

In diesem Workshop wollen wir nur das Grundgerüst (die Verpackung) für einen Dienst zeigen, Anwendungsmöglichkeiten gibt es viele; z.B. alle 5 Minuten Mails abholen und auf dem Rechner ablegen, auch wenn Sie nicht angemeldet sind oder vieles mehr.

Erstellen des Grundgerüstes:

Fangen wir einfach mal an und erstellen ein Projekt, dem wir die Komponente ntsvc hinzufügen (Dialog "Projekt - Komponenten"). Wir ziehen die Komponente auf die Form und nennen sie NTService1. Da unser Dienst im Hintergrund laufen soll und keine Form benötigt fügen wir als Erstes folgenden Code hinzu:

Option Explicit
 
Private bDoing as Boolean
 
' Mehr zu dieser Klasse gleich
Private cLog as clsLog
 
Private Sub Form_Activate()
  ' dazu werde ich später etwas sagen
  On Error Resume Next
 
  ' Form nicht anzeigen
  Me.Hide
End Sub

Als nächstes ziehen wir ein Timer Control auf die Form und nennen es einfach tmrMain. Unser Timer bekommt noch die Einstellungen "Enabled = False" und "Interval = 60000". (So wichtig ist dieser Dienst nun auch wieder nicht )

Private Sub Form_Load()
  ' Timer initialisieren
  tmrMain.Enabled = False
  tmrMain.Interval = 60000
End Sub

Fehlerbehandlung

Jetzt müssen wir uns Gedanken über eine Fehlerbehandlung machen. Die beste Lösung ist: Wir machen es mit Klasse . Wir fügen also über Projekt ein neues Klassenmodul hinzu und nennen dieses clsLog. Diese Klasse hat einzig die Aufgabe ein LogFile mitzuführe, so dass man später nachschauen kann, wann was passiert ist.

Folgenden Code fügen wir in dieses Klassenmodul ein:

Option Explicit
 
' Dateiname für das LogFile
Private m_FileName As String
 
' Referenz auf das NTService-Control
Private m_NTService As Object
' Festlegen des LogFile-Dateinamens
Public Property Let sFileName(ByVal sNewFilename As String)
  On Error Resume Next
  m_FileName = sNewFilename
End Property
 
Public Property Get sFileName() As String
  sFileName = m_FileName
End Property
' Zuweisung des NTService-Controls an die Klasse
Public Property Set NTService(oNTService As Object)
  On Error Resume Next
  Set m_NTService = oNTService
End Property
' Neuen Eintrag in LogFile schreiben
Public Sub NewLog(sLogText As String, _
  Optional ByVal bWithEventLog As Boolean = True)
 
  On Error Resume Next
  Dim F As Integer
 
  F = FreeFile
  Open m_FileName For Append As #F
  Print #F, sLogText
  Close #F
 
  If bWithEventLog Then
    Call m_NTService.LogEvent(svcMessageError, _
      svcEventError, sLogText)
  End If
End Sub

Warum mit Klasse? -> Weil mir heute einfach so danach ist Wer möchte kann dies auch ohne Klasse machen, es spielt hier keine Rolle, ich finde es einfach praktischer.

Schauen wir uns nun mal die Ereignisse der ntsvc.ocx genauer an. Folgende Ereignisse zeigt uns VB, gleich nachdem wir das Control auf die Form gezogen haben, an:

Continue
wird beim Fortführen des Dienstes ausgelöst

Control
wird zur Kontrolle des Dienstes ausgelöst

Pause
wird zum Pausieren des Dienstes ausgelöst

Start
wird zum Starten des Dienstes ausgelöst

Stop
wird zum Beenden des Dienstes ausgelöst

Beispielprojekt

Wenn wir nun einen Blick auf das Beispielprojekt werfen, das mit dem Control mitgeliefert wird, finden wir im Form_Load-Teil folgenden Code, den wir in leicht abgeänderter Form in unser Beispielprojekt übernehmen:

Private Sub Form_Load()
  ' Muss in jeder Sub/Function stehen
  On Error GoTo Err_Load 
 
  ' Timer initialisieren
  tmrMain.Interval = 2000
  tmrMain.Enabled = False  
 
  Dim strDisplayName As String
  Dim bStarted As Boolean
 
  ' LogFile-Klasse erstellen
  Set cLog = New clsLog
 
  ' Dateiname für das LogFile festlegen
  cLog.sFileName = App.Path & "\Log.txt"
 
  ' NTService-Objekt zuweisen
  Set cLog.NTService = NTService1
 
  ' So wird der Dienst unter Dienste bezeichnet
  strDisplayName = NTService1.DisplayName
 
  ' Benutzerinteraktion kann weggelassen werden
  ' StatusBar.Panels(1).Text = "Loading"
 
  If Command = "-install" Then
    ' Erlauben von Interaktion mit dem Benutzer /
    ' dem Desktop
    NTService1.Interactive = True
 
    ' Installiert den Dienst
    If NTService1.Install Then
      MsgBox strDisplayName & " erfolgreich installiert"
    Else
      MsgBox strDisplayName & " konnte nicht " & _
        "installiert werden"
    End If
    End
 
  ElseIf Command = "-uninstall" Then
    If NTService1.Uninstall Then
      MsgBox strDisplayName & " erfolgreich deinstalliert"
    Else
      MsgBox strDisplayName & " konnte nicht " & _
        "deinstalliert werden"
    End If
    End
 
  ElseIf Command = "-debug" Then
    NTService1.Debug = True
 
  ElseIf Command <> "" Then
    MsgBox "Invalid command option"
    End
  End If
 
  ' Erlaubt Pause/Continue. Muss vor StartService
  ' gesetzt werden
  NTService1.ControlsAccepted = svcCtrlPauseContinue
 
  ' Verbindet diesen Dienst mit dem Windows NT
  ' Service Controller
  NTService1.StartService
  Exit Sub
 
Err_Load:
  If NTService1.Interactive Then
    MsgBox "[" & Err.Number & "] " & Err.Description
    End
  Else
    ' eventuelle Fehler ins Eventlog eintragen.
    cLog.NewLog "[" & Err.Number & "] " & Err.Description
  End If
End Sub

Was macht nun diese Funktion?
Nun, wenn der Anwender das Programm per Doppelklick mit einem Parameter aufruft, wird dieser Dienst entweder in das Diensteverzeichnis eingetragen oder gelöscht (woher soll auch Windows wissen, dass wir nun einen tollen Dienst selbst programmiert haben?). Wenn keiner dieser Parameter mitgeliefert wird, wird die Programmausführung an den Dienste-Controler gegeben der nun das Ereignis Start auslöst.

Wird der Dienst vom System beendet kann man dies natürlich ebenfalls mitprotokollieren:

Private Sub Form_Unload(Cancel As Integer)
  ' Service wurde beendet
  cLog.NewLog "Exit Service"
 
  ' Log-Klasse beenden
  Set cLog = Nothing
End Sub

Starten, Beenden, Weiterführen, Pause

In diesem Ereignis starten wir dann einfach unseren Timer und teilen Windows mit, dass der Dienst erfolgreich gestartet wurde. Das schaut dann in etwa so aus:

Private Sub NTService1_Start(Success As Boolean)
  ' wieder die Fehlerbehandlung
  On Error GoTo Err_Start
 
  ' Starten des Timers, der nun in regelmäßigen
  ' Abständen eine Aktion auslöst
  tmrMain.Enabled = True
 
  ' Mitteilen, dass alles erfolgreich war.
  Success = True
 
  ' und protokollieren
  cLog.NewLog "Service started"
  Exit Sub
 
Err_Start:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
End Sub

Jetzt wo der Dienst gestartet werden kann wollen wir doch auch, dass wir ihn wieder beenden können:

Private Sub NTService1_Stop()
  ' Wird ausgelöst, wenn der Stop-Button in der
  ' Dienstekontrolle angeklickt wird oder Windows
  ' heruntergefahren wird
  On Error GoTo Err_Stop
 
  ' Timer deaktivieren
  tmrMain.Enabled = False
 
  ' nun warten bis eventuelle tmrEreignisse beendet
  ' werden hier
  Do While bDoing   ' siehe hierzu das tmrMain Event
    DoEvents
  Loop
 
  ' und wieder protokollieren
  cLog.NewLog "Service stopped"
 
  ' Alles beenden
  Unload Me
  Exit Sub
 
Err_Stop:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
End Sub

Nun fehlen noch die Continue- und Pause-Ereignisse:

Private Sub NTService1_Continue(Success As Boolean)
  ' wieder die Fehlerbehandlung
  On Error GoTo Err_Continue
 
  ' Timer wieder aktivieren
  tmrMain.Enabled = True
  Success = True
 
  ' und wieder protokollieren
  cLog.NewLog "Service continued"
  Exit Sub
 
Err_Continue:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
End Sub
Private Sub NTService1_Pause(Success As Boolean)
  ' wieder die Fehlerbehandlung
  On Error GoTo Err_Pause
 
  ' Timer deaktivieren
  tmrMain.Enabled = False
 
  ' siehe hierzu das tmrMain Event
  Do While bDoing
    DoEvents
  Loop
 
  Success = True
 
  ' und wieder protokollieren
  cLog.NewLog "Service paused"
  Exit Sub
 
Err_Pause:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
End Sub

Das Timer-Event

Die eigentliche Aktion, die der Dienst in bestimmten Zeitabständen verrichten soll, wird über den Timer gesteuert:

Private Sub tmrMain_Timer()
  ' wie immer: Fehlerbehandlung aktivieren
  On Error GoTo tmrMain_Err
 
  ' Wichtig! Diese Variable True setzen, solange
  ' die Aktion andauert
  bDoing = True
  DoEvents
 
  ' ####
  ' Hier die Aktion ausführen bzw. Aktions-Prozedur 
  ' aufrufen
  '
  ' Am besten im LogFile protokollieren
  cLog.NewLog "Aktion", False  
 
  ' Aktion beenden... also bDoing = False setzen
  bDoing = False
  DoEvents
  Exit Sub
 
tmrMain_Err:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
 
  ' Nie vergessen, diese Variable auf False zu setzen!!
  bDoing = False
  DoEvents
End Sub

Nun können wir das kleine Beispielprojekt mal starten. Aber staun: Es tut sich nichts - aber auch gar nichts! Korrekt. Wir haben nämlich vergessen, als Startparameter -debug anzugeben. Beenden Sie das laufende Projekt und öffnen Sie die Projekt-Eigenschaftenseite (Menü Projekt - Eigenschaften von...). Klicken Sie auf das Register "Erstellen" und tragen in der Zeile für die Befehlszeilenargumente -debug ein. Anschließend starten Sie das Projekt noch einmal - und diesmal sollte sich auch etwas tun. Zwar nicht sichtbar... aber wenn Sie sich das LogFile mal anschauen, sollten sich dort folgende Einträge befinden:

Service started
Aktion
Aktion
Aktion
Allerdings müssen Sie ein wenig warten, denn den Timer haben wir ja auf 60 Sekunden festgelegt.

Zusammenfassung

Ein Dienst ist also ein ganz normales Programm, das nicht von einem Benutzer sondern vom Betriebssystem selbst gestartet wird, ohne dass ein Benutzer angemeldet sein muss. Die Aktionen des Benutzers wie Programm starten, stoppen, pausieren und fortführen werden durch neue Ereignisse gesteuert, die das Control netterweise gleich mitliefert.

Jeder Fehler MUSS!! abgefangen werden, denn es gibt keinen Benutzer der die MsgBox mit einer etwaigen Fehlermeldung anklicken kann. Anstelle einer MsgBox leiten wir den Fehler an das Eventlog von Windows weiter.

Anstelle von Click, DoubleClick, ... Ereignissen (es gibt ja keine wird der Ablauf des Programms nun von Timer-Ereignissen gesteuert. Die Form lassen wir ganz schnell wieder verschwinden, denn die muss nun wirklich nicht angezeigt werden (wer will die denn auch betrachten *g* ).

Kommen wir nun zum Timer selbst: Er hat in diesem Programm eine ganz besondere Stelle. Wir müssen drei ganz wichtige Dinge beachten:

  1. Das Programm darf nicht angehalten oder gestoppt werden, wenn gerade ein Ereignis abgearbeitet wird.
  2. Der Timer muss extrem "schlank" (=effizient) programmiert werden, damit nicht wertvolle Systemressourcen verbraucht werden.
  3. Wie bei jedem anderem VB-Programm auch, wenn gerade Berechnungen laufen, oder etwas Anderes länger dauert, braucht dieses Progrämmchen 100 % der CPU. Das stimmt nicht ganz, Windows teilt den Diensten niemals soviel zu, aber die anderen Dienste unter Windows sind davon benachteiligt, denn diese können nun nicht mehr so richtig arbeiten.

Damit dies alles nicht auftritt brauchen wir folgende Hilfskonstruktionen:

Zu 1.)
Wir fügen folgenden Code in das Timer-Ereignis Folgendes ein:

Private Sub tmrMain_Timer()
  ' wie immer: Fehlerbehandlung aktivieren
  On Error GoTo tmrMain_Err
 
  ' Wichtig! Diese Variable True setzen, solange
  ' die Aktion andauert
  bDoing = True
  DoEvents
 
  ' ####
  ' Hier die Aktion ausführen bzw. Aktions-Prozedur 
  ' aufrufen
  '
  ' Am besten im LogFile protokollieren
  cLog.NewLog "Aktion", False  
 
  ' Aktion beenden... also bDoing = False setzen
  bDoing = False
  DoEvents
  Exit Sub
 
tmrMain_Err:
  ' eventuelle Fehler ins Eventlog eintragen.
  cLog.NewLog "[" & Err.Number & "] " & Err.Description
 
  ' Nie vergessen, diese Variable auf False zu setzen!!
  bDoing = False
  DoEvents
End Sub

Zu 2.)
Anstelle von "' ####" kommt nun Ihr Code, möglichst optimiert geschrieben. Vermeiden Sie nach Möglichkeit hier irgendwelche Warteschleifen, denn diese benötigen viel Prozessorzeit.

Zu 3.)
Zwischen viele Anweisungen - nicht hinter jede - ein DoEvents setzen, dies reduziert zwar die Ausführungsgeschwindigkeit des Dienstes, verhindert aber, dass die anderen Dienste nicht mehr richtig arbeiten können.

In die eigentliche Ereignisverarbeitung gehört nun auch ein LogFile, damit ein Benutzer die Arbeit des Programms eventuell überprüfen kann. In das EventLog würde ich das nicht hineinschreiben, da sonst dieses Log schnell überfüllt sein könnte und auf Grund des beschränkten Platzes vorhergehende Einträge (auch von anderen Programmen) nicht mehr vorhanden sind.

Erläuterung der CommandLine-Parameter

Soll der Dienst im System installiert werden, wird die EXE-Datei mit dem Parameter -install aufgerufen. Öffnen Sie anschließend den Dialog Dienste unter Systemsteuerung - Verwaltung, so sollte das kleine Beispielprogramm dort in der Liste eingetragen sein - und zwar unter der Bezeichnung Simple Service. Um den Dienst nun zu starten klicken Sie mit der rechten Maustaste auf den Eintrag und wählen Starten. Sie können auch einstellen, dass dieser Dienst autom. mit jeder Windows-Sitzung gestartet werden soll. Hierzu das Eigenschaften-Fenster öffnen und als Starttyp "automatisch" festlegen. Nachdem der Dienst gestartet wurde können Sie sich ruhig einmal das LogFile anschauen. Hier wird jetzt alles protokolliert, d.h. jeder Start, jede Aktion, jede Pause etc. Testen Sie das ganze ruhig einmal aus, indem Sie den Dienst über die Systemsteuerung manuell "anhalten", ""fortsetzen", "stoppen" oder auch "neu starten".

Um den Dienst im System zu deaktivieren, muss die EXE-Datei mit dem Parameter -uninstall aufgerufen werden.

Und wenn Sie den Dienst in der VB-IDE testen wollen, einfach mit dem Parameter -debug starten.

Wird der Dienst über einen anderen als den drei hier beschriebenen Parametern gestartet, wird eine MsgBox angezeigt und der Dienst sofort wieder beendet.

Kommen wir nun zu unserem letztem Punkt: den "Alarmglocken".

Es gibt auf dem Markt viele Programme, die einen Server usw. auf Funktion überwachen. Damit dies funktioniert, braucht unser Programm ein Winsock-Control. Ja richtig gelesen, wir brauchen eine Winsock. Das Prinzip dahinter ist einfach. Wir legen ein Winsock auf die Form und sobald der Dienst gestartet wurde machen wir einfach einen Port auf (Listen). Nun kommt das Überwachungsprogramm und verbindet sich mit diesem Port. Wenn wir nun einen Fehler feststellen, anhalten oder beenden, schließen wir die Connection oder beenden das Listen. Das Überwachungsprogramm weiß nun, dass etwas faul ist und kann nach Hilfe "MsgBoxen" (Schönes Wort oder ? Nun ist es aber so, dass das Programm nicht immer mit uns verbunden ist, nein, nur sporadisch verbindet es sich und trennt die Verbindung wieder. Wir müssen also unser Winsock so programmieren, dass es automatisch wieder auf Listen geht. Dies soll hier aber nur ein kleiner Tipp sein. Denn das ist echt ein ganz eigenes Thema an dem ich lange gebastelt habe.

Bei Fragen zu diesem Workshop können Sie sich gerne an unser  Diskussionsforum im vb@rchiv wenden.

Weitere Infos erhalten Sie auch auf der Microsoft Homepage unter:
 http://support.microsoft.com/default.aspx?scid=kb;EN-US;175948

Dieser Workshop wurde bereits 49.652 mal aufgerufen.

Über diesen Workshop im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Workshop, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Aktuelle Diskussion anzeigen (1 Beitrag)

nach obenzurück


Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
   

Druckansicht Druckansicht Copyright ©2000-2017 vb@rchiv Dieter Otter
Alle Rechte vorbehalten.
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel