如何在各个版本的VC及64位下使用CPUID指令

感谢这位楼主无私奉献http://blog.csdn.net/zyl910/article/details/7588204

前面我们探讨了在16位的DOS实模式下使用CPUID指令(http://www.cnblogs.com/zyl910/archive/2012/05/14/dos16_getcpuid.html)。而现在64位Windows系统已经很流行了,在32/64位模式下如何使用CPUID呢?于是本文介绍了如何在各个版本的VC及64位下使用CPUID指令。

一、推荐使用__cpuid、__cpuidex等Intrinsics函数

  在32位模式下,我们可以使用内嵌汇编来调用cpuid指令。但在64位模式下,VC编译器不支持内嵌汇编。
  于是微软提供了Intrinsics函数——编译器会将Intrinsics函数编译为对应的机器指令,而且同时支持32位和64位。
  例如CPUID指令的对应Intrinsics函数是——

  1. // http://msdn.microsoft.com/en-us/library/hskdteyh.aspx  
  2. void __cpuid(  
  3.    int CPUInfo[4],  
  4.    int InfoType  
  5. );  
  6.   
  7. void __cpuidex(  
  8.    int CPUInfo[4],  
  9.    int InfoType,  
  10.    int ECXValue  
  11. );  
// http://msdn.microsoft.com/en-us/library/hskdteyh.aspx
void __cpuid(
   int CPUInfo[4],
   int InfoType
);

void __cpuidex(
   int CPUInfo[4],
   int InfoType,
   int ECXValue
);



  __cpuidex函数的InfoType参数是CPUID指令的eax参数,即功能ID。ECXValue参数是CPUID指令的ecx参数,即子功能ID。CPUInfo参数用于接收输出的eax, ebx, ecx, edx这四个寄存器。
  早期的CPUID功能只需要一个功能ID参数(eax),这时可以使用__cpuid函数。
  后来CPUID的功能越来越强大,一个功能ID参数(eax)参数不够用,于是加了一个子功能ID(ecx)参数,这时应该采用__cpuidex。


二、用条件编译判断VC编译器对Intrinsics函数的支持性(_MSC_VER)

  在__cpuid、__cpuidex等Intrinsics函数时,会遇到以下问题——
1.低版本的VC编译器没有intrin.h头文件。【注】:只有VC2005(或更高)才拥有intrin.h,支持__cpuid。
2.低版本的VC编译器不支持__cpuidex。【注】:只有VC2008的部分版本及VS2010(或更高)的intrin.h中才有__cpuidex。

  这时可以使用条件编译来判断VC编译器的版本。
  _MSC_VER是微软C/C++编译器——cl.exe编译代码时预定义的一个宏,它的值表示cl的版本,它的类型是“int”。例如——
#if _MSC_VER >=1200 // VC++6.0以上
#if _MSC_VER >=1300 // VC2003以上
#if _MSC_VER >=1400 // VC2005以上
#if _MSC_VER >=1500 // VC2008以上
#if _MSC_VER >=1600 // VC2010以上

  例如发现_MSC_VER大于等于1400时,我们可以#include <intrin.h>。然后再利用_MSC_VER进一步判断__cpuid、__cpuidex的支持性。


三、用条件编译判断64位模式(_WIN64)

  使用_WIN64这个预处理宏可用来判断目标平台是不是64位。
  虽然在编译x64平台的程序时,编译器会自动推导出_WIN64。但是Visual Studio的语法高亮不清楚这些,它有可能仍是按32位代码来做语法高亮。所以,建议还是手动在项目的预处理宏中增加_WIN64。


四、32位下用内嵌汇编实现__cpuidex函数

  在32位模式下,我们可以使用内嵌汇编来实现__cpuidex函数。代码如下——

  1. void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue)  
  2. {  
  3.     if (NULL==CPUInfo)  return;  
  4.     _asm{  
  5.         // load. 读取参数到寄存器  
  6.         mov edi, CPUInfo;   // 准备用edi寻址CPUInfo  
  7.         mov eax, InfoType;  
  8.         mov ecx, ECXValue;  
  9.         // CPUID  
  10.         cpuid;  
  11.         // save. 将寄存器保存到CPUInfo  
  12.         mov [edi], eax;  
  13.         mov [edi+4], ebx;  
  14.         mov [edi+8], ecx;  
  15.         mov [edi+12], edx;  
  16.     }  
  17. }  
void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue)
{
	if (NULL==CPUInfo)	return;
	_asm{
		// load. 读取参数到寄存器
		mov edi, CPUInfo;	// 准备用edi寻址CPUInfo
		mov eax, InfoType;
		mov ecx, ECXValue;
		// CPUID
		cpuid;
		// save. 将寄存器保存到CPUInfo
		mov	[edi], eax;
		mov	[edi+4], ebx;
		mov	[edi+8], ecx;
		mov	[edi+12], edx;
	}
}


 

