Der Martin: Windows-API: Speicherverbrauch der laufenden Anwendung abfragen

Hallo zusammen,

ich brauche mal einen guten Tipp von einem routinierten Windows-Programmierer. Vielleicht genügt schon ein hilfreiches Stichwort für die Suche.

Ich habe eine selbst entwickelte Windows-Applikation, die vor allem Messdaten aus externen Quellen erfassen und protokollieren soll. Die Anwendung ist in Plain Vanilla C geschrieben und nutzt ausschließlich das Windows-API, ohne externe Bibliotheken oder Frameworks zu brauchen (und das möchte auch gern so bleiben). Dabei mache ich ausgiebig Gebrauch von dynamischer Speicherreservierung mit GlobalAlloc() und GlobalFree().

Von einem Nutzer kam nun der Vorschlag, ob ich nicht in der Statuszeile live anzeigen könnte, wieviel Arbeitsspeicher das Programm aktuell belegt. Ich fand die Idee cool und dachte, hey, mach ich gerne. Gestern habe ich dann viel Zeit mit dem Suchen in der Microsoft-Doku verbracht, aber nichts Passendes gefunden. Mir schwebt eine API-Funktion vor, die mir als Ergebnis die aktuelle Speicherbelegung durch die aufrufende Anwendung liefert, oder meinetwegen auch eine Struktur, die unter anderem diesen Wert enthält.

Gibt's da vielleicht auch was von Ratioph Microsoft?

Falls sich nichts einfach Anwendbares findet, mach ich mir vielleicht einfach je einen Wrapper für GlobalAlloc() und GlobalFree(), in dem ich nach der Reservierung bzw. vor der Freigabe die tatsächlich von Windows belegte Speichergröße mit GlobalSize() abfrage und selbst mitzähle. Klingt aber irgendwie nach Holzhammermethode, und hat außerdem den Schönheitsfehler, dass der statisch durch Code- und Datensegment belegte Speicher nicht mitzählt.

Danke schon mal vorab für jeden kommenden Denkanstoß.

Live long and pros healthy,
 Martin

