首页 > 技术精文 > C/C++ > VC获取硬盘序列号
2016
12-27

VC获取硬盘序列号

【注意】本文代码可以在XP系统下成功,但在 WIN7 系统中不行,因为 WIN7 对直接打开磁盘驱动器做了限制,必须要管理员授权。否则以普通用户身份运行会在 CreateFile 时返回 INVALID_HANDLE_VALUE(5:没有权限),从而无法获取硬盘序列号。如何在 WIN7 下面不需要以管理员身份运行就可以得到硬盘序列号呢,因为涉及一些软件保护的敏感信息,故暂不在此处发表。

#include "stdafx.h"

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

#include <windows.h>
#include <winioctl.h>

//
BOOL GetPhyDriveSerial(LPTSTR pModelNo, LPTSTR pSerialNo);
void ToLittleEndian(PUSHORT pWords, int nFirstIndex, int nLastIndex, LPTSTR pBuf);
void TrimStart(LPTSTR pBuf);

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR szModelNo[48], szSerialNo[24];
    if(GetPhyDriveSerial(szModelNo, szSerialNo))
    {
        _tprintf(_T(" : 0 1 2\n"));
        _tprintf(_T(" : 012345678901234567890123456789\n"));
        _tprintf(_T("Model No: %s\n"), szModelNo);
        _tprintf(_T("Serial No: %s\n"), szSerialNo);
        TrimStart(szSerialNo);
        _tprintf(_T("Serial No: %s\n"), szSerialNo);
    }
    else
    {
        _tprintf(_T("Failed.\n"));
    }
    getchar();
    return 0;
}

