MSI-Custom Actions in C++ Teil 2

Antworten
Andreas Kapust
Administrator
Beiträge: 1092
Registriert: 29.02.2004, 15:51
Wohnort: Hamburg
Kontaktdaten:

MSI-Custom Actions in C++ Teil 2

Beitrag von Andreas Kapust »

Im letzten Teil wurde mit der CustomAction-Klasse begonnen, die wir inzwischen ein wenige erweitert haben, und in diesem Teil auf ein paar der Änderungen und Funktionen eingehen werden.
Als Abschluss kommt die Klasse Anhand eines Beispiels das erste Mal zum Einsatz.

Bild

Neben zahlreichen weiteren Funktionen wurden einige Funktionen für aktuelle Compiler mit [[nodiscard]] versehen (der Rückgabewert muss ausgewertet werden). Statt CA_NODISCARD kann dies auch direkt verwendet werden, wenn Sie den aktuellen g++ oder Visual Studio 17 oder höher verwenden.

Wie im ersten Teil erwähnt, wurde nun auch MsiGetActiveDatabase umgesetzt (mehr in einem anderen Teil).
Beginnen wir mit dem wichtigstem.

Bild

An vielen Stellen müssen wir (WCHAR) Stings auslesen oder erstellen und greifen hierbei nicht auf Klassen der MFC oder STL zurück, da wir Exception im Idealfall vermeiden wollen. Weiterhin soll Speicher, wenn möglich wiederverwendet werden und unnötiges Alloc-Free-Alloc entfallen.

Reicht die neue Speicherlänge für den nächsten String aus, wird dieser weiterverwendet, anderenfalls kommt ein Realloc zum Einsatz.

Möchten wir ein Alloc-Free-Alloc, muss der Zeiger per FreeMem freigegeben und auf nullptr gesetzt oder das Makro (siehe oben) verwendet werden.

Bild

Für ein leichteres Debuggen sollten alle Fehler in das LogFile geschrieben werden. Hierfür, wurde eine Funktion für einen Text und eine Funktion für einen Text mit Argumenten hinzugefügt. Letzte benutzt eine einfache sprintf-Funktion die Sie gerne durch die Secure-Strings-Funktion ersetzten können. Für diese Tutorials reicht dieses vollkommen aus.

Tipp: In aufwendigeren Custom Actions macht es vielleicht Sinn, den Namen des Einstiegspunkt oder der Funktion voranzustellen. Letzteres über einen fifo-Stack - oder Compiler-Makro.

Tipp: Durch PMSIHANDLE in WriteToLogfile ersparen wir uns übrigens das Freigeben per MsiCloseHandle() von hrec, dieses erfolgt beim Verlassen oder Neusetzen automatisch.

Tipp: Das gleiche Verfahren könnte auch für die Strings verwendet werden, wenn einem das Freigeben per FreeMem zu umständlich ist.

Kommen wir nun zu einer Funktion, die wir im abschließenden Beispiel einsetzen werden.

Bild

GetRecordString() erwartet einen Record (MSIHANDLE), einen Index (beginnend bei 1) und natürlich den Zeiger auf den Speicherbereich für die Daten, später dazu mehr.
Hier wird das gleiche Verfahren wie schon bei MsiGetProperty verwendet. Es wird die Größe erfragt, Speicher besorgt und dann der Wert abgeholt. Auch hier wird sowohl berücksichtigt, dass noch kein Speicher vorhanden ist, als auch dass der Speicher vorhanden ist, aber nicht reicht und ein Realloc erfolgen muss.

Schlägt irgendetwas fehl, wird dieses im Logfile vermerkt.

Kommen wir nun zum Beispiel.

Bild

In Ziele 10 wird die Klasse eigebunden und mit hInstall initialisiert. hInstall wird uns vom Windows Installer geliefert und darf nicht freigegeben werden.
In 12 wird der oben bereits erwähne Index erzeugt, der mit Zeile 13 zusammenhängt.

MSI ist, wie vielleicht bekannt, eine Art Datenbank über die per abgespecktem SQL zugegriffen wird. Wir möchten in unserem Fall die ID (File), den Dateinamen (FileName) und die Größe (FileSize) aus der Tabelle File geordnet nach Größe (FileSize) erfragen.

Bei File und FileName handelt es sich um Stings, während FileSize ein Integer ist, siehe Zeile 14 und 15.

Bild

In Zeile 24 sehen wir einen Beispielaufruf von MsiGetProperty. INSTALLDIR, ist ja eigentlich ein Verzeichnis, wird aber als Eigenschaft definiert, darum können wir dieses hierüber erfragen.

MsiGetProperty funktioniert uneingeschränkt, wie in Teil 1 erwähnt, nur bei sofortiger Ausführung. In der verzögerten Ausführung steht nur ein begrenzter Umfang an Eigenschaften zur Verfügung.

MsiGetProperty greift auch nicht direkt auf die Tabelle Property in der MSI-Datenbank zu, sondern verwendet eine Tabelle im Speicher, wobei bei der sofortigen Ausführen die im Kontext des Benutzers verwendet wird und im anderen Fall die im Kontext des Installer-Dienstes.

Schlägt der Aufruf in Zeile 24 fehl, wird in 26 eine Fehlermeldung ausgegeben und in 27 ein Fehlercode gesetzt (falls dieser noch ERROR_SUCCESS lautet).

Auf 38 und 40 gehen wir in einem anderen Teil ein und wenden uns dem Block zwischen 42 und 63 zu.

In 42 wird die oben abgebildete Funktion GetRecordString() aufgerufen, die einen Record (Zeile 40, einen Index (1) Zeile 12 und einen Pointer Zeile 14 benötig.

Schlägt der Aufruf fehl, wird eine Fehlermeldung ausgegeben, eine Returncode gesetzt und die while-Schleife verlassen. Da wir in 11 PMSIHANDLE verwenden, müssen wir uns nicht um MsiCloseHandle kümmern.

Gelingt der Aufruf, fahren wir mit 49 fort.

Waren alle GetRecord-Aufrufe erfolgreich, wird alles in 63 ausgegeben und in Zeile 40 der nächste Wert (wenn vorhanden abgeholt).

Da AllocString verwendet wird, wird für File und FileName der gleiche Speicherbereich verwendet, bis einer der String mehr Speicher benötigt, also länger ist.

Nur WriteToLogfile fordert immer neuen Speicher an – Platz für Optimierungen.

Liefert und FetchRecord() keine Daten mehr und kehrt mit false zurück, wird in 73 bis 75 der Speicher der Strings freigegeben (es wird hier das Makro verwendet, siehe oben) und in Zeile 76 mit dem gesetzten ReturnCode zurückgekehrt.

Zur Erinnerung: Die gesamte Funktion CA_Funtion1() funktioniert in einer verzögerten Ausführung nicht, da dort nicht auf die MSI-Datenbank zugegriffen werden kann!

Damit endet der Teil 2.

Bei Fragen oder Anregungen, wenden Sie sich gern per Mail an uns.
Antworten