五、全部代码


  全部代码——

  1. #include <Windows.h>  
  2. #include <stdio.h>  
  3. #include <tchar.h>  
  4.   
  5. #if _MSC_VER >=1400  // VC2005才支持intrin.h  
  6. #include <intrin.h>   // 所有Intrinsics函数  
  7. #endif  
  8.   
  9. char szBuf[64];  
  10. INT32 dwBuf[4];  
  11.   
  12. #if defined(_WIN64)  
  13. // 64位下不支持内联汇编. 应使用__cpuid、__cpuidex等Intrinsics函数。  
  14. #else  
  15. #if _MSC_VER < 1600  // VS2010. 据说VC2008 SP1之后才支持__cpuidex  
  16. void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue)  
  17. {  
  18.     if (NULL==CPUInfo)  return;  
  19.     _asm{  
  20.         // load. 读取参数到寄存器  
  21.         mov edi, CPUInfo;   // 准备用edi寻址CPUInfo  
  22.         mov eax, InfoType;  
  23.         mov ecx, ECXValue;  
  24.         // CPUID  
  25.         cpuid;  
  26.         // save. 将寄存器保存到CPUInfo  
  27.         mov [edi], eax;  
  28.         mov [edi+4], ebx;  
  29.         mov [edi+8], ecx;  
  30.         mov [edi+12], edx;  
  31.     }  
  32. }  
  33. #endif  // #if _MSC_VER < 1600   // VS2010. 据说VC2008 SP1之后才支持__cpuidex  
  34.   
  35. #if _MSC_VER < 1400  // VC2005才支持__cpuid  
  36. void __cpuid(INT32 CPUInfo[4], INT32 InfoType)  
  37. {  
  38.     __cpuidex(CPUInfo, InfoType, 0);  
  39. }  
  40. #endif  // #if _MSC_VER < 1400   // VC2005才支持__cpuid  
  41.   
  42. #endif  // #if defined(_WIN64)  
  43.   
  44. // 取得CPU厂商(Vendor)  
  45. //  
  46. // result: 成功时返回字符串的长度(一般为12)。失败时返回0。  
  47. // pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。  
  48. int cpu_getvendor(char* pvendor)  
  49. {  
  50.     INT32 dwBuf[4];  
  51.     if (NULL==pvendor)  return 0;  
  52.     // Function 0: Vendor-ID and Largest Standard Function  
  53.     __cpuid(dwBuf, 0);  
  54.     // save. 保存到pvendor  
  55.     *(INT32*)&pvendor[0] = dwBuf[1];    // ebx: 前四个字符  
  56.     *(INT32*)&pvendor[4] = dwBuf[3];    // edx: 中间四个字符  
  57.     *(INT32*)&pvendor[8] = dwBuf[2];    // ecx: 最后四个字符  
  58.     pvendor[12] = '\0';  
  59.     return 12;  
  60. }  
  61.   
  62. // 取得CPU商标(Brand)  
  63. //  
  64. // result: 成功时返回字符串的长度(一般为48)。失败时返回0。  
  65. // pbrand: 接收商标信息的字符串缓冲区。至少为49字节。  
  66. int cpu_getbrand(char* pbrand)  
  67. {  
  68.     INT32 dwBuf[4];  
  69.     if (NULL==pbrand)   return 0;  
  70.     // Function 0x80000000: Largest Extended Function Number  
  71.     __cpuid(dwBuf, 0x80000000);  
  72.     if (dwBuf[0] < 0x80000004)   return 0;  
  73.     // Function 80000002h,80000003h,80000004h: Processor Brand String  
  74.     __cpuid((INT32*)&pbrand[0], 0x80000002);    // 前16个字符  
  75.     __cpuid((INT32*)&pbrand[16], 0x80000003);   // 中间16个字符  
  76.     __cpuid((INT32*)&pbrand[32], 0x80000004);   // 最后16个字符  
  77.     pbrand[48] = '\0';  
  78.     return 48;  
  79. }  
  80.   
  81. int _tmain(int argc, _TCHAR* argv[])  
  82. {  
  83.     //__cpuidex(dwBuf, 0,0);  
  84.     //__cpuid(dwBuf, 0);  
  85.     //printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]);  
  86.   
  87.     cpu_getvendor(szBuf);  
  88.     printf("CPU Vendor:\t%s\n", szBuf);  
  89.   
  90.     cpu_getbrand(szBuf);  
  91.     printf("CPU Name:\t%s\n", szBuf);  
  92.   
  93.     return 0;  
  94. }  
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

