取得MAC地址的4种方法

 取得MAC地址的4种方法

 
时间: 2009.02.26 13:25:00 
标签:  

有需求才有创造,有了问题才会想着去解决,那么我这里的获取MAC地址的第4种方法 也是在这种情况下产生的。因为公司有一个服务器产品,要写一个注册模块,而注册模块需要获取硬件信息,而硬件信息有很多,可以是硬盘序列号,CPU序列 号,和网卡MAC,我首先使用的是硬盘序列号,因为GOOGLE一下一大堆,我要感谢所有这些无私奉献自己智慧结晶的可爱的人。

很快地写 完一个注册模块,经过几个PC机,笔记本的测试,一切OK,以为可以run anywhere了,就提交给项目组了,以为万事大吉,过了很长时间,产品出炉了,拿出去用了,可以用户那边根本取不到机器信息。刚开始还纳闷,但是后来 得知用户那边用的是服务器是SCIS硬盘。后来还是找不到既能获取普通硬盘的序列号也能获取SCIS硬盘的序列号通用的资料。

因为这个原 因我后来就转向网卡的MAC,应该说网卡的MAC获取也很方便的,因为有那些可爱的人的无私奉献,我通过GOOGLE找到了《取得系统中网卡MAC地址的 三种方法》,URL为: http://dev.csdn.net/develop/article/7/7609.shtm. 文章写的很好,写的非常有条理,在这里我还要向作者Borland 和译者cker表示最诚挚的谢意。(文章附于最后(转载者注))

   看了这个文章,我又很快地写好了一个注册模块,我用的是第1种方法,是由网卡的MAC为基本信息的,这次我先还是在几个PC机上做测试,不过这次比较幸 运,问题马上就出现了,在一个装有防火墙软件的机器上无法获取MAC,原因cker已经说明了,是防火墙将文件共享服务关闭了,这样就获取不了MAC地 址。

而使用第二种,根本就不是那回事,我在自己的开发机上就出事了,当然也不能用了。

第三种方法用的事SNMP,但不是每个机器都是安装了这个协议的,因此也不是一种通用的好方法。

在 三种方法都没有办法行得通的情况下,一天我在看一本LINUX书的时候看到了管道,可以将输出重定向到管道,哈哈,这个时候也就想到了,用命令行 config /all来获取网卡相关的信息,然后我重定向到管道,就可以获取各种和网卡相关的信息了,哈哈,于是这第四种方法也就出炉了。

这种方法的优点是你只要能够在机器上执行“ipconfig /all”命令你就可以得到MAC地址,而这个命令在所有的网卡可用的情况下都是可用的。

另外你要遍历所有的网卡MAC,那么只需提取出所有的MAC段即可。

程序的过程也清晰简单:

1. 创建一个无名管道。

2. 创建一个IPCONFIG 的进程,并将输出重定向到管道。

3. 从管道获取命令行返回的所有信息放入缓冲区lpszBuffer。

4. 从缓冲区lpszBuffer中获得抽取出MAC地址串。

通过我这个列子你也可以用来获取可以通过命令行方式来取得的信息,比如说主机名,IP地址等等。

   源代码:

文件 GetMac.cpp:

//
// 描述: 通过命令行方式得到MAC地址
// 作者: 郭洪锋
// 日期: 2005年7月1日

// email: guohongfeng@gmail.com
/
#include <string>
#include <iostream>
using namespace std;

//命令行输出缓冲大小
const long MAX_COMMAND_SIZE = 10000;

//获取MAC命令行
char szFetCmd[] = "ipconfig /all";
//网卡MAC地址的前导信息
const string str4Search = "Physical Address. . . . . . . . . : ";

//用命令行方式获取网卡MAC地址
BOOL GetMacByCmd(char *lpszMac);


// 函数名: GetMacByCmd(char *lpszMac)
// 参数:
//      输入: void
//      输出: lpszMac,返回的MAC地址串
// 返回值:
//      TRUE:  获得MAC地址。
//      FALSE: 获取MAC地址失败。
// 过程:
//      1. 创建一个无名管道。
//      2. 创建一个IPCONFIG 的进程,并将输出重定向到管道。
//      3. 从管道获取命令行返回的所有信息放入缓冲区lpszBuffer。
//      4. 从缓冲区lpszBuffer中获得抽取出MAC串。
//
//  提示:可以方便的由此程序获得IP地址等其他信息。
//        对于其他的可以通过其他命令方式得到的信息只需改变strFetCmd 和
//        str4Search的内容即可。
///

