Calling the CPUID instruction from managed C# code

Posted: (EET/GMT+2)

 

If you recall my earlier post from March regarding the CPUID instruction of Intel's x86 processors, I mentioned that you cannot call this instruction directly from C#. However, you can do this by using a native code (Win32 code) DLL that you simply call from C#. Here's how to do that briefly. What you need is Borland Delphi and Visual Studio 2005. I used Delphi version 2006 (Borland Developer Studio or BDS).

The first step is to create the library that you are going to call from C#. The code shown here is very similar to that in my March post.

library CPUID_InfoLib;

{$ALIGN 4}

{
Source:

"Intel Processor Identification and the CPUID Instruction"
- Application Note 485, January 2006.

http://developer.intel.com/
}

type
  ProcessorInfo = record
    MaximumBasicFunction    : cardinal;
    MaximumExtendedFunction : cardinal;
    VendorID                : array[0..12] of Char;
    Signature               : cardinal;
    SupportsMMX             : boolean;
    SupportsSSE             : boolean;
    SupportsSSE2            : boolean;
    SupportsSSE3            : boolean;
    SupportsHyperThreading  : boolean;
    SupportsIA64            : boolean;
    SupportsXDBit           : boolean;
    SupportsEMT64           : boolean;
  end;

var
  { globals used by the ReadProcessorInfo function }
  MBF,MEF     : cardinal;
  Sig         : cardinal;
  A,B,C       : cardinal;
  FF1,FF2,FF3 : cardinal;

function ReadProcessorInfo : ProcessorInfo; stdcall;
begin
  {$REGION 'cpuid'}
  asm
    { save registers used by the stdcall calling convention }
    push eax
    push ebx
    push ecx
    push edx
    { call CPUID }
    xor eax,eax
    cpuid
    mov MBF,eax
    { vendor ID }
    mov A,ebx
    mov B,edx
    mov C,ecx
    { signature & feature flags 1-2 }
    mov eax,1
    cpuid
    mov Sig,eax
    mov FF1,edx
    mov FF2,ecx
    { extended functions }
    mov eax,$8000000
    cpuid
    mov MEF,eax
    { feature flags 3 }
    mov eax,$8000001
    cpuid
    mov FF3,edx
    { restore }
    pop edx
    pop ecx
    pop ebx
    pop eax
  end;
  {$ENDREGION}
  {$REGION 'parse results'}
  Result.MaximumBasicFunction := MBF;
  Result.MaximumExtendedFunction := MEF;
  Result.Signature := Sig;
  Result.VendorID[0] := Chr((A and $000000FF) shr 0);
  Result.VendorID[1] := Chr((A and $0000FF00) shr 8);
  Result.VendorID[2] := Chr((A and $00FF0000) shr 16);
  Result.VendorID[3] := Chr((A and $FF000000) shr 24);

  Result.VendorID[4] := Chr((B and $000000FF) shr 0);
  Result.VendorID[5] := Chr((B and $0000FF00) shr 8);
  Result.VendorID[6] := Chr((B and $00FF0000) shr 16);
  Result.VendorID[7] := Chr((B and $FF000000) shr 24);

  Result.VendorID[8] := Chr((C and $000000FF) shr 0);
  Result.VendorID[9] := Chr((C and $0000FF00) shr 8);
  Result.VendorID[10] := Chr((C and $00FF0000) shr 16);
  Result.VendorID[11] := Chr((C and $FF000000) shr 24);
  Result.VendorID[12] := #0;
  {$ENDREGION}
  {$REGION 'flags'}
  Result.SupportsMMX := (FF1 and (1 shl 23)) <> 0;
  Result.SupportsSSE := (FF1 and (1 shl 25)) <> 0;
  Result.SupportsSSE2 := (FF1 and (1 shl 26)) <> 0;
  Result.SupportsSSE3 := (FF2 and (1 shl 0)) <> 0;
  Result.SupportsHyperThreading := (FF1 and (1 shl 28)) <> 0;
  Result.SupportsIA64 := (FF1 and (1 shl 30)) <> 0;
  Result.SupportsXDBit := (FF3 and (1 shl 20)) <> 0;
  Result.SupportsEMT64 := (FF3 and (1 shl 29)) <> 0;
  {$ENDREGION}
end;

exports
   ReadProcessorInfo;

begin
end.

Once you have this code compiled with Delphi, the result is a DLL file. The DLL exports a function that you can call. Now, start a new C# project and add the following code to it:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace Show_CPUID
{
    [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi)]
    public struct ProcessorInfo
    {
        public uint MaximumBasicFunction;
        public uint MaximumExtendedFunction;
        
        [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 13)]
        public string VendorID;

        public uint Signature;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsMMX;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsSSE;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsSSE2;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsSSE3;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsHyperThreading;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsIA64;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsXDBit;

        [MarshalAs(UnmanagedType.I1)]
        public bool SupportsEMT64;

        public string ToString()
        {
            string format = "MaximumBasicFunction = {0}\r\n" +
                "MaximumExtendedFunction = {1}\r\n" +
                "VendorID = \"{2}\"\r\n" +
                "Signature = {3}\r\n" +
                "SupportsMMX = {4}\r\n" +
                "SupportsSSE = {5}\r\n" +
                "SupportsSSE2 = {6}\r\n" +
                "SupportsSSE3 = {7}\r\n" +
                "SupportsHyperThreading = {8}\r\n" +
                "SupportsIA64 = {9}\r\n" +
                "SupportsXDBit = {10}\r\n" +
                "SupportsEMT64 = {11}\r\n";
            return string.Format(format, MaximumBasicFunction,
                MaximumExtendedFunction, VendorID, Signature,
                SupportsMMX, SupportsSSE, SupportsSSE2, SupportsSSE3,
                SupportsHyperThreading, SupportsIA64,
                SupportsXDBit, SupportsEMT64);
        }
    }

    public class CPUID_Info
    {
        [DllImport("CPUID_InfoLib.dll",CallingConvention=CallingConvention.StdCall)]
        public static extern void ReadProcessorInfo(ref ProcessorInfo pi);
    }
   
}

The idea is that the DLL returns a record (in Delphi speak) to the C# application, but you must let C# to know how to marshal the values correctly between Win32 and .NET. Similarly, you need to use a DllImport attribute to your managed code so that the compiler knows where to find the function to read the CPUID data. Here, the attribute specified the name of the DLL file and the calling convention (here, StdCall).

Finally, to call the function, all you need to do is:

ProcessorInfo pi = new ProcessorInfo();
CPUID_Info.ReadProcessorInfo(ref pi);
MessageBox.Show(pi.ToString());

There results are shown here:

Good luck!