Thursday, September 24, 2009

GPU-Z reader.... cause I can!

While I was looking for something, I found the data structure that GPU-Z uses in its shared memory file... and given I was doing this type of thing anyway, I figured why not?

So the following is more or less identical code to the SpeedFan code.. at least, identical in function. There were a number of little gotchas that made it a little harder than I was expecting. The documented structure is here. Kudos to the author for making it available. It is, of course, in C, and we want to access it in C#. So, we need to translate the structure into something that the Marshalling code will understand.

First a couple of constants. Nothing tricky here.

const String SHMEM_NAME="GPUZShMem";
const int MAX_RECORDS = 128;


Next up, the first structure, the 'record':

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct GPUZ_RECORD
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string key;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string value;
};

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct GPUZ_SENSOR_RECORD
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string unit;

public UInt32 digits;
public double value;
};

A few important things to note here. The original type for key, value etc was WCHAR[], which means its a unicode string, not just a normal C string. So, its very important that we specify the CharSet explicitly. We haven't had to do that yet, but in this case, we need it.

Next, the WCHAR array is a fixed length and not just null terminated. So, to ensure we use enough bytes, we specify SizeConst, which indicates the number of array elements (not the number of bytes).

Lastly, we use string here rather than StringBuilder. We have specified the size, so the Marshalling code knows how many bytes to read, and knows how to create a string.

Now the main data structure:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class GPUZ_SH_MEM
{
public UInt32 version; // Version number, 1 for the struct here
public Int32 busy; // Is data being accessed?
public UInt32 lastUpdate; // GetTickCount() of last update
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_RECORDS)]
public GPUZ_RECORD[] data;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_RECORDS)]
public GPUZ_SENSOR_RECORD[] sensors;

public GPUZ_SH_MEM()
{
data = new GPUZ_RECORD[MAX_RECORDS];
sensors = new GPUZ_SENSOR_RECORD[MAX_RECORDS];
}
}

This isn't actually much different. No strings here, so we don't need to specify the CharSet, but once again we need to use SizeConst for the fixed length arrays. We also supply a constructor, to ensure the memory is allocated for the member arrays.

And that, believe it not, was the difficult bit. A failure in PtrToStruct can have some strange affects... presumably you end up overwriting random bits of memory if the sizes are not matched up, so its doubly important to get it right, as it could potentially mean random crashes in your application, without any indication why.

Full code is here. Note that in both this and the SpeedFan code, GetData() just returns the data structure. This isn't exactly the most encapsulated way to do it... but it keeps the example simple.

No comments:

Post a Comment