我已经为 Excel 完成了这项工作,编写了一个多线程 DLL,除了获取 MAC 地址之外,它还会执行 DNS 反向主机名和 ICMP ping RT。
让我知道您是否需要随附的 VBA 代码。基于 C 的 DLL(您可能会喜欢作为模板)的来源是:
/*
ExcelNet library: Provide threaded networking functions
NOTE: Serially doing these on the network involves serially waiting for timeouts, much ouchies!
Written by: Danny Holstein
*/
#if 1 // header stuff
#include <windows.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define MSEXPORT __declspec(dllexport)
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define BUFSZ 256
typedef struct {
char ip[BUFSZ]; // IP addresses in dotted notation.
int ipSize; // size of IP address buffer
char data[BUFSZ]; // general network data (MAC, hostname, etc)
int dataSize; // size of data buffer ^
int err_no; // WinAPI error number
int (*func)(LPCSTR Addr, char* buf, int BufSz); // function address, &GetNameInfos, &GetARP or &GetICMP
} NET_DATA;
int GetARP(LPCSTR Addr, char* Buf, int BufSz);
int GetNameInfos(LPCSTR Addr, char* Buf, int BufSz);
int GetICMP(LPCSTR Addr, char* Buf, int BufSz);
char msg_dbg[BUFSZ], dan[BUFSZ];
#define DEBUG_PRT() {snprintf(msg_dbg, BUFSZ, "lineno = %d\tfunc = %s", __LINE__ , __func__); MessageBox(0, msg_dbg, "debug", 0);}
#define DEBUG_MSG(msg) {snprintf(msg_dbg, BUFSZ, "msg=\"%s\"\tlineno = %d\tfunc = %s", msg, __LINE__ , __func__); MessageBox(0, msg_dbg, "debug", 0);}
#if 0 // documentation indicates malloc/realloc/free shouldn't be used in DLLs
#define malloc(A) malloc(A)
#define realloc(A, B) realloc(A, B)
#define free(A) free(A)
// #define NOMEMLEAK // dudint work
#else // kinda works, when NOT allocating all the NET_DATA structure elements
#define malloc(A) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, A)
#define realloc(A, B) HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, A, B)
#define free(A) (void) HeapFree(A, HEAP_GENERATE_EXCEPTIONS, NULL)
#define NOMEMLEAK
#endif
#endif
MSEXPORT void __stdcall ArrayDGH(SAFEARRAY **IntArr, SAFEARRAY **StrArr)
{
SAFEARRAY *A = (SAFEARRAY*) (*IntArr);
SAFEARRAY *S = (SAFEARRAY*) (*StrArr);
int n = (*A).rgsabound[0].cElements;
snprintf(dan, BUFSZ, "a num elems = %d, str num elems = %d", n, (*S).rgsabound[0].cElements); DEBUG_MSG(dan);
// for (int i=0; i<n; i++) {snprintf(dan, BUFSZ, "elem[%d] = %d", i, ((int*) (*A).pvData)[i]); DEBUG_MSG(dan);}
OLECHAR* str[] = {L"Da",L"Fuk",L"Waz",L"Dat?",L"dot",L"dot"};
for (int i=0; i<n; i++) {SysReAllocString(&(((BSTR*) (*S).pvData)[i]), str[i]);}
}
DWORD dwMilliseconds = 10000;
MSEXPORT void __stdcall SetTIMO(DWORD Timo) {dwMilliseconds = Timo;}
MSEXPORT bool __stdcall ExcelSendARP(LPCSTR Addr, BSTR* MAC)
{
char buf[BUFSZ];
int err = GetARP(Addr, buf, BUFSZ);
*MAC = SysAllocStringByteLen(buf, strlen(buf)); // avoid WIDE to ANSI stuff
return !err;
}
MSEXPORT bool __stdcall ExcelICMPRT(LPCSTR Addr, BSTR* RoundTrip)
{
char buf[BUFSZ];
int err = GetICMP(Addr, buf, BUFSZ);
*RoundTrip = SysAllocStringByteLen(buf, strlen(buf)); // avoid WIDE to ANSI stuff
return !err;
}
MSEXPORT bool __stdcall ExcelGetNameInfo(LPCSTR Addr, BSTR* NameInfo)
{
char buf[BUFSZ];
#ifdef TEST_FUNC_PTR
int (*FunAddr[2])(LPCSTR Addr, char** buf, int BufSz);
FunAddr[0] = &GetARP; FunAddr[1] = &GetNameInfos; // DRY code hooks
int err = (*(FunAddr[1]))(Addr, buf, BUFSZ);
#else
int err = GetNameInfos(Addr, buf, BUFSZ);
#endif
*NameInfo = SysAllocStringByteLen(buf, strlen(buf)); // avoid WIDE to ANSI stuff
return !err;
}
int GetNameInfos(LPCSTR Addr, char* buf, int BufSz)
{
ULONG inet; DWORD resp = 0; HOSTENT *tHI;
struct in_addr addr = { 0 };
if ((inet = inet_addr(Addr)) == INADDR_NONE) {
if (strcpy_s(buf, BufSz, "inet_addr failed and returned INADDR_NONE")) DEBUG_MSG("strcpy_s error!");
return inet;
}
addr.s_addr = inet; tHI = gethostbyaddr((char *) &addr, 4, AF_INET);
if (tHI == NULL) { // no reponse for this IP condition, decode condition and place in PCHAR buf
resp = WSAGetLastError();
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, resp, 0, buf, BufSz, 0);
return resp;
}
_snprintf_s(buf, BufSz-1, _TRUNCATE, "%s", tHI->h_name); // place hostname in PCHAR buf
return resp; // <- resp = 0, we have a hostname associated with this IP, SUCCESS!
}
int GetARP(LPCSTR Addr, char* buf, int BufSz)
{
#define BUFLEN 6
ULONG pMacAddr[BUFLEN], inet, BufLen = BUFLEN; DWORD resp = 0;
if ((inet = inet_addr(Addr)) == INADDR_NONE) {
if (strcpy_s(buf, BufSz, "inet_addr failed and returned INADDR_NONE")) DEBUG_MSG("strcpy_s error!");
return inet;
}
resp = SendARP(inet, 0, pMacAddr, &BufLen);
if (resp) { // no reponse for this IP condition, decode condition and place in PCHAR buf
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, resp, 0, buf, BufSz, 0);
return resp;
}
UCHAR *pMacAddrBytes = (UCHAR *) pMacAddr;
_snprintf_s(buf, BufSz, _TRUNCATE, "%02x:%02x:%02x:%02x:%02x:%02x", pMacAddrBytes[0], pMacAddrBytes[1], pMacAddrBytes[2],
pMacAddrBytes[3], pMacAddrBytes[4], pMacAddrBytes[5]); // place MAC in PCHAR buf
return resp; // <- resp = 0, we have a MAC associated with this IP, SUCCESS!
}
int GetICMP(LPCSTR Addr, char* buf, int BufSz)
{
HANDLE hIcmpFile;
ULONG inet; DWORD resp = 0;
char SendData[] = "Data Buffer";
LPVOID ReplyBuffer = NULL;
DWORD ReplySize = 0;
if ((inet = inet_addr(Addr)) == INADDR_NONE) {
if (strcpy_s(buf, BufSz, "inet_addr failed and returned INADDR_NONE")) DEBUG_MSG("strcpy_s error!");
return inet;
}
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
resp = GetLastError();
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, resp, 0, buf, BufSz, 0);
return resp;
}
// Allocate space for at a single reply
ReplySize = sizeof (ICMP_ECHO_REPLY) + sizeof (SendData) + 8;
ReplyBuffer = (VOID *) malloc(ReplySize);
resp = IcmpSendEcho2(hIcmpFile, NULL, NULL, NULL,
inet, SendData, sizeof (SendData), NULL,
ReplyBuffer, ReplySize, dwMilliseconds);
PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY) ReplyBuffer;
if (resp == 0) { // no reponse for this IP condition, decode condition and place in PCHAR buf
resp = pEchoReply->Status; // WSAGetLastError();
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, resp, 0, buf, BufSz, 0);
return resp;
}
_snprintf_s(buf, BufSz-1, _TRUNCATE, "%d", ((PICMP_ECHO_REPLY) ReplyBuffer)->RoundTripTime); // place roundtrip time in PCHAR buf (ASCII representation)
IcmpCloseHandle(hIcmpFile);
return 0; // we have a RT time in milliseconds associated with this IP, SUCCESS!
}
NET_DATA *NetData;
DWORD WINAPI tGetDATA(LPVOID NetData)
{
char buf[BUFSZ];
NET_DATA *m = NetData; // stupid me, I had allocated the space, instead of address of, not thinking it all disappears on exit
m->err_no = (*(m->func))(m->ip, buf, BUFSZ); // GetARP, GetNameInfos or GetICMP
if (_snprintf_s(m->data, m->dataSize-1, _TRUNCATE, "%s", buf) == -1) DEBUG_MSG("_snprintf_s error!");
// A resources error may have hit AFTER the calling function has returned, including having the thread/memory deallocated in the calling function
return 0;
}
MSEXPORT BSTR __stdcall ExcelRngNetData(UCHAR *list, ULONG *ErrNos, BSTR *DataArry, DWORD dwMilliseconds, char sep, char FunctType)
{ // take list of IP addresses (newline-separated), create thread for each to get network data. Return results in (sep-separated) BSTR.
int i=0, j, NumIPs=0, k = strlen(list), l=0;
#define NMSZ 100
char nm[NMSZ];
void* FuncAddr[3]; // DRY code hooks
FuncAddr[0] = &GetARP; FuncAddr[1] = &GetNameInfos; FuncAddr[2] = &GetICMP;
while (i<k && sscanf_s(list+i, "%[^\n]\n", nm, NMSZ)){i += strnlen_s(nm, NMSZ) + 1; NumIPs++;} // count newline-separated items before doing malloc(), because realloc() is REALLY resource intensive
NetData = malloc(NumIPs * sizeof(NET_DATA)); i = 0;
while (i<k && sscanf_s(list+i, "%[^\n]\n", nm, NMSZ)){ // load calling routines list into structures
j = strnlen_s(nm, NMSZ) + 1; i += j;
NetData[l].err_no = WAIT_TIMEOUT; // easy way to preset the error to a TIMEOUT, if the thread successfully completes before the timeout, this will be set to reflect its timeout
NetData[l].dataSize = BUFSZ;
NetData[l].func = FuncAddr[FunctType]; // DRY (Don't Repeat Yourself) code hook
if (strncpy_s(NetData[l].ip, (NetData[l].ipSize = BUFSZ), nm, j)) DEBUG_MSG("strcpy_s error!");
l++;
}
HANDLE *tHandles = malloc(NumIPs * sizeof(HANDLE)); DWORD ThreadId;
for (i=0; i<NumIPs; i++){
tHandles[i] = CreateThread(NULL, 0, tGetDATA, &(NetData[i]), 0, &ThreadId);
if (tHandles[i] == NULL) {DEBUG_MSG("Could not create threads!\nExiting now"); ExitProcess(3);}
}
if(WaitForMultipleObjects(NumIPs, tHandles, TRUE, dwMilliseconds) == WAIT_FAILED) {
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, GetLastError(), 0, dan, BUFSZ, 0); DEBUG_MSG(dan);
// Prolly means too many threads, kicked in when I exceeded 64; ERROR_INVALID_PARAMETER = "The parameter is incorrect"
}
#define CHUNK 1024
#define DATASZ 256 // tested with intentionally small size to check "safe" string functions
wchar_t wcs[DATASZ]; char MacChunk[DATASZ];
char *ans = malloc(CHUNK); int anslen = 0, anssz = CHUNK;
char separator[2]; separator[0] = sep; separator[1] = 0;
for(i=0; i<NumIPs; i++) { // build return BSTR and load array with data
CloseHandle(tHandles[i]); ErrNos[i] = NetData[i].err_no;
if (strncpy_s(MacChunk, DATASZ-1, NetData[i].err_no == 0 ? NetData[i].data : "", NetData[i].dataSize-1)) DEBUG_MSG("strcpy_s error!");
if (i<NumIPs-1 && sep != 0) if (strncat_s(MacChunk, DATASZ-1, separator, 1)) DEBUG_MSG("strcpy_s error!");
while (strnlen_s(MacChunk, DATASZ) > anssz - anslen -1) ans = realloc(ans, (anssz += CHUNK)); // choose CHUNK size to avoid constant realloc(), because realloc() is REALLY resource intensive
if (strncpy_s(&(ans[anslen]), DATASZ-1, MacChunk, DATASZ-1)) DEBUG_MSG("strcpy_s error!"); anslen += strnlen_s(MacChunk, DATASZ); // return data in returned BSTR
MultiByteToWideChar(CP_UTF8, 0,
ErrNos[i] == WAIT_TIMEOUT ? "The wait operation timed out." : NetData[i].data,
-1, wcs, DATASZ-1); // return data in supplied array
SysReAllocString(&DataArry[i], wcs);
}
BSTR r = SysAllocStringByteLen(ans, strlen(ans));
#ifdef NOMEMLEAK
free(NetData); free(tHandles); free(ans);
#endif
return r;
}