IT SOLUTIONS
Your full service technology partner! 
-Collapse +Expand
Delphi
Search Delphi Group:

Advanced
-Collapse +Expand Delphi To/From
To/FromCODEGuides
-Collapse +Expand Delphi Store
PRESTWOODSTORE

Prestwood eMagazine

January Edition
Subscribe now! It's Free!
Enter your email:

   ► KBProgrammingDelphi for W...Coding Tasks   Print This     
  From the August 2009 Issue of Prestwood eMag
 
Delphi Coding Tasks:
Get NIC MAC Address
 
Posted 16 years ago on 9/29/2003 and updated 5/24/2010
Take Away:

This code snippet shows you how to get your NIC MAC address using Borland Delphi.

KB100258



The previously published GUID techniques for finding your NIC's MAC address don't work on Windows 2000 and XP because Microsoft changed how GUID's work. Use this technique instead.

You can verify this is the correct MAC address for your first network card by using ipconfig /all at a DOS prompt. Look for the Physical Address of your first NIC.

The following code snippet is converted from Microsoft: http://support.microsoft.com/support/kb/articles/Q118/6/23.asp. I'm not sure who did the original translation, but here it is for archival reasons.


The Delphi Translation

uses NB30;
function GetAdapterInfo(Lana: Char): String;
var
Adapter: TAdapterStatus;
NCB: TNCB;
begin
FillChar(NCB, SizeOf(NCB), 0);
NCB.ncb_command := Char(NCBRESET);
NCB.ncb_lana_num := Lana;
if Netbios(@NCB) <> Char(NRC_GOODRET) then 
begin
Result := 'mac not found';
Exit;
end;
FillChar(NCB, SizeOf(NCB), 0); 
NCB.ncb_command := Char(NCBASTAT); 
NCB.ncb_lana_num := Lana;
NCB.ncb_callname := '*';
FillChar(Adapter, SizeOf(Adapter), 0); 
NCB.ncb_buffer := @Adapter; 
NCB.ncb_length := SizeOf(Adapter);
if Netbios(@NCB) <> Char(NRC_GOODRET) then 
begin
Result := 'mac not found';
Exit;
end;
Result := 
IntToHex(Byte(Adapter.adapter_address[0]), 2) + '-' +
IntToHex(Byte(Adapter.adapter_address[1]), 2) + '-' +
IntToHex(Byte(Adapter.adapter_address[2]), 2) + '-' +
IntToHex(Byte(Adapter.adapter_address[3]), 2) + '-' +
IntToHex(Byte(Adapter.adapter_address[4]), 2) + '-' +
IntToHex(Byte(Adapter.adapter_address[5]), 2);
end;
function GetMACAddress: string; 
var
AdapterList: TLanaEnum;
NCB: TNCB;
begin
FillChar(NCB, SizeOf(NCB), 0);
NCB.ncb_command := Char(NCBENUM);
NCB.ncb_buffer := @AdapterList;
NCB.ncb_length := SizeOf(AdapterList);
Netbios(@NCB);
  if Byte(AdapterList.length) > 0 then 
Result := GetAdapterInfo(AdapterList.lana[0])
else
Result := 'mac not found';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
ShowMessage(GetMACAddress);
end;

Comments

1 Comments.
Share a thought or comment...
First Comment
Comment 1 of 4
When the NIC is unplugged from the network - the MAC cannot be found. How do I get around this problem??
Posted 15 years ago

Comment 2 of 4
I have tried your method (using NB30) before but it does not always work. 
Here is a way that always works:


unit ethernet_address;

interface

uses classes, sysutils;

const
MAX_INTERFACE_NAME_LEN = $100;
ERROR_SUCCESS = 0;
MAXLEN_IFDESCR = $100;
MAXLEN_PHYSADDR = 8;

MIB_IF_OPER_STATUS_NON_OPERATIONAL = 0 ;
MIB_IF_OPER_STATUS_UNREACHABLE = 1;
MIB_IF_OPER_STATUS_DISCONNECTED = 2;
MIB_IF_OPER_STATUS_CONNECTING = 3;
MIB_IF_OPER_STATUS_CONNECTED = 4;
MIB_IF_OPER_STATUS_OPERATIONAL = 5;

MIB_IF_TYPE_OTHER = 1;
MIB_IF_TYPE_ETHERNET = 6;
MIB_IF_TYPE_TOKENRING = 9;
MIB_IF_TYPE_FDDI = 15;
MIB_IF_TYPE_PPP = 23;
MIB_IF_TYPE_LOOPBACK = 24;
MIB_IF_TYPE_SLIP = 28;

MIB_IF_ADMIN_STATUS_UP = 1;
MIB_IF_ADMIN_STATUS_DOWN = 2;
MIB_IF_ADMIN_STATUS_TESTING = 3;


