Verwenden von X-Macro in modernem C ++ - Code

Moderne C ++ - Entwicklungstrends legen nahe, dass Makros im Code maximal abgelehnt werden können. Aber manchmal ohne Makros und in ihrer besonders hĂ€sslichen Erscheinungsform kann man nicht tun, da es ohne sie noch schlimmer ist. Über dieses und die Geschichte.

Wie Sie wissen, ist der erste Schritt beim Kompilieren von C und C ++ der PrÀprozessor, der die Makros und PrÀprozessoranweisungen durch Nur-Text ersetzt.

Dies ermöglicht es uns, seltsame Dinge zu tun, zum Beispiel:

// xmacro.h "look, I'm a string!" // xmacro.cpp std::string str = #include "xmacro.h" ; 

Nachdem der PrÀprozessor funktioniert, wird aus diesem MissverstÀndnis der richtige Code:

 std::string str = "look, I'm a string!" ; 

NatĂŒrlich kann dieser schreckliche Header nirgendwo anders eingefĂŒgt werden. Und ja, aufgrund der Tatsache, dass wir diesen Header mehrmals in dieselbe Datei einfĂŒgen werden - kein #pragma einmal oder Wachen einschließen.

Schreiben wir ein komplexeres Beispiel, das mit Hilfe von Makros verschiedene Aufgaben ausfĂŒhrt und sich gleichzeitig gegen zufĂ€llige #include verteidigt:

 // xmacro.h #ifndef XMACRO #error "Never include me directly" #endif XMACRO(first) XMACRO(second) #undef XMACRO // xmacro.cpp enum class xenum { #define XMACRO(x) x, #include "xmacro.h" }; std::ostream& operator<<(std::ostream& os, xenum enm) { switch (enm) { #define XMACRO(x) case xenum::x: os << "xenum::" #x; break; #include "xmacro.h" } return os; } 

Das ist immer noch hĂ€sslich, aber es gibt bereits einen gewissen Reiz: Wenn Sie der enum-Klasse ein neues Element hinzufĂŒgen, wird es automatisch zur ĂŒberladenen output-Anweisung hinzugefĂŒgt.

Hier können Sie den Anwendungsbereich dieser Methode formalisieren: die Notwendigkeit der Codegenerierung an verschiedenen Stellen aus einer Hand.

Und jetzt die traurige Geschichte von X-Macro und Windows. Es gibt ein solches System wie Windows-Leistungsindikatoren, mit dem Sie bestimmte Leistungsindikatoren an das Betriebssystem senden können, damit andere Anwendungen sie abrufen können. Zabbix kann beispielsweise so konfiguriert werden, dass Leistungsindikatoren erfasst und ĂŒberwacht werden. Dies ist sehr praktisch und Sie mĂŒssen das Rad bei der RĂŒckgabe / Abfrage von Daten nicht neu erfinden.

Ich dachte aufrichtig, dass das HinzufĂŒgen eines neuen ZĂ€hlers wie ein HANDLE-ZĂ€hler = AddCounter ("Name") aussieht. Ah, wie falsch ich war.

Zuerst mĂŒssen Sie ein spezielles XML-Manifest ( Beispiel ) schreiben oder es mit dem Programm ecmangen.exe aus dem Windows SDK generieren. Aus irgendeinem Grund wurde dieses ecmangen jedoch aus den neuen Versionen des Windows 10 SDK entfernt. Als NĂ€chstes mĂŒssen Sie den Code und die RC-Datei mit dem Dienstprogramm ctrpp generieren, das auf unserem XML-Manifest basiert. Das HinzufĂŒgen neuer Leistungsindikatoren zum System selbst erfolgt nur mit dem Dienstprogramm lodctr mit unserem XML-Manifest im Argument.

Was ist eine RC-Datei?
Dies ist eine Erfindung von Microsoft, die nicht mit Standard-C ++ verwandt ist. Mithilfe dieser Dateien können Sie Ressourcen in exe \ dll einbetten, z. B. Zeichenfolgen \ Symbole \ Bilder usw., und diese dann mithilfe der speziellen Windows-API abrufen.

Perfcounters verwenden diese .rc-Dateien, um ZĂ€hlernamen zu lokalisieren, und es ist nicht klar, warum diese Namen lokalisiert werden sollten.

Zusammenfassend: Um 1 ZĂ€hler hinzuzufĂŒgen, benötigen Sie:

  1. XML-Manifest Àndern
  2. Generieren Sie neue C- und RC-Projektdateien basierend auf dem Manifest
  3. Schreiben Sie eine neue Funktion, die einen neuen ZÀhler erhöht
  4. Schreiben Sie eine neue Funktion, die den ZĂ€hlerwert ĂŒbernimmt

Insgesamt: 4 bis 5 geÀnderte Dateien in diff-e, um einen einzigen ZÀhler und eine Konstante zu erhalten, die unter der Arbeit mit dem XML-Manifest leidet, das die Informationsquelle im Plus-Code ist. Das bietet uns Microsoft.

