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 //一定要指定编码,否则默认是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         }
View Code

 

输出:

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
请按任意键继续. . .

 

转载于:https://www.cnblogs.com/jjseen/p/7076831.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值