type

MIB_IFROW = Record
wszName : Array[0 .. (MAX_INTERFACE_NAME_LEN*2-1)] of char;
dwIndex : LongInt;
dwType : LongInt;
dwMtu : LongInt;
dwSpeed : LongInt;
dwPhysAddrLen : LongInt;
bPhysAddr : Array[0 .. (MAXLEN_PHYSADDR-1)] of Byte;
dwAdminStatus : LongInt;
dwOperStatus : LongInt;
dwLastChange : LongInt;
dwInOctets : LongInt;
dwInUcastPkts : LongInt;
dwInNUcastPkts : LongInt;
dwInDiscards : LongInt;
dwInErrors : LongInt;
dwInUnknownProtos : LongInt;
dwOutOctets : LongInt;
dwOutUcastPkts : LongInt;
dwOutNUcastPkts : LongInt;
dwOutDiscards : LongInt;
dwOutErrors : LongInt;
dwOutQLen : LongInt;
dwDescrLen : LongInt;
bDescr : Array[0 .. (MAXLEN_IFDESCR - 1)] of Char;
end;

function Get_EthernetAddresses: TStringList;

Function GetIfTable( pIfTable : Pointer;
VAR pdwSize : LongInt;
bOrder : LongInt ): LongInt; stdcall;


implementation

Function GetIfTable; stdcall; external 'IPHLPAPI.DLL';

function Get_EthernetAddresses: TStringList;
const
_MAX_ROWS_ = 20;

type
_IfTable = Record
nRows : LongInt;
ifRow : Array[1.._MAX_ROWS_] of MIB_IFROW;
end;

VAR
pIfTable : ^_IfTable;
TableSize : LongInt;
tmp : String;
i,j : Integer;
ErrCode : LongInt;
begin
pIfTable := nil;
//------------------------------------------------------------
Result:=TStringList.Create;
if Assigned(Result) then
try
//-------------------------------------------------------
// First: just get the buffer size.
// TableSize returns the size needed.
TableSize:=0; // Set to zero so the GetIfTabel function
// won't try to fill the buffer yet,
// but only return the actual size it needs.
GetIfTable(pIfTable, TableSize, 1);
if (TableSize < SizeOf(MIB_IFROW)+Sizeof(LongInt)) then
begin
Exit; // less than 1 table entry?!
end; // if-end.

// Second:
// allocate memory for the buffer and retrieve the
// entire table.
GetMem(pIfTable, TableSize);
ErrCode := GetIfTable(pIfTable, TableSize, 1);
if ErrCode<>ERROR_SUCCESS then
begin
Exit; // OK, that did not work.
// Not enough memory i guess.
end; // if-end.

// Read the ETHERNET addresses.
for i := 1 to pIfTable^.nRows do
try
if pIfTable^.ifRow[i].dwType=MIB_IF_TYPE_ETHERNET then
begin
tmp:='';
for j:=0 to pIfTable^.ifRow[i].dwPhysAddrLen-1 do
begin
tmp := tmp + format('%.2x',
[ pIfTable^.ifRow[i].bPhysAddr[j] ] );
end; // for-end.
//-------------------------------------
if Length(tmp)>0 then Result.Add(tmp);
end; // if-end.
except
Exit;
end; // if-try-except-end.
finally
if Assigned(pIfTable) then FreeMem(pIfTable,TableSize);
end; // if-try-finally-end.
end;

// Enjoy!
Posted 13 years ago

Comment 3 of 4

I am a schoolteacher learning Delphi. I want to try your code as a unit2 used by Unit1 with a button and label on the form that has the code

   Label1.Caption :=   //What must go here to display the MAC Address (or should it be a ListBox because you seem to determine more than one MAC address)?

Your code is way above me, as I have never (yet) used pointers. I have also only used classes (records a long time ago with Pascal). I'm using D7 and run XP, Win7 32-bit and Win7 64-bit. I have a solution using NB30 but it only works on Win7 64-bit, even if I enable NetBIOS on the other OSs (gives error $30).

Posted 9 years ago

Latest Comment
Comment 4 of 4

Hi Hannes,

>I am a schoolteacher learning Delphi. I want to try your code as a unit2 used by Unit1 with a button and label on the form that has the code

First of all, I'd like to thank narayana for posting some very nice code for finding MAC addresses.  This has never been an easy or particularly straightforward task.

One of the first things to understand is that the MAC address is a property of the Ethernet controller on a motherboard or NIC card. Without such a device, a computer won't have a MAC address at all. And, to complicate things, it's not uncommon, now, to find machines with multiple NIC cards and, therefore multiple NIC addresses.

