Autor: Christoph Jüngling, https://www.juengling-edv.de
In diesem Teil widmen wir uns einigen Restarbeiten für das Erstellen eines Setups für Access-Applikationen. Diese Arbeiten sind zwar keineswegs unbedingt notwendig, runden aber unser Setup ab und sorgen daher beim Anwender oder Administrator für ein “gutes Gefühl”. Wir wollen zunächst sicherstellen, dass unsere Applikation nicht läuft, wenn wir sie updaten wollen. Dann unterscheiden wir bei der Installation zwischen Beta- und finaler Version. Und letztlich wollen wir unser Setup dann noch digital signieren.
Läuft die Applikation
Wie können wir sicherstellen, dass die Access-Applikation nicht läuft, wenn wir sie updaten wollen
Prinzip eines “Mutex”
Sicher haben Sie schon bei Setups bemerkt, dass eine Meldung wie diese kam:
Das Setup hat festgestellt, dass die Applikation Xyz noch läuft. Bitte beenden Sie diese und starten Sie dann das Setup erneut.
Dem Anwender ist das natürlich egal, aber der Entwickler fragt sich unweigerlich: “Woher weiß das Setup das eigentlich” Die Frage ist berechtigt, denn einfach mal “nachschauen” ist halt für eine Software nicht ganz so einfach wie für uns.
Das Geheimnis heißt “Mutex”. Das ist die Kurzform von “mutual exclusion”, auf Deutsch “gegenseitiger Ausschluss”, eine beeindruckend gute Bezeichnung für diesen Mechanismus.
Denn die Setupdurchführung und das laufende Programm schließen sich gegenseitig aus. Während die ACCDB aktiv ist, sollte man sie nicht per Setup austauschen.
Um dies zu nutzen, benötigen wir also sowohl in der Applikation als auch im Setup jeweils einen Mechanismus, der den Mutex setzen, löschen und überprüfen kann. Unsere Access-Applikation erzeugt den Mutex beim Start und löscht ihn kurz vor dem Ende.
Das Setup wiederum überprüft nur, ob es ihn gibt. Falls ja, kommt eine Meldung wie oben gezeigt, falls nein, läuft das Setup einfach weiter.
Das lässt sich in InnoSetup sogar soweit automatisieren, dass ein von der Accdb angestoßenes Update automatisch abläuft.
Mutex-Klasse (VBA)
Mit Hilfe eines kurzen Codesegments schaffen wir es, den Mutex beim Starten unserer Access-Applikation zu erzeugen. Das übernimmt in meinen Projekten immer eine Klasse, die ich eigens dafür geschrieben habe.
Es müsste natürlich keine Klasse sein, eine Sub-Prozedur täte es auch, aber mit der Klasse haben wir den Vorteil, dass wir mit einer Einheit beide Aktionen erledigen können: Erzeugen und Löschen des Mutex wird innerhalb der Klasse durchgeführt – und zwar über den Konstruktor Initialize und den Destruktor Terminate der Klasse.
Zur Integration in die Applikation müssen neben der Klasse selbst nur noch eine Deklaration und zwei Zeilen Code eingefügt werden.
Schauen wir uns zunächst die Mutex-Klasse an. Besonders aufwändig ist sie nicht (siehe Listing 1).
'''' '' Manage an Application Mutex for the current application '' @remarks Mutex name = APP_MUTEX '' @author Christoph Juengling <christoph@juengling-edv.de> '' @link https://gitlab.com/juengling/vb-and-vba-code-library Option Explicit #If VBA7 Then Private Declare PtrSafe Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, _ ByVal bInitialOwner As Long, ByVal lpName As String) As LongPtr Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long Private Declare PtrSafe Function ReleaseMutex Lib "kernel32" (ByVal hMutex As LongPtr) As Long #Else Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, _ ByVal bInitialOwner As Long, ByVal lpName As String) As Long Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As Long #End If Private m_lMutexHandle As Long Private Sub Class_Initialize() m_lMutexHandle = 0 CreateMyMutex End Sub Private Sub Class_Terminate() ReleaseMyMutex End Sub '' CreateMutex the Mutex Public Sub CreateMyMutex() m_lMutexHandle = CreateMutex(ByVal CLng(0), CLng(1), APP_MUTEX) End Sub '''' '' ReleaseMutex the Mutex and close handle '' Public Sub ReleaseMyMutex() If m_lMutexHandle > 0 Then ReleaseMutex m_lMutexHandle CloseHandle m_lMutexHandle m_lMutexHandle = 0 End If End Sub
Listing 1: Klasse zum Nutzen eines Mutex
Die Erzeugung passiert in Class_Initialize, die Zerstörung in Class_Terminate. Der einzige Bezug in unsere Applikation ist hier die Nutzung der globalen Konstanten APP_MUTEX. Diese deklariert den Namen des Mutex:
Public Const APP_MUTEX = "Mein Name ist Hase"
Die Bezeichnung sollte sinnvollerweise dem Namen der Applikation entsprechen, so dass man natürlich auch APP_NAME verwenden kann.
Wichtig ist die Einzigartigkeit im Hinblick auf andere Applikationen! Eine solche Deklaration muss natürlich namensgleich später auch im InnoSetup-Skript enthalten sein, damit Setup und Applikation über dieselbe Bezeichnung verfügen.
Nun müssen wir nur noch dafür sorgen, dass
- eine Modul-Variable für die Instanz der Klasse existiert,
- diese beim Start instanziiert wird und
- vor dem Beenden der Applikation zerstört wird.
Das ist ebenfalls trivial. In einem Modul, in dem vielleicht noch weitere globale Deklarationen stehen, fügen wir diese Zeile ein:
Public mutex As clsMutex
In dem Code für die Initialisierung:
Set mutex = New clsMutex
Und für das Beenden:
Set mutex = Nothing
Das war doch einfach, oder Streng genommen ist die letzte Aktion nicht nötig, da der Mutex mit dem Beenden der Applikation automatisch gelöscht wird.
Den Mutex in InnoSetup eintragen
In InnoSetup ist die Sache sogar noch einfacher, denn der ganze Mechanismus zum Überprüfen und Reagieren ist dort bereits enthalten, Code müssen wir dafür nicht schreiben.
Wir müssen nur dafür sorgen, dass InnoSetup den Namen des Mutex erfährt. Wie schon erwähnt, muss diese Deklaration natürlich identisch mit der Konstanten aus unserer Applikation sein, und das betrifft auch die Groß-/Kleinschreibung!
Im Deklarationsbereich zu Beginn des Skriptes schreiben wir also:
#define MyAppMutex "Mein Name ist Hase"
Und in der Gruppe [Setup]:
AppMutex={#MyAppMutex}
Mehr ist nicht nötig. Und wie funktioniert das nun
Zum einen genau wie oben beschrieben. Wenn das Setup bei der Ausführung entdeckt, dass der Mutex bereits existiert, zeigt es die Meldung aus Bild 1 an.
Bild 1: Mutex-Meldung
Nun kann der Anwender entsprechend darauf reagieren. Besonders elegant arbeitet InnoSetup jedoch, wenn es für die Installation mit dem Kommandozeilen-Argument silent aufgerufen wurde.
Dann nämlich verzichtet es auf alle Rückfragen mit Benutzerinteraktion. Für den Mutex-Fall bedeutet das, dass das Setup solange abwartet, bis der Mutex verschwunden ist. Dann wird das Setup mit Standardeinstellungen fortgeführt.
Dieser Mechanismus hat den besonderen Reiz, dass die Applikation selbst per Shell-Execute die Ausführung des Setups anstarten und sich dann in aller Ruhe beenden kann. Erst wenn der Mutex gelöscht ist, läuft das Setup durch.
Beta oder Final
Nicht immer ist ein Programm sofort fertig, auch wenn der Entwickler noch so überzeugt von seiner Arbeit ist. Daher kann es sinnvoll sein, ausgewählte User in einen Beta-Test einzubeziehen.
Dabei sollte jedoch immer auf eine strenge Trennung nicht nur der Programminstallation, sondern auch bezüglich der Daten geachtet werden. Eine Möglichkeit dabei ist eine Kombination aus dem Setup-Skript und dem Code, der für die Tabelleneinbindung sorgt. Worauf müssen wir achten, und wie machen wir uns die Arbeit möglichst einfach
Beginnen wir mit der Frage, bezüglich welcher Einstellungen sich Beta- und Final-Version unterscheiden:
- Installationspfad
- Beta-Hinweis oder Lizenzvereinbarung
- Pfad zum Backend
Installationspfad
Nehmen wir (wie schon im ersten Teil dargelegt) an, dass wir unser “normales” Programm unter C:\Users\USERNAME\AppData\Local\Programs installieren wollen. Dort wird sicherlich für jedes Programm ein eigenes Unterverzeichnis verwendet werden.
Es bietet sich also an, dies für unser eigenes Programm ebenfalls zu tun, und zwar getrennt für Beta- und Final-Version.
Dazu müssen wir also die Setup-Einstellung DefaultDirName angemessen verändern. Bisher steht dort {userpf}\{#MyAppName}. Es spricht also nichts dagegen, zum Beispiel {userpf}\{#MyAppName}-Beta zu verwenden.
Doch mir widerstrebt es, jedesmal mitten im Skript eine Änderung vorzunehmen. Da kann man sich leicht vertun, und es wird auch nicht die einzige Änderung bleiben. Eine Lösung für diesen Fall sind wieder einmal die Konstanten, die wir ja bereits kennengelernt haben. Ich füge also zunächst eine weitere hinzu:
#define MyAppStatus "Beta"
oder
#define MyAppStatus "Final"
Damit haben wir wieder eine Einstellung, die wir nur am Anfang des Skriptes verändern müssen, wodurch hoffentlich alle anderen Settings entsprechend angepasst werden.
Damit das funktioniert, nutzen wir eine weitere Möglichkeit von InnoSetup, die uns als Entwickler natürlich vertraut ist: Die If-Then-Else-Anweisung. Sie funktioniert im Prinzip wie von VBA her bekannt, nur sieht die Syntax ein wenig anders aus.
Im folgenden Beispiel erweitere ich bei einer Betaversion den Standard-Installationspfad und die Startmenü-Gruppe zum Beispiel durch den Zusatz -Beta:
#if MyAppStatus == "Beta" DefaultDirName={userpf}\{#MyAppName}-{#MyAppStatus} DefaultGroupName={#MyAppName}-{#MyAppStatus} #else DefaultDirName={userpf}\{#MyAppName} DefaultGroupName={#MyAppName} #endif
Selbstverständlich sollten wir auch den Registry-Eintrag für die Installation entsprechend verändern. Dafür ist bekanntlich die Einstellung AppId zuständig.
Also schreiben wir analog zum obigen:
#if MyAppStatus == "Beta" AppId={#MyAppName}-{#MyAppStatus} #else AppId={#MyAppName} #endif
Natürlich dürfen wir diese beiden auch zusammenfügen, denn die Reihenfolge der Settings in der Gruppe [Setup] ist nicht von Bedeutung.
Betahinweis oder Lizenzvereinbarung
Genau so verfahren wir bei der Entscheidung “Beta-Hinweis oder Lizenzvereinbarung”. Für die Anzeige dieses Zusatztextes ist bekanntlich die [Languages]-Gruppe verantwortlich. Dort steht bisher:
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo