设置IP地址只需要更改注册表中关于适配器的相应设置,但更改后需要重新启动系统才能生效,而AddIPAddress函数只能添加IP而不是更改当前的IP,我们在Windows NT/2000界面上操作不需要重新启动就可以生效,那系统到底做了什么额外的工作才使IP设置直接生效呢?笔者通过跟踪explorer.exe中API的调用发现在netcfgx.dll中调用了dhcpcsvc.dll中一个未公开的API:DhcpNotifyConfigChange,现将不重新启动WINDOWS直接更改IP地址的详细方法介绍如下:
一、获取适配器名称
这里指的适配器名称要区别于适配器描述,比如我的一块网卡,适配器描述是:Realtek RTL8139(A) PCI Fast Ethernet Adapter,适配器名称为:{66156DC3-44A4-434C-B8A9-0E5DB4B3EEAD}。获取适配器名称的方法有多种:
1.1 调用IP helper API取得适配器名称
ULONG ulAdapterInfoSize = sizeof(IP_ADAPTER_INFO);
IP_ADAPTER_INFO *pAdapterInfoBkp, *pAdapterInfo = (IP_ADAPTER_INFO*)new char[ulAdapterInfoSize];
if( GetAdaptersInfo(pAdapterInfo, &ulAdapterInfoSize) == ERROR_BUFFER_OVERFLOW ) // 缓冲区不够大
{
delete pAdapterInfo;
pAdapterInfo = (IP_ADAPTER_INFO*)new char[ulAdapterInfoSize];
pAdapterInfoBkp = pAdapterInfo;
}
if( GetAdaptersInfo(pAdapterInfo, &ulAdapterInfoSize) == ERROR_SUCCESS )
{
do{ // 遍历所有适配器
if(pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET) // 判断是否为以太网接口
{
// pAdapterInfo->Description 是适配器描述
// pAdapterInfo->AdapterName 是适配器名称
}
pAdapterInfo = pAdapterInfo->Next;
}while(pAdapterInfo);
}
delete pAdapterInfoBkp;
1.2 读取注册表取得适配器名称
在Windows2000中可以通过遍历 HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Class/{4d36e972-e325-11ce-bfc1-08002be10318}/000n/ (n是从0开始编号的数字)所有接口, 在Windows NT中可以读取HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/NetworkCards中的信息,下面以Windows2000为例: HKEY hKey, hSubKey, hNdiIntKey;
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"System//CurrentControlSet//Control//Class//{4d36e972-e325-11ce-bfc1-08002be10318}",
0,
KEY_READ,
&hKey) != ERROR_SUCCESS)
return FALSE;
DWORD dwIndex = 0;
DWORD dwBufSize = 256;
DWORD dwDataType;
char szSubKey[256];
unsigned char szData[256];
while(RegEnumKeyEx(hKey, dwIndex++, szSubKey, &dwBufSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
if(RegOpenKeyEx(hKey, szSubKey, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS)
{
if(RegOpenKeyEx(hSubKey, "Ndi//Interfaces", 0, KEY_READ, &hNdiIntKey) == ERROR_SUCCESS)
{
dwBufSize = 256;
if(RegQueryValueEx(hNdiIntKey, "LowerRange", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
if(strcmp((char*)szData, "ethernet") == 0) // 判断是不是以太网卡
{
dwBufSize = 256;
if(RegQueryValueEx(hSubKey, "DriverDesc", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
// szData 中便是适配器详细描述
dwBufSize = 256;
if(RegQueryValueEx(hSubKey, "NetCfgInstanceID", 0, &dwDataType, szData, &dwBufSize) == ERROR_SUCCESS)
{
// szData 中便是适配器名称
}
}
}
}
RegCloseKey(hNdiIntKey);
}
RegCloseKey(hSubKey);
}
dwBufSize = 256;
} /* end of while */
RegCloseKey(hKey);
二、将IP信息写入注册表
代码如下:BOOL RegSetIP(LPCTSTR lpszAdapterName, LPCTSTR pIPAddress, LPCTSTR pNetMask, LPCTSTR pNetGate)
{
HKEY hKey;
string strKeyName = "SYSTEM//CurrentControlSet//Services//Tcpip//Parameters//Interfaces//";
strKeyName += lpszAdapterName;
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,
strKeyName.c_str(),
0,
KEY_WRITE,
&hKey) != ERROR_SUCCESS)
return FALSE;
char mszIPAddress[100];
char mszNetMask[100];
char mszNetGate[100];
strncpy(mszIPAddress, pIPAddress, 98);
strncpy(mszNetMask, pNetMask, 98);
strncpy(mszNetGate, pNetGate, 98);
int nIP, nMask, nGate;
nIP = strlen(mszIPAddress);
nMask = strlen(mszNetMask);
nGate = strlen(mszNetGate);
*(mszIPAddress + nIP + 1) = 0x00; // REG_MULTI_SZ数据需要在后面再加个0
nIP += 2;
*(mszNetMask + nMask + 1) = 0x00;
nMask += 2;
*(mszNetGate + nGate + 1) = 0x00;
nGate += 2;
RegSetValueEx(hKey, "IPAddress", 0, REG_MULTI_SZ, (unsigned char*)mszIPAddress, nIP);
RegSetValueEx(hKey, "SubnetMask", 0, REG_MULTI_SZ, (unsigned char*)mszNetMask, nMask);
RegSetValueEx(hKey, "DefaultGateway", 0, REG_MULTI_SZ, (unsigned char*)mszNetGate, nGate);
RegCloseKey(hKey);
return TRUE;
}
三、调用DhcpNotifyConfigChange通知配置的改变
未公开函数DhcpNotifyConfigChange位于 dhcpcsvc.dll中,原型如下: BOOL DhcpNotifyConfigChange(
LPWSTR lpwszServerName, // 本地机器为NULL
LPWSTR lpwszAdapterName, // 适配器名称
BOOL bNewIpAddress, // TRUE表示更改IP
DWORD dwIpIndex, // 指明第几个IP地址,如果只有该接口只有一个IP地址则为0
DWORD dwIpAddress, // IP地址
DWORD dwSubNetMask, // 子网掩码
int nDhcpAction ); // 对DHCP的操作 0:不修改, 1:启用 DHCP,2:禁用 DHCP
具体调用代码如下: BOOL NotifyIPChange(LPCTSTR lpszAdapterName, int nIndex, LPCTSTR pIPAddress, LPCTSTR pNetMask)
{
BOOL bResult = FALSE;
HINSTANCE hDhcpDll;
DHCPNOTIFYPROC pDhcpNotifyProc;
WCHAR wcAdapterName[256];
MultiByteToWideChar(CP_ACP, 0, lpszAdapterName, -1, wcAdapterName,256);
if((hDhcpDll = LoadLibrary("dhcpcsvc")) == NULL)
return FALSE;
if((pDhcpNotifyProc = (DHCPNOTIFYPROC)GetProcAddress(hDhcpDll, "DhcpNotifyConfigChange")) != NULL)
if((pDhcpNotifyProc)(NULL, wcAdapterName, TRUE, nIndex, inet_addr(pIPAddress), inet_addr(pNetMask), 0) == ERROR_SUCCESS)
bResult = TRUE;
FreeLibrary(hDhcpDll);
return bResult;
}
在dos下可以这样修改ip:
c:/>netsh interface ip set address "本地连接" static 10.1.196.196 255.255.255.0 10.1.196.1 1
编程如下:
CString zju;
zju = CString("C://WINDOWS//system32//netsh.exe interface ip set address ")+CString("/"本地连接/"")+CString(" static 10.1.196.160 255.255.255.0 10.1.196.1 1");
WinExec(zju,SW_HIDE);
即可将ip改成10.1.196.160 子网掩码255.255.255.0 网关10.1.196.1
@echo off
rem eth //eth 为网卡名称,可在网络连接中查询,如"本地链接"
set eth="本地链接"
rem ip //ip 为你想更改的IP
set ip=192.168.0.180
rem gateway //gateway 为网关地址
set gateway=192.168.0.1
rem netmasks //netmasks 为子网掩码
set netmasks=255.255.255.0
rem dns //dns 为首选DNS
set dns=192.168.0.1
rem dns2 //dns2 为备用DNS
set dns2=192.168.0.2
echo 正在将本机IP更改到: %ip% 请等候...
rem
if %gateway%==none netsh interface ip set address %eth% static %ip% %netmasks% %gateway% > nul
if not %gateway%==none netsh interface ip set address %eth% static %ip% %netmasks% %gateway% 1 > nul
if %dns%==none netsh interface ip set dns %eth% static %dns%> nul
if not %dns%==none netsh interface ip set dns %eth% static %dns%> nul
if %dns2%==none netsh interface ip add dns %eth% %dns2%> nul
if not %dns2%==none netsh interface ip add dns %eth% %dns2% 2> nul
echo.........................
echo 检查当前本机IP:
ipconfig
echo.........................
echo 成功将本机IP更改为%ip%!
pause
exit
@echo off
if exist ipconfig.txt del ipconfig.txt
ipconfig /all >ipconfig.txt
if exist phyaddr.txt del phyaddr.txt
find "Physical Address" ipconfig.txt >phyaddr.txt
for /f "skip=2 tokens=12" %%M in (phyaddr.txt) do set Mac=%%M
if exist IPAddr.txt del IPaddr.txt
find "IP Address" ipconfig.txt >IPAddr.txt
for /f "skip=2 tokens=15" %%I in (IPAddr.txt) do set IP=%%I
arp -s %IP% %Mac%
if exist GateIP.txt del GateIP.txt
find "Default Gateway" ipconfig.txt >GateIP.txt
for /f "skip=2 tokens=13" %%G in (GateIP.txt) do set GateIP=%%G
if exist GateMac.txt del GateMac.txt
arp -a %GateIP% >GateMac.txt
for /f "skip=3 tokens=2" %%H in (GateMac.txt) do set GateMac=%%H
arp -s %GateIP% %GateMac%
exit
调用方法:SetLocalIP( tcCard,tcAddr, tcMask, tcGateWay,tcDNS,tcAddDNS )
*!* *---------------------------------------------
Function SetLocalIP( tcCard, tcAddr, tcMask, tcGateWay, tcDNS, tcAddDNS )
tcCard = Iif(Type([tcCard])=[C], tcCard, [本地连接]) && 连接名称
tcAddr = Iif(Type([tcAddr])=[C], tcAddr, []) && IP地址, 空表示动态获取
tcMask = Iif(Type([tcMask])=[C], tcMask, [255.255.255.0]) && 掩码
tcGateWay = Iif(Type([tcGateWay])=[C], tcGateWay, [none]) && 网关
tcDNS = Iif(Type([tcDNS])=[C], tcDNS, Null) && 主DNS
tcAddDNS = Iif(Type([tcAddDNS])=[C], tcAddDNS, Null) && 备用DNS
Local lcCmdStr, lcTempFile, lnReturn, lcCR
lcCR = Chr(13)+Chr(10)
lcTempFile = Addbs(Sys(2023))+[SetIP.tmp]
= Strtofile( [interface ip] + lcCR, lcTempFile )
If Empty(tcAddr)
* 指定是通过动态主机配置协议 (DHCP) 服务器配置 IP 地址
= Strtofile( [set address name="]+tcCard+[" source=dhcp] + lcCR, lcTempFile, 1 )
Else
* 使用静态 IP 地址
= Strtofile( [set address name="]+tcCard+[" source=static] ;
+ [ addr=]+tcAddr+[ mask=]+tcMask+[ GateWay=]+tcGateWay+[ gwmetric=1] + lcCR, lcTempFile, 1 )
Endif
If !Isnull(tcDNS)
* 设置 DNS
If Empty(tcDNS)
= Strtofile( [set dns name="]+tcCard+[" source=dhcp] + lcCR, lcTempFile, 1 )
Else
= Strtofile( [set dns name="]+tcCard+[" source=static addr=]+tcDNS + lcCR, lcTempFile, 1 )
Endif
Endif
If !Isnull(tcAddDNS) And !Empty(tcAddDNS)
* 备用 DNS
= Strtofile( [add dns name="]+tcCard+[" addr=]+tcAddDNS+[ index=2] + lcCR, lcTempFile, 1 )
EndIf
lcCmdStr = [netsh exec ] + lcTempFile
Return ShellAndWait( lcCmdStr, .T., .F. ) > 0
Endfunc
Function ShellAndWait( tcDosCommand, IsDos, IsShow )
IsDos = Iif(Parameters()>=2 And Type([IsDos])=[L], IsDos, .F.)
IsShow = Iif(Parameters()>=3 And Type([IsShow])=[L], IsShow, !IsDos)
Local lcCmdStr
If IsDos
If Empty(Getenv([OS]))
lcCmdStr = [Command.com /C ] + tcDosCommand + Chr(0)
Else
lcCmdStr = [Cmd.exe /C ] + tcDosCommand + Chr(0)
Endif
Else
lcCmdStr = tcDosCommand + Chr(0)
Endif
#Define NORMAL_PRIORITY_CLASS 32
#Define IDLE_PRIORITY_CLASS 64
#Define HIGH_PRIORITY_CLASS 128
#Define INFINITE -1
#Define REALTIME_PRIORITY_CLASS 1600
Declare Integer CloseHandle In kernel32 Long hObject
Declare Integer WaitForSingleObject In kernel32 Long hHandle, Long dwMilliseconds
Declare Integer CreateProcessA In kernel32 ;
Long lpApplicationName, ;
String lpCommandLine, ;
Long lpProcessAttributes, ;
Long lpThreadAttributes, ;
Long bInheritHandles, ;
Long dwCreationFlags, ;
Long lpEnvironment, ;
Long lpCurrentDirectory, ;
String @lpStartupInfo, ;
String @lpProcessInformation
Local lcStartupInfo, lcProcessInformation, RetCode, hProcess
lcStartupInfo = Long2Str(68) + Replicate(Chr(0), 40) + Chr(Iif(IsShow,0,1)) + Replicate(Chr(0), 23)
lcProcessInformation = Replicate(Chr(0), 16)
RetCode = CreateProcessA(0, lcCmdStr, 0, 0, 1, NORMAL_PRIORITY_CLASS, 0, 0, @lcStartupInfo, @lcProcessInformation )
hProcess = Str2Long(Substr(lcProcessInformation, 1, 4))
RetCode = WaitForSingleObject(hProcess, INFINITE)
RetCode = CloseHandle(hProcess)
Return RetCode
Endfunc
*-----------------------------------------------
Function Long2Str( lnLongVal )
Private i, retustr
retustr = []
For i = 24 To 0 Step -8
retustr = Chr(Int(lnLongVal/(2^i))) + retustr
lnLongVal = Mod(lnLongVal, (2^i))
Next
Return retustr
Endfunc
Function Str2Long( tcLongStr )
Private i, RetuVal
RetuVal = 0
For i = 0 To 24 Step 8
RetuVal = RetuVal + (Asc(tcLongStr) * (2^i))
tcLongStr = Right(tcLongStr, Len(tcLongStr) - 1)
Next
Return RetuVal
Endfunc
IP地址快速切换
当前笔记本的可移动性增强,我们需要在多个环境下接入网络开始工作,在没有路由器自动分配的情况下,就需要每次手动设置IP地址和网关等信息,相当烦琐,通过实践利用netsh命令用快捷方式实现了IP地址的快速切换。
A. 基本用法
1,新建记事本,写入如下信息:
int ip
set address name="本地连接" source=static addr=192.168.0.107 mask=255.255.255.0
set address name="本地连接" gateway=192.168.0.1 gwmetric=1
保存为218.sh,请注意后缀名为sh。解释一下,以上设置的addr是IP地址,mask是子网掩码,gateway是网关,gwmetric是默认网关的跃点数。
2,再新建记事本,写入如下信息:
netsh exec 218.sh
保存为218.bat批处理文件,这个很容易理解,就是通过netsh命令运行一个脚本文件。当运行此批处理时,就调用了218.sh文件,达到修改IP地址和网关的目的。
把以上文件放在硬盘任何位置,再建立快捷方式指向218.bat,就很容易实现的了对IP地址的方便切换和控制。
B. 应用扩展
以上情况只是更改了IP,掩码,网关,用样的道理,更改sh文件又有不同用法:
int ip
set address name="本地连接" source=dhcp //自动获取IP地址
set dns name="本地连接" source=dhcp //自动获取DNS
set dns "本地连接" static 192.168.0.1 primary //设置首选DNS为192.168.0.1
set dns "本地连接" static none //清除DNS列表
其实刚才第一个例子可以简单的写为:
set address "本地连接" static 192.168.0.107 255.255.255.0 192.168.0.1 1
“本地连接”为name连接名,192.168.0.107为addr待设定IP地址,255.255.255.0为mask子网掩码,192.168.0.1 1为gateway网关,1为gwmetric默认网关的跃点数。
C. netsh命令的基本和扩展用法
netsh>/?
下列指令有效:
此上下文中的命令:
.. - 移到上一层上下文级。
? - 显示命令列表。
aaaa - 更改到 `netsh aaaa' 上下文。
abort - 丢弃在脱机模式下所做的更改。
add - 在项目列表上添加一个配置项目。
alias - 添加一个别名
bridge - 更改到 `netsh bridge' 上下文。
bye - 退出程序。
commit - 提交在脱机模式中所做的更改。
delete - 在项目列表上删除一个配置项目。
dhcp - 更改到 `netsh dhcp' 上下文。
diag - 更改到 `netsh diag' 上下文。
dump - 显示一个配置脚本。
exec - 运行一个脚本文件。
exit - 退出程序。
help - 显示命令列表。
interface - 更改到 `netsh interface' 上下文。
ipsec - 更改到 `netsh ipsec' 上下文。
offline - 将当前模式设置成脱机。
online - 将当前模式设置成联机。
popd - 从堆栈上打开一个上下文。
pushd - 将当前上下文放入堆栈。
quit - 退出程序。
ras - 更改到 `netsh ras' 上下文。
routing - 更改到 `netsh routing' 上下文。
rpc - 更改到 `netsh rpc' 上下文。
set - 更新配置设置。
show - 显示信息。
unalias - 删除一个别名。
wins - 更改到 `netsh wins' 上下文。
下列的子上下文可用:
aaaa bridge dhcp diag interface ipsec ras routing rpc wins
若需要命令的更多帮助信息,请键入命令,接着是空格,后面跟 ?。
netsh>
————————————————————
由以上可以看出,在netsh模式下,还可进入下级的操作模式:
aaaa
|—下列指令有效:
命令从 netsh 上下文继承:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
此上下文中的命令:
show clients - 以脚本格式转储 aaaa 客户端设置。
show config - 以脚本格式转储 aaaa 配置信息。
show connection_request_policies - 以脚本格式转储 aaaa 连接请求策略设置。
show logging - 以脚本格式转储 aaaa 日志记录设置。
show remote_access_policies - 以脚本格式转储 aaaa 远程访问策略设置。
show server_settings - 以脚本格式转储 aaaa 服务器设置。
show version - 显示一个 aaaa 配置数据库的版本。
bridge
|—下列指令有效:
命令从 netsh 上下文继承:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
此上下文中的命令:
show adapter - 显示配置为单桥的适配器。
dhcp
|—下列指令有效:
list - 列出所有可用的命令。
help - 显示帮助。
? - 显示帮助。
add server - 在目录服务上的授权服务器中添加服务器。
delete server - 从目录服务中的授权的服务器列表中删除一 DHCP 服务器。
show server - 为当前域显示所有在目录服务中的 DHCP 服务器
server [//servername/ipaddress] - 将上下文切换到指定的服务器。
空值意味着是本地机器。
diag
|—下列指令有效:
命令从 netsh 上下文继承:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
此上下文中的命令:
show adapter - 显示所有适配器。
show all - 显示所有类别。
show client - 显示所有网络客户端。
show computer - 显示计算机信息。
show dhcp - 显示每个适配器的 DHCP 服务器。
show dns - 显示每个适配器的 DNS 服务器。
show gateway - 显示每个适配器的默认网关服务器。
show ieproxy - 显示 Internet Explorer 的服务器名称和端口号。
show ip - 显示每个适配器的 IP 地址。
show mail - 显示邮件服务器名称和端口号。
show modem - 显示所有调制解调器。
show news - 显示新闻服务器名称和端口号。
show os - 显示操作系统信息。
show test - 显示所有类别并执行所有测试。
show version - 显示 Windows 和 WMI 版本。
show wins - 显示每个适配器的首选和辅助 WINS 服务器。
interface
|—下列指令有效:
命令从 netsh 上下文继承:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
此上下文中的命令:
show credentials - 显示用于连接到接口的凭据。
show interface - 显示接口。
ipsec
|—下列指令有效:
下列指令有效:
此上下文中的命令:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
ras
|—下列指令有效:
命令从 netsh 上下文继承:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
此上下文中的命令:
show activeservers - 听远程访问服务器广告。
show authmode - 显示身份验证模式。
show authtype - 显示当前启用的身份验证类型。
show client - 显示连接到此计算机的远程访问客户端。
show link - 显示 PPP 要协商的链接属性
show multilink - 显示 PPP 要协商的多重链接
show registeredserver - 显示计算机是否注册为在给定的域中的
Active Directory 中的远程访问服务器。
show user - 为用户显示远程访问属性。
routing
|—下列指令有效:
此上下文中的命令:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
rpc
|—下列指令有效:
此上下文中的命令:
? - 显示命令列表。
add - 创建子网的“添加”列表。
delete - 创建子网的“删除”列表。
dump - 显示一个配置脚本。
help - 显示命令列表。
reset - 将选择绑定设置重置为 'none' (监听所有界面)。
show - 在系统上显示每个子网的选择绑定状态。
wins
|—下列指令有效:
此上下文中的命令:
show alias - 列出所有定义的别名。
show helper - 请列出所有顶层的助手。
show mode - 显示当前的模式。
netsh命令功能的强大,在这里就不深入讨论了,有兴趣的朋友可在cmd模式下,直接netsh回车进入netsh模式,提醒一点,回到上级用.或者..,退出netsh模式用bye或者exit。