#if _MSC_VER >=1400	// VC2005才支持intrin.h
#include <intrin.h>	// 所有Intrinsics函数
#endif

char szBuf[64];
INT32 dwBuf[4];

#if defined(_WIN64)
// 64位下不支持内联汇编. 应使用__cpuid、__cpuidex等Intrinsics函数。
#else
#if _MSC_VER < 1600	// VS2010. 据说VC2008 SP1之后才支持__cpuidex
void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue)
{
	if (NULL==CPUInfo)	return;
	_asm{
		// load. 读取参数到寄存器
		mov edi, CPUInfo;	// 准备用edi寻址CPUInfo
		mov eax, InfoType;
		mov ecx, ECXValue;
		// CPUID
		cpuid;
		// save. 将寄存器保存到CPUInfo
		mov	[edi], eax;
		mov	[edi+4], ebx;
		mov	[edi+8], ecx;
		mov	[edi+12], edx;
	}
}
#endif	// #if _MSC_VER < 1600	// VS2010. 据说VC2008 SP1之后才支持__cpuidex

#if _MSC_VER < 1400	// VC2005才支持__cpuid
void __cpuid(INT32 CPUInfo[4], INT32 InfoType)
{
	__cpuidex(CPUInfo, InfoType, 0);
}
#endif	// #if _MSC_VER < 1400	// VC2005才支持__cpuid

#endif	// #if defined(_WIN64)

// 取得CPU厂商(Vendor)
//
// result: 成功时返回字符串的长度(一般为12)。失败时返回0。
// pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。
int cpu_getvendor(char* pvendor)
{
	INT32 dwBuf[4];
	if (NULL==pvendor)	return 0;
	// Function 0: Vendor-ID and Largest Standard Function
	__cpuid(dwBuf, 0);
	// save. 保存到pvendor
	*(INT32*)&pvendor[0] = dwBuf[1];	// ebx: 前四个字符
	*(INT32*)&pvendor[4] = dwBuf[3];	// edx: 中间四个字符
	*(INT32*)&pvendor[8] = dwBuf[2];	// ecx: 最后四个字符
	pvendor[12] = '\0';
	return 12;
}

// 取得CPU商标(Brand)
//
// result: 成功时返回字符串的长度(一般为48)。失败时返回0。
// pbrand: 接收商标信息的字符串缓冲区。至少为49字节。
int cpu_getbrand(char* pbrand)
{
	INT32 dwBuf[4];
	if (NULL==pbrand)	return 0;
	// Function 0x80000000: Largest Extended Function Number
	__cpuid(dwBuf, 0x80000000);
	if (dwBuf[0] < 0x80000004)	return 0;
	// Function 80000002h,80000003h,80000004h: Processor Brand String
	__cpuid((INT32*)&pbrand[0], 0x80000002);	// 前16个字符
	__cpuid((INT32*)&pbrand[16], 0x80000003);	// 中间16个字符
	__cpuid((INT32*)&pbrand[32], 0x80000004);	// 最后16个字符
	pbrand[48] = '\0';
	return 48;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//__cpuidex(dwBuf, 0,0);
	//__cpuid(dwBuf, 0);
	//printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]);

	cpu_getvendor(szBuf);
	printf("CPU Vendor:\t%s\n", szBuf);

	cpu_getbrand(szBuf);
	printf("CPU Name:\t%s\n", szBuf);

	return 0;
}



六、兼容性说明

  VC编译器对32/64位的支持性——
32位:VC6是最早支持编译32位Intrinsics函数的。
64位:VC2005是最早支持编译64位Intrinsics函数的。

  本文方法在32位编译器下的兼容性——
__cpuid:兼容VC6(或更高)。
__cpuidex:兼容VC6(或更高)。

  本文方法在64位编译器下的兼容性——
__cpuid:兼容VC2005(或更高)。
__cpuidex:兼容VC2010(或更高)。

 


参考文献——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《AMD CPUID Specification》. September 2010. http://support.amd.com/us/Embedded_TechDocs/25481.pdf
《__cpuid, __cpuidex》. http://msdn.microsoft.com/en-us/library/hskdteyh.aspx
《__cpuidex》. http://social.msdn.microsoft.com/Forums/en-US/vclanguage/thread/cac9c43b-ed72-4283-baa0-e7cd397591bc
《Predefined Macros》. http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.110).aspx
《预定义_MSC_VER宏》. OwnWaterloo著。http://www.cppblog.com/ownwaterloo/archive/2009/04/15/predefined_macro__MSC_VER.html

 

源码下载——
http://files.cnblogs.com/zyl910/vcgetcpuid.rar

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值