--
Fische, die bellen, beißen nicht.
  1. Hallo Martin,

    hilft Dir das Process Status API weiter?

    https://docs.microsoft.com/en-us/windows/win32/psapi/about-psapi

    Entweder "Memory Usage" für den Gesamtverbrauch oder "Working Set" für das, was aktuell im RAM (und nicht im Pagefile) ist.

    Rolf

    --
    sumpsi - posui - obstruxi
  2. Hallo Martin,

    Dabei mache ich ausgiebig Gebrauch von dynamischer Speicherreservierung mit GlobalAlloc() und GlobalFree().

    Aber bitte nur im Notfall. Diese Funktionen sind da, um Kompatibilität für 16-bit Anwendungen bereitzustellen.

    Memory Management - Global and Local functions

    Neue Anwendungen sollen die Heap-Funktionen verwenden. Die sind auf der genannten Seite verlinkt.

    Die Existenz eines private heap bedeutet übrigens auch, dass die Speichermenge, die für deinen Prozess gemeldet wird, unerwartet groß ist. Oder sich nicht verändert, wenn Du einen HeapAlloc machst. Eventuell musst Du mit GetProcessHeap, QueryHeapInformation oder HeapWalk ein paar Details austüfteln.

    Inwieweit malloc und Konsorten unter der Haube auf die Heap-Funktionen zugreifen oder ob sie einen von der C Runtime allocierten Heap unterverwalten, weiß ich nicht. Das hängt von der jeweiligen CRT und mutmaßlich auch vom gewählten Speichermodell ab. Aber eigentlich sollte man Speicherverwaltung über die C Runtime machen und nicht direkt beim Betriebssystem.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      hilft Dir das Process Status API weiter?

      https://docs.microsoft.com/en-us/windows/win32/psapi/about-psapi

      nicht wirklich; einen Teil der dort verlinkten Funktionen hatte ich gestern schon gefunden. Es scheitert immer daran, dass ich zuerst "meine eigene" Prozess-ID herausfinden muss, über deren Herkunft die API-Dokumentation den Mantel des Schweigens deckt.
      Wenn ich die erstmal habe - okay, dann käme ich da weiter.

      Entweder "Memory Usage" für den Gesamtverbrauch oder "Working Set" für das, was aktuell im RAM (und nicht im Pagefile) ist.

      Danke. Über die Ausdrücke habe ich mich nämlich auch schon gewundert und nicht gefunden, was sie bedeuten.

      Dabei mache ich ausgiebig Gebrauch von dynamischer Speicherreservierung mit GlobalAlloc() und GlobalFree().

      Aber bitte nur im Notfall. Diese Funktionen sind da, um Kompatibilität für 16-bit Anwendungen bereitzustellen.

      Jein. Microsoft schreibt zwar etwas in der Richtung. Aber solange es gleichzeitig heißt, dass diese Funktion Wrapper für die Heap-Funktionen sind, benutze ich lieber GlobalAlloc() und Co. und freue mich, dass ich einen Parameter weniger übergeben muss (das Heap-Handle).

      Neue Anwendungen sollen die Heap-Funktionen verwenden. Die sind auf der genannten Seite verlinkt.

      Ja. Aber in der Beschreibung von HeapAlloc() habe ich auch empfindliche Einschränkungen gefunden. Zum Beispiel, dass ich mit HeapAlloc() maximal 0x7FFF8 Bytes am Stück anfordern kann. Das sind knapp 512kB. Mit GlobalAlloc() kann ich dagegen auch mal 12MB am Stück kriegen.

      Und ich habe schon andere Abhandlungen gelesen, die letztendlich das Fazit ziehen, dass es bei heutigen Windows-Versionen eigentlich egal ist, welchen Satz an Funktionen man für die Speicherverwaltung verwendet (solange man sie nicht mischt), denn seit der 32bit-Ära würde Windows sowieso nicht mehr zwischen dem lokalen und dem globalen Heap unterscheiden.

      Die Existenz eines private heap bedeutet übrigens auch, dass die Speichermenge, die für deinen Prozess gemeldet wird, unerwartet groß ist. Oder sich nicht verändert, wenn Du einen HeapAlloc machst. Eventuell musst Du mit GetProcessHeap, QueryHeapInformation oder HeapWalk ein paar Details austüfteln.

      Das wird mir zu kompliziert.

      Inwieweit malloc und Konsorten unter der Haube auf die Heap-Funktionen zugreifen oder ob sie einen von der C Runtime allocierten Heap unterverwalten, weiß ich nicht. Das hängt von der jeweiligen CRT und mutmaßlich auch vom gewählten Speichermodell ab.

      Speichermodelle gab es früher mal, bei 32bit-Anwendungen mit linearer Adressierung eigentlich nicht mehr. Und ja, die Überlegung ist schon okay, aber für Windows-Anwendungen sind malloc() und free() auch nur Wrapper für die passenden Betriebssystemfunktionen.

      Aber eigentlich sollte man Speicherverwaltung über die C Runtime machen und nicht direkt beim Betriebssystem.

      Das sehe ich komplett anders. Wenn mir das Betriebssystem direkt eine API-Funktion für eine bestimmte Aufgabe bietet, dann nutze ich die auch direkt, anstatt vergleichbare CRTL-Funktionen, die dann irgendwann auch nur die API-Funktion aufrufen. Deswegen benutze ich auch CreateFile() und Co. anstatt fopen(). Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

      Anders seht's aus, wenn mir die CRTL eine Funktion bietet, mit der das System-API nicht in der Form dienen kann, und die ich auch nicht mal schnell in 10min von Hand nachbauen kann.

      Live long and pros healthy,
       Martin

      --
      Fische, die bellen, beißen nicht.
      1. Hallo Martin,

        Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

        Erkennt dein Linker das? Wird nicht vielmehr eine Referenz auf die msvcrt DLL gesetzt und die wird geladen? Oder gibt's auch heute noch ein auf obj-Ebene optimiertes, statisches Linken auf die Runtime Lib? Vor allem: Ist das relevant? Läufst Du derart hart am Speicherlimit, dass Du Dir das nicht erlauben kannst?

        Zum Beispiel, dass ich mit HeapAlloc() maximal 0x7FFF8 Bytes am Stück anfordern kann. Das sind knapp 512kB.

        Kommt drauf an. Da steht: „If the heap specified by the hHeap parameter is a "non-growable" heap, dwBytes must be less than 0x7FFF8. You create a non-growable heap by calling the HeapCreate function with a nonzero value.“

        Deine App hat aber zunächst mal einen Default-Heap, auf dem sie rumturnt. Dessen Anfangsgröße legst Du beim Linken mit /HEAP fest, es ist standardmäßig 1MB, und er ist unlimitiert. D.h. die genannte Begrenzung existiert nicht.

        Das sehe ich komplett anders. Wenn mir das Betriebssystem direkt eine API-Funktion für eine bestimmte Aufgabe bietet, dann nutze ich die auch direkt, anstatt vergleichbare CRTL-Funktionen, die dann irgendwann auch nur die API-Funktion aufrufen.

        Hm. Normalerweise sind die CRT Funktionen für den Einsatz in der jeweiligen Sprache optimiert. Dinge wie ein Prozess-Handle sollten sie unter der Haube verwalten.

        Der zweite Punkt ist, dass API Funktionen auch mal verschimmeln. Wie z.B. GlobalAlloc. Eine CRT Lib kapselt das.

        Es scheitert immer daran, dass ich zuerst "meine eigene" Prozess-ID herausfinden muss, über deren Herkunft die API-Dokumentation den Mantel des Schweigens deckt.

        Dann fragt man halt einen, der es weiß. Quack! Bei mir ist's der erste Treffer. Falls bei Dir nicht: hier.

        De facto ist GetCurrentProcess ein Einzeiler: return (HANDLE)-1;. Aber man soll die Funktion trotzdem verwenden, falls MS das irgendwann mal ändern wird (werden sie nicht können, es wird zu viele Deppen geben, die die -1 hardcoded haben).

        Oh Mann. Ich mach seit über 20 Jahren .net und fasse das WinAPI höchtens mit einer Kneifzange an. Und dann so ein Thema 😂

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo,

          Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

          Erkennt dein Linker das?

          da bin ich gerade nicht sicher; vermutlich nicht. Der Borland-Compiler bzw. -Linker, den ich ganz, ganz früher (so vor 20 Jahren) mal verwendet habe, konnte das bis auf Funktions-Ebene tracken und hat aus den angegebenen Bibliotheken (einschließlich CRTL) nur die Funktionen gelinkt, die auch referenziert wurden. Der gcc (als Teil der MinGW32-Toolchain in Code::Blocks) ist nicht ganz so schlau, meine ich.

          Ich habe allerdings nur in einem von acht C-Modulen, aus denen das Projekt besteht, die stdio.h eingebunden, weil ich dort in einer Funktion noch eine Altlast in Form von fopen(), fwrite() und fclose() habe und bisher zu faul war, das auf CreateFile(), WriteFile() und CloseHandle() umzustellen.
          Wenn ich da "saubermache", kann ich vermutlich komplett auf die CRTL verzichten.

          Wird nicht vielmehr eine Referenz auf die msvcrt DLL gesetzt und die wird geladen? Oder gibt's auch heute noch ein auf obj-Ebene optimiertes, statisches Linken auf die Runtime Lib?

          Irgendwo konnte ich das einstellen, ich find's jetzt aber auf die Schnelle nicht.

          Vor allem: Ist das relevant? Läufst Du derart hart am Speicherlimit, dass Du Dir das nicht erlauben kannst?

          Nein. Das ist ein seit über 30 Jahren tief verwurzelter Ehrgeiz. Ich habe das Programmieren auf einem C64 in Assembler begonnen, da zählte noch jedes Byte!

          Der zweite Punkt ist, dass API Funktionen auch mal verschimmeln. Wie z.B. GlobalAlloc. Eine CRT Lib kapselt das.

          Nun, da gehe ich mal davon aus, dass Betriebssystemkomponenten in sehr viel kürzeren Intervallen aktualisiert werden als Compiler und deren Bibliotheken.

          Es scheitert immer daran, dass ich zuerst "meine eigene" Prozess-ID herausfinden muss, über deren Herkunft die API-Dokumentation den Mantel des Schweigens deckt.

          Dann fragt man halt einen, der es weiß. Quack! Bei mir ist's der erste Treffer.

          Google und auch die Microsoft-Suche haben mir zu ähnlichen Anfragen nur die große Keule der Process API um die Ohren gehauen, aber nichts Spezifisches.

          Falls bei Dir nicht: hier.

          Danke! Damit kann man doch arbeiten!

          De facto ist GetCurrentProcess ein Einzeiler: return (HANDLE)-1;. Aber man soll die Funktion trotzdem verwenden, falls MS das irgendwann mal ändern wird (werden sie nicht können, es wird zu viele Deppen geben, die die -1 hardcoded haben).

          Fürchte ich auch. So wie irgendwann als "undocumented feature" durchsickerte, dass das Window-Handle 0 den Desktop repräsentiert, was die Funktion GetDesktopWindow() de facto überflüssig machte. Ich meine, das wurde später sogar mal in die Doku aufgenommen. Wie du sagst: Zu viele haben es schon verwendet, da wollte man das Feature nicht mehr zurückziehen.

          Oh Mann. Ich mach seit über 20 Jahren .net und fasse das WinAPI höchtens mit einer Kneifzange an. Und dann so ein Thema 😂

          Ich bevorzuge diese Methode. Der Einarbeitungsaufwand und der Overhead anderer Bibliotheken oder Frameworks war mir bisher einfach zu groß. Das Windows-API ist dagegen gut dokumentiert und relativ straightforward (wenn man das mit der Fenster-Organisation und den Messages mal verstanden hat).

          Live long and pros healthy,
           Martin

          --
          Fische, die bellen, beißen nicht.
          1. Hallo Der,

            Wenn ich da "saubermache", kann ich vermutlich komplett auf die CRTL verzichten.

            Unwahrscheinlich. Vergiss nicht den Startup-Code, der letztlich dein main aufruft. Der ist nicht so gewaltig, allerdings referenziert der wieder Komponenten der CRT. Eine gut geschriebene CRT würde alle Initialisierungen on demand machen, und zwar erst, wenn das entspechende Teilsystem benutzt wird. Tut gcc das?

            Nein. Das ist ein seit über 30 Jahren tief verwurzelter Ehrgeiz. Ich habe das Programmieren auf einem C64 in Assembler begonnen, da zählte noch jedes Byte!

            Lass das los. Ich habe ebenfalls noch liebevolle Erinnerungen an meinen Atari 800. Aber dieser Ehrgeiz bringt Dich nirgendwohin. Deine Zeit ist zu wertvoll dafür. Die Zeit für deinen C64 war deine eigene. Aber jetzt programmierst Du auf einem PC mit Gigabytes Hauptspeicher und musst deine Arbeitsstunden bezahlt bekommen. Was nützt es, wenn Du in 4 Stunden Arbeit 10KB in deiner Anwendung sparst? Ich weiß, dass das für Leute in unserem Alter, die auf einem Sinclair ZX81 mit 1KB shared RAM ein Space Invaders programmieren konnten, schmerzhaft ist. Aber es ist einfach so.

            Wenn Du eine Speicherstruktur hast, die millionenfach verwendet wird (bei mir: die LINE-Struktur in einem Logfile-Viewer) und deren Speicherbedarf du halbieren kannst (bei mir: Umstellen von .net Unicode mit 2 Byte pro Zeichen auf den Bytecode aus der Logdatei), kann das bedeuten, dass man nicht 3 Logdateien zu je 100MB in den Speicher bekommt, sondern 20 davon. Und eine Logdatei in 5 Sekunden lädt statt in 50, wie beim Logviewer des Systemherstellers. Für solche Gewinne lohnt sich Bitknabberei. Ich musste grundlegende Stringfunktionen neu schreiben und Speicher selbst verwalten. Aber ohne dieses Tool würde ich beim Durchsuchen von Logs Stunden brauchen. DAS hat sich gelohnt. Es ist aber die große Ausnahme. Wenn Du Megabyteweise Daten verarbeitest, sind Codeeinsparungen im KB Bereich sinnlos.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. n'Abend,

              Wenn ich da "saubermache", kann ich vermutlich komplett auf die CRTL verzichten.

              Unwahrscheinlich. Vergiss nicht den Startup-Code, der letztlich dein main aufruft. Der ist nicht so gewaltig, allerdings referenziert der wieder Komponenten der CRT.

              okay, daran hatte ich nicht gedacht.

              Nein. Das ist ein seit über 30 Jahren tief verwurzelter Ehrgeiz. Ich habe das Programmieren auf einem C64 in Assembler begonnen, da zählte noch jedes Byte!

              Lass das los.

              Warum? Lass mich doch meinen Spaß haben!

              Ich habe ebenfalls noch liebevolle Erinnerungen an meinen Atari 800. Aber dieser Ehrgeiz bringt Dich nirgendwohin. Deine Zeit ist zu wertvoll dafür. Die Zeit für deinen C64 war deine eigene. Aber jetzt programmierst Du auf einem PC mit Gigabytes Hauptspeicher und musst deine Arbeitsstunden bezahlt bekommen.

              Nein, es ist zu >90% immer noch meine eigene Zeit. Ich habe vor ein paar Wochen ein Kontingent von 40 bezahlten Arbeitsstunden bekommen, um das Programm insgesamt so weit zu ertüchtigen, dass es für unsere betrieblichen Anforderungen zu gebrauchen ist.
              Aber jetzt habe ich wieder Blut geleckt und sitze fast täglich wieder ein, zwei Stunden am Abend[1] dran, um noch eine Menge Kleinigkeiten nachzurüsten oder zu verbessern, die nicht unbedingt nötig, aber "nice to have" wären. Und wenn die Kollegen, die das dann nutzen, diesen Pflegeaufwand auch zu schätzen wissen, steigt meine Chance, dass ich für künftige Erweiterungen auch mal wieder ein Zeitkontingent bezahlt bekomme.

              Was nützt es, wenn Du in 4 Stunden Arbeit 10KB in deiner Anwendung sparst?

              Das ist es ja nicht. Ich freue mich einfach, wenn ich mit 0 Aufwand trotzdem einen Vorteil rausholen kann. Es ist ja in diesem Fall nur das Umstellen von Funktionen aus einer Kategorie auf gleichwertige Funktionen, die das OS sowieso bereithält.

              Ich weiß, dass das für Leute in unserem Alter, die auf einem Sinclair ZX81 mit 1KB shared RAM ein Space Invaders programmieren konnten, schmerzhaft ist.

              Oh ja, das Brettchen war auch meine Einstiegsdroge. 😀
              Ist leider irgendwann einer misslungenen Hardware-Bastelei zum Opfer gefallen. Aber da wusste ich sowieso schon, dass meine Ansprüche mittlerweile so weit gestiegen waren, dass der ZX81 die nicht mehr erfüllen konnte.

              Live long and pros healthy,
               Martin

              --
              Fische, die bellen, beißen nicht.

              1. In Zeiten von Home Office mit flexibler Zeitplanung können die "ein, zwei Stunden am Abend" auch mal mittags um zwei sein. ↩︎

            2. Moin Rolf,

              Unwahrscheinlich. Vergiss nicht den Startup-Code, der letztlich dein main aufruft. Der ist nicht so gewaltig, allerdings referenziert der wieder Komponenten der CRT. Eine gut geschriebene CRT würde alle Initialisierungen on demand machen, und zwar erst, wenn das entspechende Teilsystem benutzt wird. Tut gcc das?

              den Entry-Point kann man beim Kompilieren angeben: Konsolenanwendung nutzen main, grafische Anwendungen WinMain.

              Viele Grüße
              Robert

              1. Hallo,

                Vergiss nicht den Startup-Code, der letztlich dein main aufruft. Der ist nicht so gewaltig, allerdings referenziert der wieder Komponenten der CRT. Eine gut geschriebene CRT würde alle Initialisierungen on demand machen, und zwar erst, wenn das entspechende Teilsystem benutzt wird. Tut gcc das?

                den Entry-Point kann man beim Kompilieren angeben: Konsolenanwendung nutzen main, grafische Anwendungen WinMain.

                das ist bekannt. Aber hat Rolf nicht trotzdem Recht mit dem Startup-Code, der noch vor main() bzw. WinMain() oder DllMain() ausgeführt wird?

                Live long and pros healthy,
                 Martin

                --
                Fische, die bellen, beißen nicht.
                1. Moin,

                  Vergiss nicht den Startup-Code, der letztlich dein main aufruft. Der ist nicht so gewaltig, allerdings referenziert der wieder Komponenten der CRT. Eine gut geschriebene CRT würde alle Initialisierungen on demand machen, und zwar erst, wenn das entspechende Teilsystem benutzt wird. Tut gcc das?

                  den Entry-Point kann man beim Kompilieren angeben: Konsolenanwendung nutzen main, grafische Anwendungen WinMain.

                  das ist bekannt. Aber hat Rolf nicht trotzdem Recht mit dem Startup-Code, der noch vor main() bzw. WinMain() oder DllMain() ausgeführt wird?

                  Ich habe Rolf nicht widersprochen.

                  Allein das Vorhandensein einer Funktion wie GetCommandLine lässt darauf schließen, dass die Kommandozeile geparst wird, damit sie in Form von char *argv[] bereitgestellt werden kann.

                  Viele Grüße
                  Robert

          2. Hallo nochmal,

            Wird nicht vielmehr eine Referenz auf die msvcrt DLL gesetzt und die wird geladen? Oder gibt's auch heute noch ein auf obj-Ebene optimiertes, statisches Linken auf die Runtime Lib?

            Irgendwo konnte ich das einstellen, ich find's jetzt aber auf die Schnelle nicht.

            vermutlich spielt mir meine Erinnerung einen Streich: In DevCPP, einer IDE mitsamt Compiler, die ich bis vor etwa 10 Jahren noch benutzt habe, konnte ich das einstellen. Und dort hatte ich tatsächlich eingestellt: Statische Runtime.

            Aber gcc scheint das nicht mehr anzubieten (oder Code::Blocks als IDE bietet die entsprechende Einstellung nicht übers GUI an). Da scheint das dynamische Linken gegen msvcrt.dll voreingestellt zu sein. Das dürfte auch der Grund sein, dass die Größe der resultierenden exe-Datei von rund 600kB (DevCPP) auf rund 150kB (gcc) zurückging.

            Live long and pros healthy,
             Martin

            --
            Fische, die bellen, beißen nicht.
          3. Moin Martin,

            Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

            Erkennt dein Linker das?

            da bin ich gerade nicht sicher; vermutlich nicht. Der Borland-Compiler bzw. -Linker, den ich ganz, ganz früher (so vor 20 Jahren) mal verwendet habe, konnte das bis auf Funktions-Ebene tracken und hat aus den angegebenen Bibliotheken (einschließlich CRTL) nur die Funktionen gelinkt, die auch referenziert wurden. Der gcc (als Teil der MinGW32-Toolchain in Code::Blocks) ist nicht ganz so schlau, meine ich.

            Das kann man doch mit zwei Programmen testen – eines ruft eine einfache CRT-Funktion auf, das andere zwei und dann schaut man sich an, wie sich die Executables unterscheiden.

            Der zweite Punkt ist, dass API Funktionen auch mal verschimmeln. Wie z.B. GlobalAlloc. Eine CRT Lib kapselt das.

            Nun, da gehe ich mal davon aus, dass Betriebssystemkomponenten in sehr viel kürzeren Intervallen aktualisiert werden als Compiler und deren Bibliotheken.

            In Bezug auf den Microsoft Compiler mag das zutreffen, aber der GCC beispielsweise dürfte in etwa mit einer vergleichbaren Frequenz wie das Betriebssystem aktualisiert zu werden. Die APIs hingegen dürften deutlich stabiler sein.

            Viele Grüße
            Robert

        2. Hallo Rolf,

          De facto ist GetCurrentProcess ein Einzeiler: return (HANDLE)-1;. Aber man soll die Funktion trotzdem verwenden, falls MS das irgendwann mal ändern wird (werden sie nicht können, es wird zu viele Deppen geben, die die -1 hardcoded haben).

          danke nochmal für den Tipp.

                   GetProcessMemoryInfo(GetCurrentProcess(),              // query memory info
                      &pmc, sizeof(pmc));                                 // of current process
                   wsprintf(str, "RAM: %d kB", pmc.WorkingSetSize>>10);   //
          

          Das liefert glaubwürdige Werte - auch wenn die deutlich größer sind als das, was der Windows-Taskmanager als RAM-Verbrauch für den Prozess meldet.

          Live long and pros healthy,
           Martin

          --
          Fische, die bellen, beißen nicht.
          1. Moin Martin,

            wsprintf(str, "RAM: %d kB", pmc.WorkingSetSize>>10);
            

            woher weiß denn wsprintf hier, wie groß str ist? Oder weiß es das gar nicht:

            Note Do not use. Consider using one of the following functions instead: StringCbPrintf, StringCbPrintfEx, StringCchPrintf, or StringCchPrintfEx. See Security Considerations.

            Viele Grüße
            Robert

            1. Hallo Robert,

              wsprintf(str, "RAM: %d kB", pmc.WorkingSetSize>>10);
              

              woher weiß denn wsprintf hier, wie groß str ist? Oder weiß es das gar nicht

              das weiß es tatsächlich nicht. Aber ich weiß es.

              Note Do not use. Consider using one of the following functions instead: StringCbPrintf, StringCbPrintfEx, StringCchPrintf, or StringCchPrintfEx. See Security Considerations.

              Ich mag solche Pauschalaussagen nicht. Weiter unten heißt es übrigens:

              The maximum size of the buffer is 1,024 bytes.

              Nanu, warum benutze ich wohl immer CHAR[1024] als Puffer für wsprintf()? 😉

              Außerdem hat wsprintf() noch einen großen Vorteil gegenüber den als Alternativen vorgeschlagenen Funktionen: Es liefert mir als Ergebnis die Länge des erzeugten Strings. Sehr nützlich, wenn man einen String in einer Schleife Schritt für Schritt aufbauen will.

              Live long and pros healthy,
               Martin

              --
              Fische, die bellen, beißen nicht.
              1. Moin Martin,

                wsprintf(str, "RAM: %d kB", pmc.WorkingSetSize>>10);
                

                woher weiß denn wsprintf hier, wie groß str ist? Oder weiß es das gar nicht

                das weiß es tatsächlich nicht. Aber ich weiß es.

                die berühmten „letzten Worte eines C-Programmierers“ analog zum Beifahrer, der vermeldet, dass „von rechts kein Auto kommt“ 😉

                Note Do not use. Consider using one of the following functions instead: StringCbPrintf, StringCbPrintfEx, StringCchPrintf, or StringCchPrintfEx. See Security Considerations.

                Ich mag solche Pauschalaussagen nicht.

                Das Beispiel gets zeigt allerdings, dass solche Warnungen notwendig sind, weil sonst Programmierer, die glaube zu wissen, was sie vermeintlich tun, auch weiterhin unsicheren Code schreiben.

                Weiter unten heißt es übrigens:

                The maximum size of the buffer is 1,024 bytes.

                Nanu, warum benutze ich wohl immer CHAR[1024] als Puffer für wsprintf()? 😉

                Deine formatierten Strings sind 1 KiB groß? Auf einem C64 wäre das aber sehr eng geworden 😜 Du kannst aber auch Annahmen über deine Stringlänge treffen und Funktionen verwenden, die dann garantiert keinen Buffer-Overflow erzeugen, z.B. snprintf

                Außerdem hat wsprintf() noch einen großen Vorteil gegenüber den als Alternativen vorgeschlagenen Funktionen: Es liefert mir als Ergebnis die Länge des erzeugten Strings. Sehr nützlich, wenn man einen String in einer Schleife Schritt für Schritt aufbauen will.

                … das auch die tatsächliche Stringlänge zurückliefert. Es ist nicht alles schlecht am C-Standard.

                Viele Grüße
                Robert

                1. Hallo,

                  wsprintf(str, "RAM: %d kB", pmc.WorkingSetSize>>10);
                  

                  woher weiß denn wsprintf hier, wie groß str ist? Oder weiß es das gar nicht

                  das weiß es tatsächlich nicht. Aber ich weiß es.

                  die berühmten „letzten Worte eines C-Programmierers“ analog zum Beifahrer, der vermeldet, dass „von rechts kein Auto kommt“ 😉

                  oder die des stotternden Beifahrers: Frei... frei... FREI... FREILICH KOMMT DAS WAS!!

                  Nein, im Ernst: Ich weiß doch, was ich den den String reinkippe. In diesem Fall ein DWORD. Das hat als Zahl ausgeschrieben maximal 10 Ziffern. Dazu in diesem Fall eine Handvoll Zierat drumrum, Summe <20 Zeichen. Also reicht mein üblicher 1kB-Puffer lang und schmutzig.

                  Wenn ich natürlich wiederum Strings einsetze, von denen ich vorher nicht weiß, wei lang sie sind, sieht's anders aus.

                  Ich habe die 1024-Byte-Anmerkung bei wsprintf aber bisher so verstenden, dass die Funktionen auch keine Strings >1024 Byte Länge liefert. Getestet habe ich das allerdings noch nicht. Sollte ich vielleicht mal.

                  Note Do not use. Consider using one of the following functions instead: StringCbPrintf, StringCbPrintfEx, StringCchPrintf, or StringCchPrintfEx. See Security Considerations.

                  Ich mag solche Pauschalaussagen nicht.

                  Das Beispiel gets zeigt allerdings, dass solche Warnungen notwendig sind, weil sonst Programmierer, die glaube zu wissen, was sie vermeintlich tun, auch weiterhin unsicheren Code schreiben.

                  Natürlich gibt es solche gefährlichen Fälle. Hier in diesem Fall ist es aber überschaubar, und das barsche Verbot daher unangemessen. Es sollte eher heißen: Use only if you know exactly what you're doing.

                  Weiter unten heißt es übrigens:

                  The maximum size of the buffer is 1,024 bytes.

                  Nanu, warum benutze ich wohl immer CHAR[1024] als Puffer für wsprintf()? 😉

                  Deine formatierten Strings sind 1 KiB groß?

                  Nein, viel kleiner.

                  Auf einem C64 wäre das aber sehr eng geworden 😜

                  Auf einem C64 hätte ich schon etwas Aufwand treiben müssen, um Strings mit einer Länge >256 Zeichen zu verarbeiten. C64-Programmierer waren von Haus aus sparsam und haben daher auch bei Stringoperationen meist nur ein 8bit-Indexregister verwendet. Das sorgte für eine natürliche Grenze.

                  Du kannst aber auch Annahmen über deine Stringlänge treffen und Funktionen verwenden, die dann garantiert keinen Buffer-Overflow erzeugen, z.B. snprintf

                  Das kannte ich tatschlich noch nicht, danke für den Tipp!

                  Es ist nicht alles schlecht am C-Standard.

                  Das habe ich auch nicht gesagt. Nicht einmal gedacht. 😉

                  Live long and pros healthy,
                   Martin

                  --
                  Fische, die bellen, beißen nicht.
      2. Moin Martin,

        hilft Dir das Process Status API weiter?

        https://docs.microsoft.com/en-us/windows/win32/psapi/about-psapi

        nicht wirklich; einen Teil der dort verlinkten Funktionen hatte ich gestern schon gefunden. Es scheitert immer daran, dass ich zuerst "meine eigene" Prozess-ID herausfinden muss, über deren Herkunft die API-Dokumentation den Mantel des Schweigens deckt.

        denk POSIX: Es gibt auch unter Windows getpid.

        Neue Anwendungen sollen die Heap-Funktionen verwenden. Die sind auf der genannten Seite verlinkt.

        Ja. Aber in der Beschreibung von HeapAlloc() habe ich auch empfindliche Einschränkungen gefunden. Zum Beispiel, dass ich mit HeapAlloc() maximal 0x7FFF8 Bytes am Stück anfordern kann. Das sind knapp 512kB. Mit GlobalAlloc() kann ich dagegen auch mal 12MB am Stück kriegen.

        Da dein Programm eh in C ist, könntest du mal testen, wie viel Speicher dir malloc auf einmal liefern kann 😉

        Aber eigentlich sollte man Speicherverwaltung über die C Runtime machen und nicht direkt beim Betriebssystem.

        Das sehe ich komplett anders. Wenn mir das Betriebssystem direkt eine API-Funktion für eine bestimmte Aufgabe bietet, dann nutze ich die auch direkt, anstatt vergleichbare CRTL-Funktionen, die dann irgendwann auch nur die API-Funktion aufrufen. Deswegen benutze ich auch CreateFile() und Co. anstatt fopen(). Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

        Da spricht jemand, der wirklich nur auf einer Plattform arbeitet 😉 Ich habe privat schon unter Mac OS X, Linux und Windows eigenen Software geschrieben und laufen haben wollen – da wollte ich nicht jedes Mal Code portieren.

        Viele Grüße
        Robert

        1. Hallo,

          nicht wirklich; einen Teil der dort verlinkten Funktionen hatte ich gestern schon gefunden. Es scheitert immer daran, dass ich zuerst "meine eigene" Prozess-ID herausfinden muss, über deren Herkunft die API-Dokumentation den Mantel des Schweigens deckt.

          denk POSIX: Es gibt auch unter Windows getpid.

          dort heißt es, der Funktionsname sei deprecated und ein Alias für _getpid. Und da wiederum lese ich: "This API cannot be used in applications that execute in the Windows Runtime."
          Was will uns der Dichter damit sagen?

          Ja. Aber in der Beschreibung von HeapAlloc() habe ich auch empfindliche Einschränkungen gefunden. Zum Beispiel, dass ich mit HeapAlloc() maximal 0x7FFF8 Bytes am Stück anfordern kann. Das sind knapp 512kB. Mit GlobalAlloc() kann ich dagegen auch mal 12MB am Stück kriegen.

          Da dein Programm eh in C ist, könntest du mal testen, wie viel Speicher dir malloc auf einmal liefern kann 😉

          Soweit ich weiß, gibt es da keine Beschränkungen (außer dem verfügbaren Speicher). Aber ich pobier's gelegentlich mal aus.

          Wenn mir das Betriebssystem direkt eine API-Funktion für eine bestimmte Aufgabe bietet, dann nutze ich die auch direkt, anstatt vergleichbare CRTL-Funktionen, die dann irgendwann auch nur die API-Funktion aufrufen. Deswegen benutze ich auch CreateFile() und Co. anstatt fopen(). Das Ziel ist, dass ein großer Teil der CRTL beim Linken als überflüssig (nicht benutzt) erkannt wird und rausfällt - im Idealfall die komplette Runtime. Das ergibt kleinen, kompakten Code.

          Da spricht jemand, der wirklich nur auf einer Plattform arbeitet 😉

          Nein, das nicht. Aber die Projekte, die ich bisher gebastelt habe, waren von ihrer Zweckbindung her von Anfang an *Windows only". Wenn ich das Zielsystem von Beginn an kenne, lasse ich mich auch darauf ein. Wenn ich weiß, dass das Zielsystem ein Raspberry Pi oder ein ESP8266 ist, gehe ich ja auch komplett auf die Eigenheiten dieser Plattformen ein.

          Ich habe privat schon unter Mac OS X, Linux und Windows eigenen Software geschrieben und laufen haben wollen – da wollte ich nicht jedes Mal Code portieren.

          Wenn man diese Absicht hat, tut man natürlich gut daran, allgemein zu bleiben.

          Live long and pros healthy,
           Martin

          --
          Fische, die bellen, beißen nicht.
          1. Moin Martin,

            denk POSIX: Es gibt auch unter Windows getpid.

            dort heißt es, der Funktionsname sei deprecated und ein Alias für _getpid. Und da wiederum lese ich: "This API cannot be used in applications that execute in the Windows Runtime."
            Was will uns der Dichter damit sagen?

            Das, was im zweiten Satz steht:

            For more information, see CRT functions not supported in Universal Windows Platform apps.

            Diese „Windows Runtime“ ist die Laufzeitumgebung für UWP, das sind diese neuen Windows Store Apps. Das dürfte in deinem Fall keine Rolle spielen (bei mir funktioniert es jedenfalls).

            Viele Grüße
            Robert

            1. Hallo Robert,

              Diese „Windows Runtime“ ist die Laufzeitumgebung für UWP

              Jein, UWP ist der Nachfolger von Windows RT.

              WinRT ist in Win8 gekommen. Es gab auch mal ein Surface Tablet in der RT Version, das nur WinRT Anwendungen ausführen konnte.

              In Win10 haben sie RT zu UWP weiterentwickelt. Ob mal irgendwann nur noch UWP Anwendungen ausführen kann? Vielleicht in Windows 13 😉

              Rolf

              --
              sumpsi - posui - obstruxi
          2. Ich habe privat schon unter Mac OS X, Linux und Windows eigenen Software geschrieben und laufen haben wollen – da wollte ich nicht jedes Mal Code portieren.

            Wenn man diese Absicht hat, tut man natürlich gut daran, allgemein zu bleiben.

            IMHO ist das keine persönliche Absicht, sondern für jedes halbwegs ernsthafte Projekt notwendig. Du bist doch hier neben Anderen Verfechter von „ich bin vor Ewigkeiten auf Linux umgestiegen, weil toller“? Wenn man direkt gegen die System-API coded, macht man diese Tür schlicht zu.

            Ja, richtig. Das bringt Overhead mit sich.

            1. Hallo,

              Ich habe privat schon unter Mac OS X, Linux und Windows eigenen Software geschrieben und laufen haben wollen – da wollte ich nicht jedes Mal Code portieren.

              Wenn man diese Absicht hat, tut man natürlich gut daran, allgemein zu bleiben.

              IMHO ist das keine persönliche Absicht, sondern für jedes halbwegs ernsthafte Projekt notwendig.

              nein, sehe ich nicht so. Oft ist von Anfang an durch bestimmte Vorgaben klar, dass ein Programm nur für eine ganz konkrete Plattform gedacht ist, nur auf einer ganz bestimmten Plattform laufen kann. Beispiele hatte ich genannt.

              Du bist doch hier neben Anderen Verfechter von „ich bin vor Ewigkeiten auf Linux umgestiegen, weil toller“? Wenn man direkt gegen die System-API coded, macht man diese Tür schlicht zu.

              Ja. Das sehe ich aber nicht als Problem, wenn die Tür aufgrund anderer Randbedingungen eh schon zu ist. In meinem Fall ist es die Verwendung von Sonder-Hardware, zu der der Lieferant sowieso nur Treiber für Windows liefert.

              Live long and pros healthy,
               Martin

              --
              Fische, die bellen, beißen nicht.
              1. IMHO ist das keine persönliche Absicht, sondern für jedes halbwegs ernsthafte Projekt notwendig.

                nein, sehe ich nicht so.

                Das sei Dir unbenommen.

                Oft ist von Anfang an durch bestimmte Vorgaben klar, dass ein Programm nur für eine ganz konkrete Plattform gedacht ist, nur auf einer ganz bestimmten Plattform laufen kann. Beispiele hatte ich genannt.

                Denkt man sich mal so...

          3. Hallo Martin,

            Nein, das nicht. Aber die Projekte, die ich bisher gebastelt habe, waren von ihrer Zweckbindung her von Anfang an *Windows only". ...

            das dachte ein Kollege auch. Und dann kam ein Forschungsaufenthalt bei einer amerikanischen Gruppe, und die hatten nur Macs, die damals noch intelfrei waren. Zum Glück ist der Kollege nur über das Little-Big-Endian-Problem gestolpert.

            Gruß
            Jürgen

    1. Hallo Raketensuchgerät,

      auch interessant, aber wenn ich das richtig deute, geht es dort um Informationen über den Gesamtzustand des Systems und nicht über das Innenleben eines einzelnen Prozesses.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. hI,

        auch interessant, aber wenn ich das richtig deute, geht es dort um Informationen über den Gesamtzustand des Systems und nicht über das Innenleben eines einzelnen Prozesses.

        korrekt, zumindest was den Windows-Teil angeht. Bei den Ausführungen zu Linux ist einiges dabei, was konkret auf den laufenden Prozess abzielt. Aber das ist gerade nicht meine Baustelle.

        Live long and pros healthy,
         Martin

        --
        Fische, die bellen, beißen nicht.
      2. eines einzelnen Prozesses

        Hm. https://docs.microsoft.com/en-us/windows/win32/psapi/collecting-memory-usage-information-for-a-process könnte helfen ist aber C++

        Da geht es um eine Prozess-ID, die eigene sollte man mit jeder brauchbaren Programmiersprache erfahren können.

        Zum Thema „Linux nicht meine Baustelle“: Da wäre es wirklich einfach. In /proc/$$/status oder „so“ nachsehen... $$ ist die eigene Prozess-ID.

        1. Hallo,

          eines einzelnen Prozesses

          Hm. https://docs.microsoft.com/en-us/windows/win32/psapi/collecting-memory-usage-information-for-a-process könnte helfen ist aber C++

          C++ stört mich nicht, solange nur das Beispiel in C++ ist. Die Systemaufrufe, auf die es ankommt, sind ja doch dieselben, und auch wie man an die nötigen Parameter kommt.

          Da geht es um eine Prozess-ID

          Nein, Thema verfehlt. Das ist eine Variante (oder sogar eine exakte Kopie, ich bin nicht sicher) des Beispiels aus der Microsoft-Doku. Da fragt eine Applikation einfach stumpf alle erdenklichen Prozess-IDs ab (bis zu 1024 Stück), und ermittelt deren Details nacheinander. Die Zuordnung, welche Prozess-ID davon zum eigenen Prozess gehört, wird nicht betrachtet.

          die eigene sollte man mit jeder brauchbaren Programmiersprache erfahren können.

          IMO ist das nicht Sache der Programmiersprache, sondern eine Information, die das OS liefern muss.

          Live long and pros healthy,
           Martin

          --
          Fische, die bellen, beißen nicht.
          1. Hallo Martin,

            tut's ja auch, mit einer aufwändigen Funktion, die (HANDLE)-1 zurückgibt.

            Rolf

            --
            sumpsi - posui - obstruxi
          2. Das ist eine Variante (oder sogar eine exakte Kopie, ich bin nicht sicher) des Beispiels aus der Microsoft-Doku

            😀 Guckst Du URL: 😀

            https://docs.microsoft.com ...

            😷

            Den Rest hat Rolf schon „gesagt“…

            P.S. Morgen 15:45: Pieks!

      3. Hallo Raketensuchgerät,

        auch interessant, aber wenn ich das richtig deute, geht es dort um Informationen über den Gesamtzustand des Systems und nicht über das Innenleben eines einzelnen Prozesses.

        Rolf

        Hm. Demnach war also

        Virtual Memory currently used by current process:

        #include "windows.h"
        #include "psapi.h"
        
        PROCESS_MEMORY_COUNTERS_EX pmc;
        GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc));
        SIZE_T virtualMemUsedByMe = pmc.PrivateUsage;
        

        doch nicht das Gesuchte?

        1. Hallo Raketensuchgerät,

          doch, vermutlich hilft das weiter.

          Ich schrub ja:

          aber wenn ich das richtig deute

          Da hab ich wohl was verdeutet.

          Rolf

          --
          sumpsi - posui - obstruxi