About Terminal Server, Citrix, Delphi and other stuff
20 Mar
I’m working on a new build of TSAdminEx for which I need to query the total amount of physical memory. Locally we can use the GlobalMemoryStatusEx API but there’s no API to do this remotely. It would be possible using WMI but I decided not to use that because I dislike it because of it’s slowness and I need support for older OS versions which might not have WMI.
So I found in the registry the following key:
HKLM\HARDWARE\RESOURCEMAP\System Resources\Physical Memory
It has a value .Translated of type RES_RESOURCE_LIST which seems undocumented besides stating that it exists. Regedit knows how to handle it though. If you doubleclick on the key you will see something like this:
If we click on the list and choose display we see the following:
If we sum the Length of all memory blocks we get the total amount of Physical Memory. I wondered how to do this programmatically so I Googled on it but found mostly pages where the values are extracted at fixed offsets rather than knowing the correct structure. I found however that the structure is present in the DDK (wdm.h) and is called CM_RESOURCE_LIST. I was also lucky because Jack van Nuenen had already done a conversion of those headers to Delphi which saved me some work. I did some small modifications to the headers and included them in a new unit into the Jedi Apilib (JwaWdm.pas).
Let’s take a look at the structure:
ANYSIZE_ARRAY is a constant from the Windows PSDK which is meant to make a dummy array, so here we make actually an array[0..0]. However the compiler permits us to do array[1], array[2] if we turn off range checking so this makes it easier to loop through the array.
The CM_FULL_RESOURCE_DESCRIPTOR describes the resource:
And finally points to a CM_PARTIAL_RESOURCE_LIST structure:
Which in turn points to an array of CM_PARTIAL_RESOURCE_DESCRIPTOR structures:
We are interested in the RDD_MEMORY structure which finally gives us the requested information (Length):
So it’s just a matter of looping through the Partial Descriptor array and summing up the Lengths. However on a x64 (64 bit) Windows the memory length is a 8 bytes instead of 4 byte, so we need to account for that. If we use a 64 bit compiler it will handle this automatically because the ULONG type is 8 bytes then. That will make it impossible though to query remote 32 bit systems from 64 bit or vice versa. So I wondered how Taskmanager handled that, the answer is: IT DOESN’T! See the following example:
Querying locally on 32 bit system:

Now I query the same machine remotely from a 64 bit system:

I think it can only be solved by using two different structures, one for x86 and one for x64. So I introduced the following structures:
// This is for x64
RDD_MEMORY_EX = packed record
Start: PHYSICAL_ADDRESS;
Length: ULONGLONG;
end;
PRDD_MEMORY_EX = ^RDD_MEMORY_EX;
Now we need a generic function to query both x86 and x64 (I use the Environment variable PROCESSOR_ARCHITECTURE to determine if a system is 64bit):
// Prepend \\ to machinename if needed
s := Machine;
if Pos(‘\\’, s) <> 0 then
begin
s := ‘\\’ + s;
end;
{ Connect to (remote) Registry. Please note there is a bug in Windows 2003 SP1
from http://support.microsoft.com/kb/906570: After you apply Microsoft
Windows Server 2003 Service Pack 1 (SP1) or install an x64-based version of
Windows Server 2003, a custom program that uses the RegConnectRegistry
function can no longer access the registry of a remote computer.
I was not able to test if this bug prevents us from reading the value
remotely }
if RegConnectRegistry(PChar(Machine), HKEY_LOCAL_MACHINE, RootKey) <> ERROR_SUCCESS then
begin
Exit;
end;
Reg := TRegistry.Create;
try
Reg.RootKey := RootKey;
{ First we read out the Environment variable PROCESSOR_ARCHITECTURE to
determine is the system is x86 or x64 }
if Reg.OpenKeyReadOnly(EnvKey) then
begin
try
{ Both intel and Amd 64 bit systems have the value AMD64 }
Is64Bit := CompareText(Reg.ReadString(EnvVal), ‘AMD64′) = 0;
// Now open the Physical Memory key
if Reg.OpenKeyReadOnly(PhysMemKey) then
begin
{ Get Value Size in bytes }
cbSize := Reg.GetDataSize(PhysMemVal);
{ Check is Size > 0 }
if cbSize > 0 then begin
{ Allocate and zero Memory }
ResourceListPtr := AllocMem(cbSize);
{ Read data into ResourceListPtr var }
Reg.ReadBinaryData(PhysMemVal, ResourceListPtr^, cbSize);
{ Loop throught the ResourceList(s), usually it’s only 1 }
for Index := 0 to ResourceListPtr^.Count-1 do
begin
{ x64 systems have a slightly different structure because the
memory size parameter is not ULONG but ULONGLONG. To solve this we
assign the PartialDescriptors array to 2 pointers. }
PartDescript := @ResourceListPtr^.List[Index].PartialResourceList.PartialDescriptors; // x86
PartDescriptEx := @ResourceListPtr^.List[Index].PartialResourceList.PartialDescriptors; // x64
for Counter := 0 to ResourceListPtr^.List[Index].PartialResourceList.Count – 1 do
begin
begin
if Is64Bit then { for x64 we use PartDescriptEx }
begin
{ Check if the Resourcetype = Memory and that the memory is
read/write }
if (PartDescriptEx.ResType = CmResourceTypeMemory) and
(PartDescriptEx.Flags = CM_RESOURCE_MEMORY_READ_WRITE) then
begin
Result := Result + PartDescriptEx.Memory.Length;
end;
{ The Inc operator increases the pointer by
SizeOf(PartDescriptEx^) which jumps to the next item in
the array }
Inc(PartDescriptEx);
end
else begin { for x86 we use PartDescript }
{ Check if the Resourcetype = Memory and that the memory is
read/write }
if (PartDescript.ResType = CmResourceTypeMemory) and
(PartDescript.Flags = CM_RESOURCE_MEMORY_READ_WRITE) then
begin
Result := Result + PartDescript.Memory.Length;
end;
{ The Inc operator increases the pointer by
SizeOf(PartDescriptEx^) which jumps to the next item in
the array }
Inc(PartDescript);
end;
end;
end;
end;
end;
end;
except
{ Return 0 on any exception }
Result := 0;
end;
{ Free the memory }
if ResourceListPtr <> nil then FreeMem(ResourceListPtr);
PartDescript := nil;
PartDescriptEx := nil;
end;
finally
{ Close the key }
RegCloseKey(RootKey);
FreeAndNil(Reg);
end;
end;
Leave a reply