Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Determining the amount of RAM 2

Status
Not open for further replies.

Griffyn

Programmer
Jul 11, 2002
1,077
0
36
AU
I maintain lots of systems, from servers with 8GB of RAM, to those running NT4. Stretchwickster wrote faq102-3240, but it doesn't handle systems with more than 2GB of RAM - something that is becoming common.

So I've written my own function that (hopefully) handles any combination of OS and RAM amount: GetTotalRAM.

Attached is the code required. It's this complex, because Windows NT (and earlier) doesn't have GlobalMemoryStatusEx in it's kernel.

Code:
[b]uses[/b]
  Windows;
[b]type[/b]
  TWinVersion = (wvUnknown, wv95, wv98, wv98SE, wvNT,
    wvME, wv2000, wvXP, wvVista, wv2003, wv7);

  TMemoryStatusEx = [b]record[/b]
    dwLength: DWORD;
    dwMemoryLoad: DWORD;
    ullTotalPhys: Int64;
    ullAvailPhys: Int64;
    ullTotalPageFile: Int64;
    ullAvailPageFile: Int64;
    ullTotalVirtual: Int64;
    ullAvailVirtual: Int64;
    ullAvailExtendedVirtual: Int64;
  [b]end[/b];

[b]function[/b] GetWinVersion: TWinVersion;
[b]var[/b]
  osVerInfo: TOSVersionInfo;
  majorVersion, minorVersion: Integer;
[b]begin[/b]
  Result := wvUnknown;
  osVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  [b]if[/b] GetVersionEx(osVerInfo) [b]then[/b]
  [b]begin[/b]
    minorVersion := osVerInfo.dwMinorVersion;
    majorVersion := osVerInfo.dwMajorVersion;
    [b]case[/b] osVerInfo.dwPlatformId [b]of[/b]
      VER_PLATFORM_WIN32_NT:
      [b]begin[/b]
        [b]if[/b] majorVersion <= [purple]4[/purple] [b]then[/b]
          Result := wvNT
        [b]else[/b] [b]if[/b] (majorVersion = [purple]5[/purple]) [b]and[/b] (minorVersion = [purple]0[/purple]) [b]then[/b]
          Result := wv2000
        [b]else[/b] [b]if[/b] (majorVersion = [purple]5[/purple]) [b]and[/b] (minorVersion = [purple]1[/purple]) [b]then[/b]
          Result := wvXP
        [b]else[/b] [b]if[/b] (majorVersion = [purple]5[/purple]) [b]and[/b] (minorVersion = [purple]2[/purple]) [b]then[/b]
          Result := wv2003
        [b]else[/b] [b]if[/b] (majorVersion = [purple]6[/purple]) [b]then[/b]
          Result := wvVista
        [b]else[/b] [b]if[/b] (majorVersion = [purple]7[/purple]) [b]then[/b]
          Result := wv7;
      [b]end[/b];
      VER_PLATFORM_WIN32_WINDOWS:
      [b]begin[/b]
        [b]if[/b] (majorVersion = [purple]4[/purple]) [b]and[/b] (minorVersion = [purple]0[/purple]) [b]then[/b]
          Result := wv95
        [b]else[/b] [b]if[/b] (majorVersion = [purple]4[/purple]) [b]and[/b] (minorVersion = [purple]10[/purple]) [b]then[/b]
        [b]begin[/b]
          [b]if[/b] osVerInfo.szCSDVersion[[purple]1[/purple]] = [teal]'A'[/teal] [b]then[/b]
            Result := wv98SE
          [b]else[/b]
            Result := wv98;
        [b]end[/b]
        [b]else[/b] [b]if[/b] (majorVersion = [purple]4[/purple]) [b]and[/b] (minorVersion = [purple]90[/purple]) [b]then[/b]
          Result := wvME
        [b]else[/b]
          Result := wvUnknown;
      [b]end[/b];
    [b]end[/b];
  [b]end[/b];
[b]end[/b];

[b]function[/b] GetGlobalMemoryRecord: TMemoryStatusEx;
[b]type[/b]
  TGlobalMemoryStatusEx = [b]procedure[/b]([b]var[/b] lpBuffer: TMemoryStatusEx); stdcall;
[b]var[/b]
  ms : TMemoryStatus;
  h : THandle;
  gms : TGlobalMemoryStatusEx;
