An attempt to bring CPUID to C#

Following from my previous post about detecting CPUID instruction availability, which is as I mentioned, fairly easy to achieve in both C/C++ and assembly language. In fact, it is already a common knowledge mostly to C/C++ and assembly programmers -- this time I will make an attempt to bring this very useful instruction to the .NET world, particularly in C#.

For details and background information about CPUID instruction, please read from this page:
http://www.sandpile.org/ia32/cpuid.htm -- this instruction is particularly useful, if you need to extract CPU information, its features like MMX, 3DNow! SSE, SSE2, SSE3 and type, family and other rich set of information you can't get from Windows API.

C/C++ has an inline assember built into the compilers, there's no need to attempt to bring this command in there, as it is already available via inline assembler. C# however has no inline assembler support; hence we can't call CPUID directly from C# programs. Good thing, C# can interop Win32 unmanaged code, and this is the method I am going to use to bring CPUID into C#.

I already shown you how to detect CPUID availability from my previous post using MASM, hence all we need is to create a dynamic link library. The code I wrote for this DLL is as follows:

; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; CPUID.DLL                                            - start here -
; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

.386
.model flat, stdcall
option casemap:none

include windows.inc

.code
start:

; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; DLL Entry point
; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
DllEntry proc hInstance:HINSTANCE, dwReason:dword, dwReserved:dword
 mov     eax, TRUE
 ret
DllEntry endp

; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; CPUIDIsSupported exported function
; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

CPUIDIsSupported proc uses ebx edx
 pushfd
 pop     eax            ; Get EFLAGS to EAX
 mov     ecx, eax       ; Preserve it in ECX
 
 xor     eax, 200000h   ; Check if CPUID bit can toggle
 push    eax
 popfd                  ; Restore the modified EAX
                        ; to EFLAGS

 pushfd                 ; Get the EFLAGS again
 pop     ebx            ; to EBX
 xor     eax, ebx       ; Has it toggled?
 and     eax, 200000h
 jnz     __not__        ; No? CPUID is not supported
 
 mov     eax, 1
 jmp     _ciis_ret_     ; Yes? CPUID is supported
 
 __not__:
 xor  eax, eax

 _ciis_ret_:
 push     ecx           ; Restore the original EFLAGS
 popfd

 ret
CPUIDIsSupported endp

; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;
__cpuid exported function
; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
.586
__cpuid proc stdcall public uses ebx edi \
                     __funcNumber:dword, \
                     __eax:ptr dword, \
                     __ebx:ptr dword, \
                     __ecx:ptr dword, \
                     __edx:ptr dword

 ; Must support CPUID instruction
 call    CPUIDIsSupported
 dec     eax
 jz      _cpuid_begin_
 
 ; No CPUID instruction?
 xor     eax, eax
 jmp     _cpuid_ret_
 
 _cpuid_begin_:

 ; Call CPUID on the given level in EAX
 mov     eax, __funcNumber
 cpuid
 
 ; Transfer the results back to the out parameters
 mov     edi, __eax
 mov     dword ptr [edi], eax    ; __eax = eax
 mov     edi, __ecx
 mov     dword ptr [edi], ecx    ; __ecx = ecx
 mov     edi, __edx
 mov     dword ptr [edi], edx    ; __edx = edx
 mov     edi, __ebx
 mov     dword ptr [edi], ebx    ; __ebx = ebx
 
 mov     eax, 1

 _cpuid_ret_:
 ret
__cpuid endp

end start
; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; CPUID.DLL                                              - end here -

; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


If you can't understand the code above, it's fine, I don't really think that everybody could understand assembly language. For the beneft of all, I already compiled the program above into a fully working dynamic link library (DLL) you can use from your C# project.

Here are the information about the two exported functions of cpuid.dll:

---------------------------------------------------------------------
CPUIDIsSupported
---------------------------------------------------------------------

Determines whether the CPU is capable of executing CPUID instruction. There's no need to invoke this function explicitly when calling __cpuid function, since __cpuid is already able to detect CPUID instruction availability.


Syntax

   bool
CPUIDIsSupported(void);

Parameters

    
This function has no parameters.

Return

     If CPUID instruction is supported, the return value is true.
     If CPUID instruction is NOT supported, the return value is false.

---------------------------------------------------------------------
__cpuid
---------------------------------------------------------------------