TatsĂ€chlich sieht die erfundene Lösung beĂ€ngstigend aus, aber das HinzufĂŒgen eines neuen ZĂ€hlers erfolgt genau in einer Zeile in einer Datei. Außerdem wird alles automatisch mithilfe von Makros und leider einem vorab erstellten Skript generiert, da das XML-Manifest weiterhin benötigt wird, obwohl es jetzt nicht das Hauptmanifest ist.

Unsere perfcounters_ctr.h sieht fast identisch mit dem obigen Beispiel aus:

 #ifndef NV_PERFCOUNTER #error "You cannot do this!" #endif ... NV_PERFCOUNTER(copied_bytes) NV_PERFCOUNTER(copied_files) ... #undef NV_PERFCOUNTER 

Wie ich bereits geschrieben habe, erfolgt das HinzufĂŒgen von ZĂ€hlern durch Laden des XML-Manifests mit lodctr.exe. In unserem Programm können wir sie nur initialisieren und Ă€ndern.

Die fĂŒr uns interessanten Initialisierungsfragmente im generierten Opener sehen folgendermaßen aus:

 #define COPIED_BYTES 0 //     0 #define COPIED_FILES 1 //      const PERF_COUNTERSET_INFO counterset_info{ ... 2, //    XML-  ... }; struct { PERF_COUNTERSET_INFO set; PERF_COUNTER_INFO counters[2]; //     } counterset { counterset_info, { //     { COPIED_BYTES, ... }, { COPIED_FILES, ... } } } 

Total: Wir brauchen eine Entsprechung in der Form "ZĂ€hlername - Zunehmender Index", und bei der Kompilierung ist es notwendig, die Anzahl der ZĂ€hler zu kennen und ein Initialisierungsarray aus den ZĂ€hlerindizes zu sammeln. Hier hilft das X-Makro.

Die Zuordnung eines ZĂ€hlernamens zu seinem aufsteigenden Index ist einfach genug.

Der folgende Code wird zu einer AufzÀhlungsklasse, deren interne Indizes bei 0 beginnen und um eins inkrementieren. Wenn wir das letzte Element mit unseren HÀnden addieren, finden wir sofort heraus, wie viele ZÀhler wir insgesamt haben:

 enum class counter_enum : int { #define NV_PERFCOUNTER(ctr) ctr, #include "perfcounters_ctr.h" total_counters }; 

Und basierend auf unserer AufzĂ€hlung mĂŒssen wir die ZĂ€hler initialisieren:

 static constexpr int counter_count = static_cast<int>(counter_enum::total_counters); const PERF_COUNTERSET_INFO counterset_info{ ... counter_count, ... }; struct { PERF_COUNTERSET_INFO set; PERF_COUNTER_INFO counters[counter_count]; } counterset { counterset_info, { //     #define NV_PERFCOUNTER(ctr) \ { static_cast<int>(counter_enum::ctr), ... }, #include "perfcounters_ctr.h" } } 

Das Ergebnis war, dass die Initialisierung eines neuen ZĂ€hlers jetzt 1 Zeile dauert und keine zusĂ€tzlichen Änderungen in anderen Dateien erfordert (zuvor hat jede Regeneration 3 Codeteile nur bei der Initialisierung geĂ€ndert).

Und fĂŒgen wir eine praktische API zum Inkrementieren von ZĂ€hlern hinzu. Etwas im Geist:

 #define NV_PERFCOUNTER(ctr) \ inline void ctr##_tick(size_t diff = 1) { /*   counter_enum::ctr */ } #include "perfcounters_ctr.h" #define NV_PERFCOUNTER(ctr) \ inline size_t ctr##_get() { /*    counter_enum::ctr */ } #include "perfcounter_ctr.h" 

Der PrĂ€prozessor generiert wunderschöne Getter / Setter fĂŒr uns, die wir sofort im Code verwenden können, zum Beispiel:

 inline void copied_bytes_tick(size_t diff = 1); inline size_t copied_bytes_get(); 

Aber wir haben noch 2 traurige Dinge: das XML-Manifest und die .rc-Datei (leider ist es notwendig).

Wir haben es einfach genug gemacht - ein vorgefertigtes Skript, das die Originaldatei mit Makros liest, die ZĂ€hler definieren, analysiert, was zwischen "NV_COUNTER (" und ")" liegt, und basierend darauf beide Dateien generiert, die sich in .gitignore befinden Streu keine Unterschiede.

Es war : Spezielle Software basierend auf dem XML-Manifest generierten Codierungscode. Viele Änderungen im Projekt fĂŒr jedes HinzufĂŒgen / Entfernen des ZĂ€hlers.

Jetzt: Der PrĂ€prozessor und das Prebuild-Skript generieren alle Leistungsindikatoren, das XML-Manifest und die RC-Datei. Genau eine Zeile in diff-e zum HinzufĂŒgen / Entfernen eines ZĂ€hlers. Vielen Dank an den PrĂ€prozessor, der zur Lösung dieses Problems beigetragen hat und in diesem speziellen Fall mehr Gutes als Schaden angerichtet hat.

Source: https://habr.com/ru/post/de475162/


All Articles