首页 > 代码库 > C#调用Win32 api时的内存操作

C#调用Win32 api时的内存操作

一般情况下,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\n");            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("\tNumber of entries: %d\n",               (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  #region struct  2         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]  3         public struct MibIpForwardRow  4         {  5             public uint dwForwardDest;  6             public uint dwForwardMask;  7             public uint dwForwardPolicy;  8             public uint dwForwardNextHop;  9             public uint dwForwardIfIndex; 10             public uint dwForwardType; 11             public uint dwForwardProto; 12             public uint dwForwardAge; 13             public uint dwForwardNextHopAS; 14             public uint dwForwardMetric1; 15             public uint dwForwardMetric2; 16             public uint dwForwardMetric3; 17             public uint dwForwardMetric4; 18             public uint dwForwardMetric5; 19         } 20  21         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 22         public struct MibIpForwardTable 23         { 24             public uint dwNumEntries; 25             public IntPtr table; 26         } 27  28         [StructLayout(LayoutKind.Sequential)] 29         public struct S_un_B 30         { 31             public byte s_b1; 32             public byte s_b2; 33             public byte s_b3; 34             public byte s_b4; 35         } 36  37         [StructLayout(LayoutKind.Sequential)] 38         public struct S_un_w 39         { 40             public ushort s_w1; 41             public ushort s_w2; 42         } 43  44         [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] 45         public struct IN_ADDR 46         { 47             [FieldOffset(0)] 48             public S_un_B B_addr; 49  50             [FieldOffset(0)] 51             public S_un_w St_Addr; 52  53             [FieldOffset(0)] 54             public uint S_addr; 55         } 56         #endregion 57  58 [DllImport("iphlpapi.dll", SetLastError = true)] 59 public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref ulong pdwSize, bool bOrder); 60  61 public IEnumerable<Win32.MibIpForwardRow> ForwardTable() 62         { 63             ulong TableLength() 64             { 65                 ulong _len = 0; 66                 var zero = new MibIpForwardTable(); 67                 GetIpForwardTable(ref zero, ref _len, false); 68                 return _len; 69             } 70  71             var buffSize = TableLength(); 72             var tablePtr = Marshal.AllocHGlobal((int)buffSize); 73             GetIpForwardTable(tablePtr, ref buffSize, false); 74  75             var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr); 76             var list = new List<MibIpForwardRow>((int)table.dwNumEntries); 77             var begin = table.table = tablePtr + sizeof(uint); 78             for (var i = 0; i < table.dwNumEntries; i++) 79             { 80                 list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin)); 81                 begin += Marshal.SizeOf<MibIpForwardRow>(); 82             } 83  84             Marshal.FreeHGlobal(tablePtr); 85             return list; 86         } 87  88 public static string ToInAddrString(this uint value) 89         { 90             var addr = new IN_ADDR() { S_addr = value }; 91             var ptr = Marshal.AllocHGlobal(64); 92             RtlIpv4AddressToString(ref addr, ptr); 93             var ip = Marshal.PtrToStringAnsi(ptr); 94             Marshal.FreeHGlobal(ptr); 95             return ip; 96         } 97  98 static void Main(string[] args) 99         {100             var route = new Router();101             foreach (var r in route.ForwardTable())102                 Console.WriteLine(r.dwForwardDest.ToInAddrString());103         }
View Code

输出:

0.0.0.0127.0.0.0127.0.0.1127.255.255.255192.168.199.0192.168.199.245192.168.199.255224.0.0.0224.0.0.0255.255.255.255255.255.255.255请按任意键继续. . .

 

C#调用Win32 api时的内存操作