//
// Model Number: 40 ASCII Chars
// SerialNumber: 20 ASCII Chars
//
BOOL GetPhyDriveSerial(LPTSTR pModelNo, LPTSTR pSerialNo)
{
    //-1是因为 SENDCMDOUTPARAMS 的结尾是 BYTE bBuffer[1];
    BYTE IdentifyResult[sizeof(SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE - 1];
    DWORD dwBytesReturned;
    GETVERSIONINPARAMS get_version;
    SENDCMDINPARAMS send_cmd = { 0 };

    HANDLE hFile = CreateFile(_T("\\\\.\\PHYSICALDRIVE0"), GENERIC_READ | GENERIC_WRITE,    
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if(hFile == INVALID_HANDLE_VALUE)
        return FALSE;

    //get version
    DeviceIoControl(hFile, SMART_GET_VERSION, NULL, 0,
        &get_version, sizeof(get_version), &dwBytesReturned, NULL);

    //identify device
    send_cmd.irDriveRegs.bCommandReg = (get_version.bIDEDeviceMap & 0x10)? ATAPI_ID_CMD : ID_CMD;
    DeviceIoControl(hFile, SMART_RCV_DRIVE_DATA, &send_cmd, sizeof(SENDCMDINPARAMS) - 1,
        IdentifyResult, sizeof(IdentifyResult), &dwBytesReturned, NULL);
    CloseHandle(hFile);

    //adjust the byte order
    PUSHORT pWords = (USHORT*)(((SENDCMDOUTPARAMS*)IdentifyResult)->bBuffer);
    ToLittleEndian(pWords, 27, 46, pModelNo);
    ToLittleEndian(pWords, 10, 19, pSerialNo);
    return TRUE;
}

//把WORD数组调整字节序为little-endian,并滤除字符串结尾的空格。
void ToLittleEndian(PUSHORT pWords, int nFirstIndex, int nLastIndex, LPTSTR pBuf)
{
    int index;
    LPTSTR pDest = pBuf;
    for(index = nFirstIndex; index <= nLastIndex; ++index)
    {
        pDest[0] = pWords[index] >> 8;
        pDest[1] = pWords[index] & 0xFF;
        pDest += 2;
    }    
    *pDest = 0;
    
    //trim space at the endof string; 0x20: _T(' ')
    --pDest;
    while(*pDest == 0x20)
    {
        *pDest = 0;
        --pDest;
    }
}

//滤除字符串起始位置的空格
void TrimStart(LPTSTR pBuf)
{
    if(*pBuf != 0x20)
        return;

    LPTSTR pDest = pBuf;
    LPTSTR pSrc = pBuf + 1;
    while(*pSrc == 0x20)
        ++pSrc;

    while(*pSrc)
    {
        *pDest = *pSrc;
        ++pDest;
        ++pSrc;
    }
    *pDest = 0;
}

在代码开头,有一些宏定义了比如 _WIN32_WINNT 的版本,它们是来自 win32 程序的 stdafx.h 里,出现在包含 windows.h 等头文件之前,因为 console 程序 IDE 不会自动添加这些内容,而没有这些宏,代码中的一些结构体的定义就不会被编译到。所以我们必须手工加上这些宏定义。

序列号开头可能有空格补充,所以我又用 TrimStart 这个辅助函数,可以去掉字符串开头的空格。
硬件厂商应该保证,ModelNumber + SerialNumber 组合在一起是唯一标识,不会重复。
上面的代码产生如下输出:

         : 0         1         2
         : 012345678901234567890123456789
Model  No: WDC WD1600AAJS-60M0A0
Serial No:     WD-WCAV30353688
Serial No: WD-WCAV30353688

 

最后编辑:
作者:小企鹅
这个作者貌似有点懒,什么都没有留下。
捐 赠如果您觉得这篇文章有用处,请支持作者!鼓励作者写出更好更多的文章!

VC获取硬盘序列号》有 1 条评论

  1. 小企鹅 说:
    
    
    //把WORD数组调整字节序为little-endian,并滤除字符串结尾的空格。
    void ToLittleEndian(PUSHORT pWords, int nFirstIndex, int nLastIndex, LPTSTR pBuf)
    {
    	int index;
    	LPTSTR pDest = pBuf;
    	for(index = nFirstIndex; index <= nLastIndex; ++index)
    	{
    		pDest[0] = pWords[index] >> 8;
    		pDest[1] = pWords[index] & 0xFF;
    		pDest += 2;
    	}    
    	*pDest = 0;
    
    	//trim space at the endof string; 0x20: _T(' ')
    	--pDest;
    	while(*pDest == 0x20)
    	{
    		*pDest = 0;
    		--pDest;
    	}
    }
    
    //滤除字符串起始位置的空格
    void TrimStart(LPTSTR pBuf)
    {
    	if(*pBuf != 0x20)
    		return;
    
    	LPTSTR pDest = pBuf;
    	LPTSTR pSrc = pBuf + 1;
    	while(*pSrc == 0x20)
    		++pSrc;
    
    	while(*pSrc)
    	{
    		*pDest = *pSrc;
    		++pDest;
    		++pSrc;
    	}
    	*pDest = 0;
    }
    
    string GetPhyDriveSerial()
    {
    	//-1是因为 SENDCMDOUTPARAMS 的结尾是 BYTE bBuffer[1];
    	BYTE IdentifyResult[sizeof(SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE - 1];
    	DWORD dwBytesReturned;
    	GETVERSIONINPARAMS get_version;
    	SENDCMDINPARAMS send_cmd = { 0 };
    
    	HANDLE hFile = CreateFile(_T("\\\\.\\PHYSICALDRIVE0"), GENERIC_READ | GENERIC_WRITE,    
    		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    	if(hFile == INVALID_HANDLE_VALUE)
    		return FALSE;
    
    	//get version
    	DeviceIoControl(hFile, SMART_GET_VERSION, NULL, 0,
    		&get_version, sizeof(get_version), &dwBytesReturned, NULL);
    
    	//identify device
    	send_cmd.irDriveRegs.bCommandReg = (get_version.bIDEDeviceMap & 0x10)? ATAPI_ID_CMD : ID_CMD;
    	DeviceIoControl(hFile, SMART_RCV_DRIVE_DATA, &send_cmd, sizeof(SENDCMDINPARAMS) - 1,
    		IdentifyResult, sizeof(IdentifyResult), &dwBytesReturned, NULL);
    	CloseHandle(hFile);
    
    	//adjust the byte order
    	PUSHORT pWords = (USHORT*)(((SENDCMDOUTPARAMS*)IdentifyResult)->bBuffer);
    	TCHAR szModelNo[48], szSerialNo[24];
    	ToLittleEndian(pWords, 27, 46, szModelNo);
    	ToLittleEndian(pWords, 10, 19, szSerialNo);
    	TrimStart(szModelNo);
    	return string(szModelNo)+string(szSerialNo);
    }
    

    简单一点的。

留下一个回复