[b]begin[/b]
  Result.dwLength := SizeOf(Result);
  [b]if[/b] GetWinVersion [b]in[/b] [wvUnknown, wv95, wv98, wv98SE, wvNT, wvME] [b]then[/b]
  [b]begin[/b]
    ms.dwLength := SizeOf(ms);
    GlobalMemoryStatus(ms);
    Result.dwMemoryLoad := ms.dwMemoryLoad;
    Result.ullTotalPhys := ms.dwTotalPhys;
    Result.ullAvailPhys := ms.dwAvailPhys;
    Result.ullTotalPageFile := ms.dwTotalPageFile;
    Result.ullAvailPageFile := ms.dwAvailPageFile;
    Result.ullTotalVirtual := ms.dwTotalVirtual;
    Result.ullAvailVirtual := ms.dwAvailVirtual;
  [b]end[/b]
  [b]else[/b]
  [b]begin[/b]
    h := LoadLibrary(kernel32);
    [b]try[/b]
      [b]if[/b] h <> [purple]0[/purple] [b]then[/b]
      [b]begin[/b]
        @gms := GetProcAddress(h, [teal]'GlobalMemoryStatusEx'[/teal]);
        [b]if[/b] @gms <> [b]nil[/b] [b]then[/b]
          gms(Result);
      [b]end[/b];
    [b]finally[/b]
      FreeLibrary(h);
    [b]end[/b];
  [b]end[/b];
[b]end[/b];

[b]function[/b] GetTotalRAM: Int64;
[b]begin[/b]
  Result := GetGlobalMemoryRecord.ullTotalPhys;
[b]end[/b];

You can see from the record structure it is trivial to get other values from the system.

