查找物理适配器的MAC地址

7

我希望使用一个唯一标识符来确定我的应用程序是否移动到了另一台计算机。MAC地址似乎适合这个目的。我使用的代码如下:

Procedure TForm4.GetMacAddress;
var item: TListItem;
    objWMIService : OLEVariant;
    colItems      : OLEVariant;
    colItem       : OLEVariant;
    oEnum         : IEnumvariant;
    iValue        : LongWord;
    wmiHost, root, wmiClass: string;
    i: Int32;

  function GetWMIObject(const objectName: String): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;//for access to a bind context
    Moniker: IMoniker;//Enables you to use a moniker object
  begin
    OleCheck(CreateBindCtx(0, bindCtx));
    OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));//Converts a string into a moniker that identifies the object named by the string
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));//Binds to the specified object
  end;

begin
   wmiHost       := '.';
   root          := 'root\CIMV2';
   wmiClass      := 'Win32_NetworkAdapterConfiguration';
   objWMIService := GetWMIObject(Format('winmgmts:\\%s\%s',[wmiHost,root]));
   colItems      := objWMIService.ExecQuery(Format('SELECT * FROM %s',[wmiClass]),'WQL',0);
   oEnum         := IUnknown(colItems._NewEnum) as IEnumVariant;
   i := 0;
   while oEnum.Next(1, colItem, iValue) = 0 do
   begin
      Item := View.Items.Add;
      item.Caption := Copy (colItem.Caption, 2, 8);

      Item.SubItems.Add (colItem.Description);
      Item.SubItems.Add (colItem.ServiceName);
      Item.SubItems.Add (VarToStrNil (colItem.MACAddress));
      if (VarToStrNil(colItem.MACAddress) <> '')
         then Item.SubItems.Add ('yes')
         else Item.SubItems.Add ('no');
      if colItem.IPEnabled
         then Item.SubItems.Add ('yes')
         else Item.SubItems.Add ('no');
     Item.SubItems.Add (VarToStrNil (colItem.SettingID));
     Item.SubItems.Add (IntToStr (colItem.InterfaceIndex));
   end; // if
end; // GetMacAddress //

我的计算机只有一个网络端口,但是这段代码找到了18个与网络相关的端口/设备/其他。其中有四个MAC地址。我认为网络端口应该启用IP,因此只剩下两个(在图像中标记为MAC)。可以假设,在筛选出的端口中,索引最低的端口是硬件端口吗?
在上面的快照中,Realtek适配器是计算机中唯一的物理适配器。另一个适配器是VirtualBox虚拟适配器。TLama的答案确定了这两个适配器,但是否有办法找到唯一物理(Realtek)适配器的地址?
EJP指出MAC地址可以更改。这有点挫败了我的目的,但由于我正在寻找适合大多数情况的解决方案,所以我决定接受它。
TLama和TOndrej指出了几种解决方案。最终都无法毫无疑问地找到物理适配器。
TLama的优秀阅读列表显示,可能没有确定的方法来确定物理适配器。第一条提示中提到的文章显示如何基于一些简单的假设缩小适配器的数量。第三个提示中的文章显示如何选择连接到PCI总线的适配器,这实际上就是我想知道的。文章中提到了一些奇怪的异常情况,但我认为这将在大多数情况下提供答案。
感谢大家的贡献!

1
好的,我采纳了你的建议。谢谢。 - Arnold
MAC地址不适合此目的。用户可以更改它。 - user207421
Windows控制面板是一个好的开始。 - user207421
抱歉,找不到它。 - Arnold
适配器的网络属性。Windows版本越高,您需要深入挖掘的程度就越深。但是它确实存在。Unix和Linux也一样。 - user207421
2个回答

8
使用 Win32_NetworkAdapter 类代替。它拥有 PhysicalAdapter 成员。以下示例可以列出物理适配器的 MAC 地址:
program Program1;

{$APPTYPE CONSOLE}

uses
  SysUtils, ActiveX, ComObj, Variants;

procedure GetWin32_NetworkAdapterInfo;
const
  WbemUser = '';
  WbemPassword = '';
  WbemComputer = 'localhost';
  wbemFlagForwardOnly = $00000020;
var
  ElementCount: LongWord;
  FWMIService: OleVariant;
  FWbemObject: OleVariant;
  EnumVariant: IEnumVARIANT;
  FSWbemLocator: OleVariant;
  FWbemObjectSet: OleVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet := FWMIService.ExecQuery('SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = 1', 'WQL', wbemFlagForwardOnly);
  EnumVariant := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while EnumVariant.Next(1, FWbemObject, ElementCount) = 0 do
  begin
    Writeln(Format('MACAddress %s', [VarToStr(FWbemObject.MACAddress)]));
    FWbemObject := Unassigned;
  end;
end;

begin
  try
    CoInitialize(nil);
    try
      GetWin32_NetworkAdapterInfo;
    finally
      CoUninitialize;
    end;
  except
    on E:EOleException do
      Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
      Writeln(E.Classname, ':', E.Message);
  end;
  Writeln('Press Enter to exit');
  Readln;
end.

基于由 WMI Delphi Code Creator 生成的代码。
更新:
您实际上正在尝试过滤属于虚拟机的适配器,这并不容易,因为它们被模拟成物理适配器(甚至可以在设备管理器中看到它们作为物理适配器),所以您无法区分它们,例如:
通过 Win32_NetworkAdapter 类的 DHCPEnabled 成员来区分,因为即使虚拟机也可能被配置为从 DHCP 服务器获取 IP 地址;
通过 Win32_NetworkAdapter 类的 AdapterTypeId 成员来区分,因为没有专门用于虚拟适配器的类型;
通过 Win32_NetworkAdapter 类的 PhysicalAdapter 成员来区分,因为它们被模拟成物理适配器。
更多阅读:

2
好像我也是这么想的。恐怕这些适配器就像真正的物理网络适配器一样由Windows处理,所以如果没有这种巧妙的方法,就不可能将它们与硬件适配器区分开来。 - TLama
1
我尝试了TOndrej指出的IP Helper库。AdapterInfo结构体也缺少了太多信息。不过这是有趣的代码,如果你不想依赖WMI的话。 - Arnold
1
我更希望得到PNPDeviceID,其中根会是PCI\...之类的,但我不知道这是否适用于USB适配器(如果有的话),但这仍然不是很完美的解决方案。虚拟适配器就像真正的硬件适配器一样,所以很难区分它们。我正在寻找类似的主题,但似乎没有人以清晰的方式解决这个问题。 - TLama
1
Manufacturer位于Win32_NetworkAdapter类中,而不是Win32_NetworkAdapterConfiguration,因此应该是SELECT * FROM Win32_NetworkAdapter WHERE Manufacturer != "Microsoft"。无论如何,最好的方法是下载最新的RRUZ的WMI Delphi Code Creator,它还包括一个查询编辑器,在那里您可以构建查询以及生成完整的Delphi代码。 - TLama
1
感谢您的澄清和指向RRUZ。我现在使用PNPDeviceID,如果没有产生任何结果,那么它可能是虚拟机器,我将采取第一个可用的机器。为了测试它是否在所有情况下都有效,我应该真正测试数百台机器。无论如何,这回答了我的问题,非常感谢您的帮助和解释! - Arnold
显示剩余3条评论

4

谢谢指针。有趣的代码,但据我所见,产生的输出类型与“Win32_NetworkAdapterConfiguration” WMI类大致相同,最终产生相同的输出:两个充当物理适配器的端口。 - Arnold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接