Call CPUID instruction, on given level, and return the result of the registers in its parameters.


Syntax

   bool __cpuid(
      int level,
     
int* eax,
     
int* ebx,
     
int* ecx,
     
int* edx);

Parameters

     level
            [in] The level in which to use when calling CPUID.

     eax
            [out] Receives the return value in EAX register after calling CPUID.

     ebx
            [out] Receives the return value in EBX register after calling CPUID.

     ecx
            [out] Receives the return value in ECX register after calling CPUID.

     edx
            [out] Receives the return value in EDX register after calling CPUID.

Return

     If the function succeeds, the return value is true and the parameters will be filled with
              return values.
     If the function failed, the return value is false and the parameters will be filled with zeros.

Remarks

The function __cpuid acts as the mediator into calling CPUID in assembly level for thos language that has no access to CPU instructions (C#, VB6 etch), it will use the provided level then call the CPUID instructions. When invoking __cpuid function, there's no need to explicitly invoke CPUIDIsSupported function, as the function __cpuid is already calling CPUIDIsSupported to determine internally.

---------------------------------------------------------------------
Here's a wrapper class in C# that will utilize the exported functions of cpuid.dll:
---------------------------------------------------------------------

namespace ChrisVega.CPUID
{
 using System;
 using System.Runtime.InteropServices;
 using System.Text;

 internal class cpuid
 {
  private cpuid()
  {
  }

  [DllImport("cpuid.dll")]
     public static extern bool CPUIDIsSupported();

  [DllImport("cpuid.dll")]
     private unsafe static extern bool __cpuid
            (uint function,
             int* eax,
             int* ebx,
             int* ecx,
             int* edx);

  // Invoke __cpuid function
  public unsafe static bool Invoke
      (uint level,
       out int eax,
       out int ebx,
       out int ecx,
       out int edx)
  {
   int __eax = 0;
   int __ebx = 0;
   int __ecx = 0;
   int __edx = 0;

   if (__cpuid(level, &__eax, &__ebx, &__ecx, &__edx))
   {
    eax = __eax;
    ebx = __ebx;
    ecx = __ecx;
    edx = __edx;

    return true;
   }
   else
   {
    eax = 0;
    ebx = 0;
    ecx = 0;
    edx = 0;

    return false;
   }
  }

  // Convert a DWORD value into ASCII string
  public static string dword2str(int dword)
  {
   int dxl = (dword & 0xff);
   int dxh = (dword & 0xff00) >> 8;
   int dlx = (dword & 0xff0000) >> 16;
   int dhx = (int)((uint)(dword & 0xff000000) >> 24);

   return
    ((char)dxl).ToString() +
    ((char)dxh).ToString() +
    ((char)dlx).ToString() +
    ((char)dhx).ToString();
  }
 }
}

---------------------------------------------------------------------


The above C# class uses pointers to get the response from parameters; when you included it to your project, set the allow unsafe code in your project settings or use /unsafe switch in cs.exe compiler. Also, I named the class as NativeMethods, although it can have a different name.

For an example of how to utilize the C# class into a project, see the attached C# program (coded in Visual Studio 2003 for compatibility reason), here's the screenshot of the attached C# program:




Download the sample project, C# class implementation, and the cpuid.dll (with source), all in the attached zip file: cpuid.zip

For the complete output, here's a result of the sample C# program when executed on an Intel Pentium IV 1.60 GHz AT/AT Compatible machine:

CPUID is supported.

-----------------------------------------

Invoking CPUID, EAX=0

Results:
EAX: 0x00000002
EBX: 0x49656E69
ECX: 0x756E6547
EDX: 0x6C65746E

Max Supported Levels: 2
VendorId in EBX:EDX:ECX: GenuineIntel

-----------------------------------------

Invoking CPUID, EAX=1

Results:
EAX: 0x00000F24
EBX: 0x3FEBFBFF
ECX: 0x00010809
EDX: 0x00000000

Stepping: 0x4
Model: 0x4
Family: 0xE
Type: 0x1
Extended Model: 0x0
Extended Family: 0x0

Brand ID: 0x9
CLFLUSH: 0x10
APIC ID: 0x0
Logical Processor Count: 0x2

SSE3, MXCSR, CR4.OSXMMEXCPT, #XF: No
MON: No
DSCPL: No
VMX: No
EST: No
TM2: No
Context ID (CID): No
CMPXCHG16B (CX16): No
ETPRD: No

FPU: Yes
CR4.VME/PVI, EFLAGS.VIP/VIF: Yes
CR4.DE, DR7.RW=10b: Yes
PDE.PS, PDE/PTE.res, CR4.PSE: Yes
TSC, RDTSC, CR4.TSD: Yes
MSRs, RDMSR/WRMSR: Yes
64bit PDPTE/PDE/PTEs, CR4.PAE: Yes
MCAR/MCTR MSRs, CR4.MCE: Yes
CMPXCHG8B : Yes
APIC: Yes
SYSENTER/SYSEXIT, SEP_* MSRs : Yes
MTRR* MSRs : Yes
PDE/PTE.G, CR4.PGE : Yes
MCG_*/MCn_* MSRs, CR4.MCE : Yes
CMOVcc : Yes
PAT MSR, PDE/PTE.PAT : Yes
4 MB PDE bits 16..13, CR4.PSE : Yes
PSN, MISC_CTL.PSND  : No
CLFLUSH : Yes
Debug Trace and EMON Store MSRs : Yes
THERM_CONTROL MSR : Yes
MMX : Yes
FXSAVE/FXRSTOR, CR4.OSFXSR : Yes
SSE, MXCSR, CR4.OSXMMEXCPT : Yes
SSE2, MXCSR, CR4.OSXMMEXCPT : Yes
Selfsnoop : Yes
Hyper-Threading Technology (HTT) : Yes
TM1 : Yes
IA-64, JMPE Jv, JMPE Ev : No
Pending Break Event : No

-----------------------------------------

Invoking CPUID, EAX=2

Results:
EAX: 0x665B5001
EBX: 0x007B7040
ECX: 0x00000000
EDX: 0x00000000


-----------------------------------------

Invoking CPUID, EAX=3

Results:
EAX: 0x665B5001
EBX: 0x007B7040
ECX: 0x00000000
EDX: 0x00000000

Serial Number (EBX:ECX:EDX): 007B7040

-----------------------------------------

Invoking CPUID, EAX=4

Results:
EAX: 0x665B5001
EBX: 0x007B7040
ECX: 0x00000000
EDX: 0x00000000


-----------------------------------------

Invoking CPUID, EAX=5

Results:
EAX: 0x665B5001
EBX: 0x007B7040
ECX: 0x00000000
EDX: 0x00000000


-----------------------------------------

Invoking CPUID, EAX=0x80000000

Results:
EAX: 0x80000004
EBX: 0x00000000
ECX: 0x00000000
EDX: 0x00000000


-----------------------------------------

Invoking CPUID, EAX=0x80000001

Results:
EAX: 0x00000000
EBX: 0x00000000
ECX: 0x00000000
EDX: 0x00000000


-----------------------------------------

Invoking CPUID, EAX=0x80000002

Results:
EAX: 0x20202020
EBX: 0x6E492020
ECX: 0x20202020
EDX: 0x20202020
Processor Name, part 1 (EAX:EBX:ECX:EDX):
              In

-----------------------------------------

Invoking CPUID, EAX=0x80000003

Results:
EAX: 0x286C6574
EBX: 0x52286D75
ECX: 0x50202952
EDX: 0x69746E65
Processor Name, part 2 (EAX:EBX:ECX:EDX):
tel(R) Pentium(R

-----------------------------------------

Invoking CPUID, EAX=0x80000004

Results:
EAX: 0x20342029
EBX: 0x007A4847
ECX: 0x20555043
EDX: 0x30362E31
Processor Name, part 3 (EAX:EBX:ECX:EDX):
) 4 CPU 1.60GHz


Now you can write a program in C#, that other high-level language can do, CPU identification Smile [:)]

The next time somebody told you that you can't write a program in C# that can detect detailed CPU information, then point them to this blog post.

Cheers,

-chris

Attachment: cpuid.zip
Published 04-07-2006 7:26 AM by cvega
Filed under: ,

Comments

# Informazioni CPU & Ram - MasterDrive.it - Information Technology Developers Community

Pingback from  Informazioni CPU & Ram - MasterDrive.it - Information Technology Developers Community

# x86/x64 CPUID in C# | C Language Development | C Programming Language Tutorial

Pingback from  x86/x64 CPUID in C# | C Language Development | C Programming Language Tutorial