To ensure accurate reporting for systems that have between 2GB and 4GB of RAM, the /LARGEADDRESSAWARE flag must be set in the compiled .EXE. Delphi 6 and earlier don't have this as an option in the IDE, so you must include this line
Code:
[navy][i]{$SetPEFlags $0020}   { /LargeAddressAware }[/i][[/navy]
in your .dpr (project source) file. Putting it in a unit, such as your main form unit is not enough as the linker will not include it unless the unit is recompiled.

I'm not sure if this option is included in D7 or above. Setting that option in the linker options if it's available should be enough.

One last code snippet to format the byte count to something more readable:

Code:
[navy][i]{ This function returns a formatted string with at the appropriate level of
  bytes, kilobytes, megabytes, gigabytes, etc }[/i][/navy]
[b]function[/b] FormatBytes(ABytes: Int64): String;
[b]const[/b]
  suffix : [b]array[/b][[purple]0..6[/purple]] [b]of[/b] String = ([teal]'B'[/teal], [teal]'KB'[/teal], [teal]'MB'[/teal], [teal]'GB'[/teal], [teal]'TB'[/teal], [teal]'PB'[/teal], [teal]'EB'[/teal]);
[b]var[/b]
  l : Integer;
  fr : Double;
[b]begin[/b]
  l := [purple]0[/purple];
  fr := ABytes;
  [b]while[/b] fr >= [purple]1024[/purple] [b]do[/b]
  [b]begin[/b]
    inc(l);
    fr := fr / [purple]1024[/purple];
  [b]end[/b];
  [b]if[/b] fr >= [purple]1000[/purple] [b]then[/b]   [navy][i]// ensures eg. 1022 MB will show 0.99 GB
[/i][/navy]  [b]begin[/b]
    inc(l);
    fr := fr / [purple]1024[/purple];
  [b]end[/b];
  [b]if[/b] l > High(suffix) [b]then[/b]
    Result := [teal]'too large'[/teal]
  [b]else[/b]
    Result := Format([teal]'%f %s'[/teal], [fr, suffix[l]]);
[b]end[/b];
 
I'm pretty sure this assumpsion:
Code:
        else if (majorVersion = 7) then
          Result := wv7;
is wrong, and should be replaced with:
Code:
        else if (majorVersion = 6) and (minorVersion = 1) then
          Result := wv7;
as a commandline 'ver' on Windows 7 RTM reports:
Code:
Microsoft Windows [Version 6.1.7600]

HTH
TonHu
 
Many thanks for posting this code Griffyn, I haven't had much time to visit Tek-Tips lately, let alone update any of my out-dated 2005 FAQs!

I've updated my FAQ to point to this thread. However, I'm happy to remove my FAQ on this topic, if you're willing to replace it with the finalised content of this thread. What do you think?

Clive
Runner_1Revised.gif

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"To err is human, but to really foul things up you need a computer." (Paul Ehrlich)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To get the best answers from this forum see: faq102-5096
 
Hi Stretchwickster,

I've submitted this (with change suggested by TonHu) as a FAQ.
 
This is definitely to not belittle anything you've done, but I do have a question. Is all the versioning code necessary? Couldn't you just test off of the load of the GlobalMemoryStatusEx proc? Or is the posted code more to "standard"? For example:

Code:
{$APPTYPE CONSOLE}
program getmemoryex; uses windows;
  { getmemoryex example for Delphi.  Version 3 used, so Comp was used as
    equivalent to int64.  Comment the type line for this out if you use a
    Delphi which supports it. }
  type
    Int64 = Comp;
    TMemoryStatusEx = record
      dwLength: DWord;
      dwMemoryLoad: DWord;
      ullTotalPhys: Int64;
      ullAvailPhys: Int64;
      ullTotalPageFile: Int64;
      ullAvailPageFile: Int64;
      ullTotalVirtual: Int64;
      ullAvailVirtual: Int64;
      ullAvailExtendedVirtual: Int64;
    end;
    TGlobalMemoryStatusEx = procedure(var lpbuffer: TMemoryStatusEx); stdcall;


  procedure MGetMemoryStatus(var inms: TMemoryStatusEx);
    var
      ms: TMemoryStatus;
      gms: TGlobalMemoryStatusEx;
      h: THandle;
    begin
      h := LoadLibrary('kernel32.dll');
      try
        if h <> 0 then
          begin
            @gms := GetProcAddress(h, 'GlobalMemoryStatusEx');
            if @gms <> nil then
              begin
                writeln('Calling GlobalMemoryStatusEx');
                writeln('============================');
                inms.dwLength := sizeof(inms);
                gms(inms)
              end
            else
              begin
                writeln('Calling GlobalMemoryStatus');
                writeln('==========================');                
                Ms.dwLength := sizeof(Ms);
                GlobalMemoryStatus(Ms);
                inms.dwMemoryLoad := ms.dwMemoryLoad;
                inms.ullTotalPhys := ms.dwTotalPhys;
                inms.ullAvailPhys := ms.dwAvailPhys;
                inms.ullTotalPageFile := ms.dwTotalPageFile;
                inms.ullAvailPageFile := ms.dwAvailPageFile;
                inms.ullTotalVirtual := ms.dwTotalVirtual;
                inms.ullAvailVirtual := ms.dwAvailVirtual;
              end;
          end;
      finally
        FreeLibrary(h);
      end;
    end;

  var
    mstatus: TMemoryStatusEx;
  begin
    MGetMemoryStatus(mstatus);
    with mstatus do
      begin
        writeln('    Memory Load: ', dwMemoryLoad);
        writeln(' Total Physical: ', ullTotalPhys:0:0);
        writeln(' Avail Physical: ', ullAvailPhys:0:0);
        writeln('Total Page File: ', ullTotalPageFile:0:0);
        writeln('Avail Page File: ', ullAvailPageFile:0:0);
        writeln('  Total Virtual: ', ullTotalVirtual:0:0);
        writeln('  Avail Virtual: ', ullAvailVirtual:0:0);
      end;
    readln;
  end.

This tests fine on WindowsXP and on my virtual Windows ME partition.

Measurement is not management.
 
Hi Glenn,

WinNT definitely does not support GlobalMemoryStatusEx - trying to load it from the kernel in this way results in an exception. Through info on the web I was 'told' that GlobalMemoryStatusEx only became available in Win2000. Perhaps it was also available in WinME?

I'll modify the FAQ the allow WinME to report in this way.
 
WinME doesn't support GlobalMemoryStatusEx. When the code is run, the GlobalMemoryStatus call is made. The purpose of the " if @gms <> nil then" line is to see if the function exists within kernel32.dll.

Measurement is not management.
 
ah. I moved from statically loading GlobalMemoryStatusEx to checking if the OS should support it. Your approach is better; I'll modify the FAQ.
 
hrm, Tek-Tips deleted my FAQ.
 
As an aside on this general topic, those interested in compatibility functions like in this thread can look into the KernelEx project, which aims to make a number of these available for 98/ME systems. If it doesn't work well for you (try it on a test system, not a production system), the source is definitely an interesting read.


Measurement is not management.
 
FAQ has been restored (glitch) faq102-7253
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top