>I am a schoolteacher learning Delphi. I want to try your code as a unit2 used by Unit1 with a button and label on the form that has the code

>   Label1.Caption :=   //What must go here to display the MAC Address (or should it be a ListBox because you seem to determine more than one MAC address)?

You're right in thinking you'll want a way to display more than one MAC address.  More about that in a moment.

>Your code is way above me, as I have never (yet) used pointers.

Just a quick comment on that. In Delphi, almost everything is already a pointer, so you rarely encounter the need to use pointers directly.  Don't be troubled that narayana's code uses a pointer because your code doesn't really have to "know" anything about it.  His function, complex as it is, returns a TStringList, one of the most common (and useful) constructs in Delphi.  When using his code, you'll only have to deal with the TStringList he returns.

>I have also only used classes (records a long time ago with Pascal). I'm using D7 and run XP, Win7 32-bit and Win7 64-bit. I have a solution using NB30 but it only works on Win7 64-bit, even if I enable NetBIOS on the other OSs (gives error $30).

Caveat: I haven't tested narayana's' code, but it looks reasonable.

For a start, try this:

* You can put his code in its own unit if you like - or simply put it into you own unit's code.

* In your code, do something like this:

var
  myStringList : TStringList;
begin
  // Note: You don't need to consturct the StringList - the function below does that.
  myStringList := Get_EthernetAddresses;
  if myStringList.Count > 0 then
    Label1.Caption := myStringList.Strings[0]
  else
    Label1.Caption := 'No MAC addresses found.';
  myStringList.Free;
end;

Three comments about the above:

First you're checking to be sure Get_EthernetAddresses returned anything besides an empty StringList.

Second, only if there are more than zero strings (MAC Addresses) in the StringList, do you assign one (the first one) to your label's caption - otherwise you report "failure." (We'll get to multiple MAC addresses in a moment).

Third, and this may not be obvious, but there is a potential memory leak in narayana's function.  He creates a new TStringList and returns it. In that case, he can't very well free it, or you'd get nothing useful back. So you have to free his TStringList. This works, but isn't a best practice. I'll suggest a refinement in a moment.

Okay... If the above exercise works, there are several ways to display multiple MAC addresses. This one is easiest:

Label1.Caption := myStringList.CommaText;

Isn't that cool? CommaText is a very handy property of TStringList.

Alternately, instead of a label, you might use a TMemo.  In that case, your code might look like this:

var
  myStringList;
begin
  myStringList := Get_EthernetAddresses;
  Memo1.Lines.Assign(myStringList.Strings);
  // And don't forget!
  myStringList.Free;
end;

The above technique can be used with a ListBox, ComboBox; any component that has a TStrings member.

That fact, and the potential memory leak would tempt me to revise narayana's code just a bit.

Instead of a function returning a TStringList, it could be a procedure or function taking a TStrings parameter. The caller, then, would be responsible for first constructing (or otherwise providing) the parameter's object and passing it to the function. The function would first (just to be thorough), clear the TStrings parameter, then do it's elegant dance and add found MAC addresses to the passed TStrings.

Since the programmer invoking the function would have to first provide the parameter object, it would be more obvious that it has to be freed.

Here's what that might look like:

procedure Get_EthernetAddresses(_list : TStrings);
...
begin
  _list.Clear; // just in case
  // all the same except for creating a TStringList
end; // Note: nothing is returned.

Your code, using the above, would then look more like this:

var
  // we don't need one! We'll use Memo1 on our form
begin
  Get_EthernetAddresses(Memo1.Lines);
end;

That's it! One line of code.

Posted 9 years ago
 
Write a Comment...
...
Sign in...

If you are a member, Sign In. Or, you can Create a Free account now.


Anonymous Post (text-only, no HTML):

Enter your name and security key.

Your Name:
Security key = P1212A1
Enter key:
KB Post Contributed By Mike Prestwood:

Mike Prestwood is a drummer, an author, and creator of the PrestwoodBoards online community. He is the President & CEO of Prestwood IT Solutions. Prestwood IT provides Coding, Website, and Computer Tech services. Mike has authored 6 computer books and over 1,200 articles. As a drummer, he maintains play-drums.com and has authored 3 drum books. If you have a project you wish to discuss with Mike, you can send him a private message through his PrestwoodBoards home page or call him 9AM to 4PM PST at 916-726-5675 x205.

Visit Profile

 KB Article #100258 Counter
32995
Since 4/2/2008
-
   Contact Us!
 
Have a question? Need our services? Contact us now.
--Mike Prestwood

Call: 916-726-5675

email: info@prestwood.com


Go ahead!   Use Us! Call: 916-726-5675 


©1995-2019 Prestwood IT Solutions.   [Security & Privacy]