zoukankan      html  css  js  c++  java
  • 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
    ");
                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         }
    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
    请按任意键继续. . .
  • 相关阅读:
    一个纠结的问题
    打开SQL Developer时,提示缺少快捷方式
    打开eclipse时,An error has occurred. See the log file
    bash: id : command not found
    Fatal error: Call to undefined function: mysql_connect()解决方法
    struts.xml中标签自动提示问题
    Hibernate向Oracle中添加自增字段
    linux 忘记root密码的解决办法
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'
    Ctrl+Alt+Fn不能切换到字符界面
  • 原文地址:https://www.cnblogs.com/jjseen/p/7076831.html
Copyright © 2011-2022 走看看