BOOL GetMacByCmd(char *lpszMac)
{
//初始化返回MAC地址缓冲区
memset(lpszMac, 0x00, sizeof(lpszMac));
BOOL bret;

 SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe,hWritePipe;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

//创建管道
bret = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
if(!bret)
{
return FALSE;
}

 //控制命令行窗口信息
STARTUPINFO si;
//返回进程信息
PROCESS_INFORMATION pi;

    si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.wShowWindow = SW_HIDE; //隐藏命令行窗口
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

 //创建获取命令行进程
bret = CreateProcess (NULL, szFetCmd, NULL, NULL, TRUE, 0, NULL,
NULL, &si, &pi );

char szBuffer[MAX_COMMAND_SIZE+1]; //放置命令行输出缓冲区
string strBuffer;

 if (bret)
{
WaitForSingleObject (pi.hProcess, INFINITE);
unsigned long count;
CloseHandle(hWritePipe);

  memset(szBuffer, 0x00, sizeof(szBuffer));
bret  =  ReadFile(hReadPipe,  szBuffer,  MAX_COMMAND_SIZE,  &count,  0);
if(!bret)
{
//关闭所有的句柄
CloseHandle(hWritePipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hReadPipe);
return FALSE;
}
else
{
strBuffer = szBuffer;
long ipos;
ipos = strBuffer.find(str4Search);

   //提取MAC地址串
strBuffer = strBuffer.substr(ipos+str4Search.length());
ipos = strBuffer.find("/n");
strBuffer = strBuffer.substr(0, ipos);
}

}

 memset(szBuffer, 0x00, sizeof(szBuffer));
strcpy(szBuffer, strBuffer.c_str());

 //去掉中间的“00-50-EB-0F-27-82”中间的'-'得到0050EB0F2782
int j = 0;
for(int i=0; i<strlen(szBuffer); i++)
{
if(szBuffer[i] != '-')
{
lpszMac[j] = szBuffer[i];
j++;
}
}

 //关闭所有的句柄
CloseHandle(hWritePipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hReadPipe);
return TRUE;

}


文件GetMacByCmd.cpp:

#include "stdafx.h"
#include <iostream.h>
#include <string>

#include "windows.h"
using namespace std;

extern BOOL GetMacByCmd(char *lpszMac);


void main (int argc, char *argv[])
{
char lpszMac[128];
memset(lpszMac, 0x00, sizeof(lpszMac));

 //获取MAC
GetMacByCmd(lpszMac);

 //打印出MAC
cout << lpszMac << endl;


FILE *fp = NULL;
fp = fopen("c://1.txt", "w");
fwrite(lpszMac, sizeof(char), strlen(lpszMac), fp);
fclose(fp);
}

第 一种方法使用Microsoft的Netbios API。 这是一套通过Winsock提供底层网络支持的命令。使用Netbios的最大缺点是您必须在系统中安装了Netbios服务(如果您在windows网 络中启用了文件共享的话,这就不是问题了)。除此此外,这种方法又快又准确。

Netbios API只包括了一个函数,就叫做Netbios。这个函数使用网络控制块(network control block)结构作为参数,这个结构告诉函数要做什么。结构的定义如下:
  typedef struct _NCB {
    UCHAR  ncb_command;
    UCHAR  ncb_retcode;
    UCHAR  ncb_lsn;
    UCHAR  ncb_num;
    PUCHAR ncb_buffer;
    WORD   ncb_length;
    UCHAR  ncb_callname[NCBNAMSZ];
    UCHAR  ncb_name[NCBNAMSZ];
    UCHAR  ncb_rto;
    UCHAR  ncb_sto;
    void (CALLBACK *ncb_post) (struct _NCB *);
    UCHAR  ncb_lana_num;
    UCHAR  ncb_cmd_cplt;
 #ifdef _WIN64
    UCHAR  ncb_reserve[18];
 #else
    UCHAR  ncb_reserve[10];
 #endif
    HANDLE ncb_event;
} NCB, *PNCB;

 

