一般情况下,C#与Win 32 Api的互操作都表现的很一致:值类型传递结构体,一维、二维指针传递IntPtr。在Win32 分配内存时,可以通过IntPtr以类似移动指针的方式读取内存。通过IntPtr移动时,需要考虑指针的计算。规则总体上来说显得一致,但Win32 Api庞杂,总有一些令人困惑的函数。比如GetIpForwardTable。该函数的功能是返回Ip(v4)的路由表。在win32 的结构体定义如下:
DWORD GetIpForwardTable( _Out_ PMIB_IPFORWARDTABLE pIpForwardTable, _Inout_ PULONG pdwSize, _In_ BOOL bOrder ); typedef struct _MIB_IPFORWARDTABLE { DWORD dwNumEntries; MIB_IPFORWARDROW table[ANY_SIZE]; } MIB_IPFORWARDTABLE, *PMIB_IPFORWARDTABLE;
MIB_IPFORWARDROW table[ANY_SIZE]这个定义就很容易使人误解。初一看是一个数组,但搜索之下,却并没有轻易发现ANY_SIZE到底是多大。如果互操作需要定义数组,则一定要指定其大小。再看一下该参数的说明:指向路由实体的指针。很显然,并不是一个数组,而是一个指针。则定义为IntPtr。(确实没有搜索大ANY_SIZE定义)
函数就更有迷惑了,先看定义:
DWORD GetIpForwardTable(
_Out_ PMIB_IPFORWARDTABLE pIpForwardTable,
_Inout_ PULONG pdwSize,
_In_ BOOL bOrder
);
Win32 Api的一般套路是空参数返回全部从参数的大小;传递内存空间返回全部内容的数组。按照常理,返回的预期缓存大小是数组的大小,很容易让人以为是MIB_IPFORWARDROW table[ANY_SIZE]; 所需要的大小(基于字节)。我这么做了,并且网上还有示例也这么做了。我按照自己的要么内存错误,要么读取不到数据。不知道网络上是怎么编译通过的。
网络错误示例:
var buffSize = TableLength(); var size = 1 + buffSize / (ulong)Marshal.SizeOf<MibIpForwardRow>(); var table = new MibIpForwardTable() { dwNumEntries = (uint)size, table = Marshal.AllocHGlobal((int)buffSize) }; /** 思路大概是这样,还对结构体数量加了1 (C语言经常这么干)**/
依然失败。我尝试了多种方法,没有成功。初步怀疑自己理解错了,看了MSDN上,关于该函数的C++示例,果然是自己理解错了。(MSDN关于此函数的链接)
if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { FREE(pIpForwardTable); pIpForwardTable = (MIB_IPFORWARDTABLE *) MALLOC(dwSize); if (pIpForwardTable == NULL) { printf("Error allocating memory "); return 1; } } /* Note that the IPv4 addresses returned in * GetIpForwardTable entries are in network byte order */ if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR) { printf(" Number of entries: %d ", (int) pIpForwardTable->dwNumEntries); for (i = 0; i < (int) pIpForwardTable->dwNumEntries; i++) { /* Convert IPv4 addresses to strings */ IpAddr.S_un.S_addr = (u_long) pIpForwardTable->table[i].dwForwardDest;
看黑色加粗的两个部分。当第一次call此函数时,返回的预期空间大小(基于字节)是指针指向的内存空间大小。是 dwNumEntries + table[ANY_SIZE]的总字节大小。需要自行操作内存,以获取table的首地址。也就是说当通过IntPtr获取到所有的路由表后,需要转换、操作赋值,即:
var tablePtr = Marshal.AllocHGlobal((int)buffSize); GetIpForwardTable(tablePtr, ref buffSize, false); var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr); var list = new List<MibIpForwardRow>(); var begin = table.table = tablePtr + sizeof(uint); for (var i = 0; i < table.dwNumEntries; i++) { list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin)); begin += Marshal.SizeOf<MibIpForwardRow>(); } Marshal.FreeHGlobal(tablePtr); return list;
黑体部分:
现将填充后的IntPtr转换为MibIpForwardTable结构体;这时候会得到dwNumEntries。但第二个参数IntPtr需要手动再赋值。
var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr);
在赋值语句为:
table.table = tablePtr + sizeof(uint);
需要手动向前移动4个字节。
这样内存布局就正确了。
完整代码如下:
1 //一定要指定编码,否则默认是ASCII格式 2 [DllImport("iphlpapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] 3 public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref ulong pdwSize, bool bOrder); 4 5 [DllImport("Ntdll.dll", CharSet = CharSet.Unicode, SetLastError = true)] 6 public static extern IntPtr RtlIpv4AddressToString(ref IN_ADDR Addr, IntPtr s); 7 8 [DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 9 public static extern string inet_ntoa(IN_ADDR inAddr); 10 11 public IEnumerable<Win32.MibIpForwardRow> ForwardTable() 12 { 13 ulong TableLength() 14 { 15 ulong _len = 0; 16 GetIpForwardTable(IntPtr.Zero, ref _len, false); 17 return _len; 18 } 19 20 var buffSize = TableLength(); 21 var tablePtr = Marshal.AllocHGlobal((int)buffSize); 22 GetIpForwardTable(tablePtr, ref buffSize, false); 23 24 var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr); 25 var list = new List<MibIpForwardRow>((int)table.dwNumEntries); 26 var begin = table.table = tablePtr + sizeof(uint); 27 for (var i = 0; i < table.dwNumEntries; i++) 28 { 29 list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin)); 30 begin += Marshal.SizeOf<MibIpForwardRow>(); 31 } 32 33 Marshal.FreeHGlobal(tablePtr); 34 return list; 35 } 36 37 public static string ToInAddrString(this uint value) 38 { 39 var addr = new IN_ADDR() { S_addr = value }; 40 var ptr = Marshal.AllocHGlobal(64); 41 RtlIpv4AddressToString(ref addr, ptr); 42 var ip = Marshal.PtrToStringUni(ptr); 43 Marshal.FreeHGlobal(ptr); 44 return ip; 45 } 46 47 static void Main(string[] args) 48 { 49 var route = new Router(); 50 foreach (var r in route.ForwardTable()) 51 Console.WriteLine(r.dwForwardDest.ToInAddrString()); 52 }
输出:
0.0.0.0 127.0.0.0 127.0.0.1 127.255.255.255 192.168.199.0 192.168.199.245 192.168.199.255 224.0.0.0 224.0.0.0 255.255.255.255 255.255.255.255 请按任意键继续. . .