重点在于ncb_command 成员。这个成员告诉Netbios该作什么。我们使用三个命令来探测MAC地址。他们在MSDN的定义如下:
命令描述:
NCBENUM Windows NT/2000: 列举系统中网卡的数量。使用此命令后,ncb_buffer成员指向由LANA_ENUM结构填充的缓冲区。
NCBENUM 不是标准的 NetBIOS 3.0 命令。

NCBRESET 重置网卡。网卡在接受新的NCB命令之前必须重置。
NCBASTAT 接受本地或远程接口卡的状态。使用此命令后,ncb_buffer成员指向由ADAPTER_STATUS结构填充的缓冲区,随后是NAME_BUFFER结构的数组。

下面就是取得您系统MAC地址的步骤:
1》列举所有的接口卡。
2》重置每块卡以取得它的正确信息。
3》查询接口卡,取得MAC地址并生成标准的冒号分隔格式。

下面就是实例源程序。
netbios.cpp

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>

using namespace std;
#define bzero(thing,sz) memset(thing,0,sz)

bool GetAdapterInfo(int adapter_num, string &mac_addr)
{
  // 重置网卡,以便我们可以查询
  NCB Ncb;
  memset(&Ncb, 0, sizeof(Ncb));
  Ncb.ncb_command = NCBRESET;
  Ncb.ncb_lana_num = adapter_num;
  if (Netbios(&Ncb) != NRC_GOODRET) {
    mac_addr = "bad (NCBRESET): ";
    mac_addr += string(Ncb.ncb_retcode);
    return false;
  }

  // 准备取得接口卡的状态块
  bzero(&Ncb,sizeof(Ncb);
  Ncb.ncb_command = NCBASTAT;
  Ncb.ncb_lana_num = adapter_num;
  strcpy((char *) Ncb.ncb_callname, "*");
  struct ASTAT
  {
    ADAPTER_STATUS adapt;
    NAME_BUFFER NameBuff[30];
  } Adapter;
  bzero(&Adapter,sizeof(Adapter));
  Ncb.ncb_buffer = (unsigned char *)&Adapter;
  Ncb.ncb_length = sizeof(Adapter);

  // 取得网卡的信息,并且如果网卡正常工作的话,返回标准的冒号分隔格式。
  if (Netbios(&Ncb) == 0)
  {
    char acMAC[18];
    sprintf(acMAC, "%02X:%02X:%02X:%02X:%02X:%02X",
            int (Adapter.adapt.adapter_address[0]),
            int (Adapter.adapt.adapter_address[1]),
            int (Adapter.adapt.adapter_address[2]),
            int (Adapter.adapt.adapter_address[3]),
            int (Adapter.adapt.adapter_address[4]),
            int (Adapter.adapt.adapter_address[5]));
    mac_addr = acMAC;
    return true;
  }
  else
  {
    mac_addr = "bad (NCBASTAT): ";
    mac_addr += string(Ncb.ncb_retcode);
    return false;
  }
}

int main()
{
  // 取得网卡列表
  LANA_ENUM AdapterList;
  NCB Ncb;
  memset(&Ncb, 0, sizeof(NCB));
  Ncb.ncb_command = NCBENUM;
  Ncb.ncb_buffer = (unsigned char *)&AdapterList;
  Ncb.ncb_length = sizeof(AdapterList);
  Netbios(&Ncb);

  // 取得本地以太网卡的地址
  string mac_addr;
  for (int i = 0; i < AdapterList.length - 1; ++i)
  {
    if (GetAdapterInfo(AdapterList.lana[i], mac_addr))
    {
      cout << "Adapter " << int (AdapterList.lana[i]) <<
              "'s MAC is " << mac_addr << endl;
    }
    else
    {
      cerr << "Failed to get MAC address! Do you" << endl;
      cerr << "have the NetBIOS protocol installed?" << endl;
      break;
    }
  }

  return 0;
}


file://---------------------------------------------------------------------------


第二种方法-使用COM GUID API
这种方法使用COM API创建一个GUID(全局唯一标识符)并从那里继承MAC地址。GUID通常用来标识COM组件以及系统中的其他对象。它们是由MAC地址(结合其他东西)计算得来的,表面上MAC地址就包含在其中。我说表面上是因为事实上并没有包含。
我提供这种方法更多的是为了作为反面教材。您也许用这种方法能够得到MAC地址,但有时候您只会得到随机的十六进制数值。
下面的例子十分简单,无需多讲。我们使用CoCreateGuid创建GUID,并将最后六个字节放入字符串中。它们可能是MAC地址,但并不是必然的。

uuid.cpp
#include <windows.h>
#include <iostream>
#include <conio.h>

using namespace std;

int main()
{
    cout << "MAC address is: ";

    // 向COM要求一个UUID。如果机器中有以太网卡,
    // UUID最后的六个字节(Data4的2-7字节)应该是本地以太网卡的MAC地址。
    GUID uuid;
    CoCreateGuid(&uuid);
    // Spit the address out
    char mac_addr[18];
    sprintf(mac_addr,"%02X:%02X:%02X:%02X:%02X:%02X",
            uuid.Data4[2],uuid.Data4[3],uuid.Data4[4],
            uuid.Data4[5],uuid.Data4[6],uuid.Data4[7]);
    cout << mac_addr << endl;
    getch();
    return 0;
}


第三种方法- 使用SNMP扩展API
我要讨论的第三种方法是使用Windows的SNMP(简单网络管理协议)扩展来取得MAC地址。在我的经验里,这个协议很简单。代码也是直勾勾的向前的。基本步骤和Netbios相同:
1》取得网卡列表
2》查询每块卡的类型和MAC地址
3》保存当前网卡
我个人对SNMP了解不多,但如我刚刚所言,代码十分清楚。

snmp.cpp
#include <snmp.h>
#include <conio.h>
#include <stdio.h>

typedef bool(WINAPI * pSnmpExtensionInit) (
        IN DWORD dwTimeZeroReference,
        OUT HANDLE * hPollForTrapEvent,
        OUT AsnObjectIdentifier * supportedView);

typedef bool(WINAPI * pSnmpExtensionTrap) (
        OUT AsnObjectIdentifier * enterprise,
        OUT AsnInteger * genericTrap,
        OUT AsnInteger * specificTrap,
        OUT AsnTimeticks * timeStamp,
        OUT RFC1157VarBindList * variableBindings);

typedef bool(WINAPI * pSnmpExtensionQuery) (
        IN BYTE requestType,
        IN OUT RFC1157VarBindList * variableBindings,
        OUT AsnInteger * errorStatus,
        OUT AsnInteger * errorIndex);

typedef bool(WINAPI * pSnmpExtensionInitEx) (
        OUT AsnObjectIdentifier * supportedView);

void main()
{
  HINSTANCE m_hInst;
  pSnmpExtensionInit m_Init;
  pSnmpExtensionInitEx m_InitEx;
  pSnmpExtensionQuery m_Query;
  pSnmpExtensionTrap m_Trap;
  HANDLE PollForTrapEvent;
  AsnObjectIdentifier SupportedView;
  UINT OID_ifEntryType[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 3};
  UINT OID_ifEntryNum[] = {1, 3, 6, 1, 2, 1, 2, 1};
  UINT OID_ipMACEntAddr[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 6};
  AsnObjectIdentifier MIB_ifMACEntAddr =
    { sizeof(OID_ipMACEntAddr)  sizeof(UINT), OID_ipMACEntAddr };
  AsnObjectIdentifier MIB_ifEntryType =
    {sizeof(OID_ifEntryType)  sizeof(UINT), OID_ifEntryType};
  AsnObjectIdentifier MIB_ifEntryNum =
    {sizeof(OID_ifEntryNum)  sizeof(UINT), OID_ifEntryNum};
  RFC1157VarBindList varBindList;
  RFC1157VarBind varBind[2];
  AsnInteger errorStatus;
  AsnInteger errorIndex;
  AsnObjectIdentifier MIB_NULL = {0, 0};
  int ret;
  int dtmp;
  int i = 0, j = 0;
  bool found = false;
  char TempEthernet[13];
  m_Init = NULL;
  m_InitEx = NULL;
  m_Query = NULL;
  m_Trap = NULL;

  /* 载入SNMP DLL并取得实例句柄 */
  m_hInst = LoadLibrary("inetmib1.dll");
  if (m_hInst < (HINSTANCE) HINSTANCE_ERROR)
  {
    m_hInst = NULL;
    return;
  }
  m_Init =
    (pSnmpExtensionInit) GetProcAddress(m_hInst, "SnmpExtensionInit");
  m_InitEx =
    (pSnmpExtensionInitEx) GetProcAddress(m_hInst,
                                          "SnmpExtensionInitEx");
  m_Query =
    (pSnmpExtensionQuery) GetProcAddress(m_hInst,
                                         "SnmpExtensionQuery");
  m_Trap =
    (pSnmpExtensionTrap) GetProcAddress(m_hInst, "SnmpExtensionTrap");
  m_Init(GetTickCount(), &PollForTrapEvent, &SupportedView);

  /* 初始化用来接收m_Query查询结果的变量列表 */
  varBindList.list = varBind;
  varBind[0].name = MIB_NULL;
  varBind[1].name = MIB_NULL;

  /* 在OID中拷贝并查找接口表中的入口数量 */
  varBindList.len = 1;        /* Only retrieving one item */
  SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryNum);
  ret =
    m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,
            &errorIndex);
  printf("# of adapters in this system : %in",
       varBind[0].value.asnValue.number);
  varBindList.len = 2;

  /* 拷贝OID的ifType-接口类型 */
  SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryType);

  /* 拷贝OID的ifPhysAddress-物理地址 */
  SNMP_oidcpy(&varBind[1].name, &MIB_ifMACEntAddr);

  do
  {

    /* 提交查询,结果将载入 varBindList。
       可以预料这个循环调用的次数和系统中的接口卡数量相等 */
    ret =
      m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,
              &errorIndex);
    if (!ret)
      ret = 1;
    else
        /* 确认正确的返回类型 */
      ret =
          SNMP_oidncmp(&varBind[0].name, &MIB_ifEntryType,
                       MIB_ifEntryType.idLength); if (!ret) {
    j++;
    dtmp = varBind[0].value.asnValue.number;
    printf("Interface #%i type : %in", j, dtmp);

    /* Type 6 describes ethernet interfaces */
    if (dtmp == 6)
    {

      /* 确认我们已经在此取得地址 */
      ret =
          SNMP_oidncmp(&varBind[1].name, &MIB_ifMACEntAddr,
                       MIB_ifMACEntAddr.idLength);
      if ((!ret) && (varBind[1].value.asnValue.address.stream != NULL))
      {
        if((varBind[1].value.asnValue.address.stream[0] == 0x44)
          && (varBind[1].value.asnValue.address.stream[1] == 0x45)
          && (varBind[1].value.asnValue.address.stream[2] == 0x53)
          && (varBind[1].value.asnValue.address.stream[3] == 0x54)
          && (varBind[1].value.asnValue.address.stream[4] == 0x00))
        {
          /* 忽略所有的拨号网络接口卡 */
          printf("Interface #%i is a DUN adaptern", j);
          continue;
        }
        if ((varBind[1].value.asnValue.address.stream[0] == 0x00)
            && (varBind[1].value.asnValue.address.stream[1] == 0x00)
            && (varBind[1].value.asnValue.address.stream[2] == 0x00)
            && (varBind[1].value.asnValue.address.stream[3] == 0x00)
            && (varBind[1].value.asnValue.address.stream[4] == 0x00)
            && (varBind[1].value.asnValue.address.stream[5] == 0x00))
        {
          /* 忽略由其他的网络接口卡返回的NULL地址 */
          printf("Interface #%i is a NULL addressn", j);
          continue;
        }
        sprintf(TempEthernet, "%02x%02x%02x%02x%02x%02x",
                varBind[1].value.asnValue.address.stream[0],
                varBind[1].value.asnValue.address.stream[1],
                varBind[1].value.asnValue.address.stream[2],
                varBind[1].value.asnValue.address.stream[3],
                varBind[1].value.asnValue.address.stream[4],
                varBind[1].value.asnValue.address.stream[5]);
        printf("MAC Address of interface #%i: %sn", j,
               TempEthernet);}
      }
    }
  } while (!ret);         /* 发生错误终止。 */
  getch();

  FreeLibrary(m_hInst);
  /* 解除绑定 */
  SNMP_FreeVarBind(&varBind[0]);
  SNMP_FreeVarBind(&varBind[1]);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值