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         }

输出:

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
请按任意键继续. . .
时间: 2024-10-15 00:58:39

C#调用Win32 api时的内存操作的相关文章

C语言调用WIN32 API教程之1创建窗口

本学习笔记基于VC++6.0开发环境,通过c语言编程语言,调用win32 API进行windows系统应用程序开发. 1,打开VC++6.0,点击 文件->新建->工程->Win32 Application 工程名填写example1,点击确定,选择 一个空工程,点击完成. 2,点击"新建文件" 按钮,新建一个空白文件,点击 文件->另存为 输入文件名example1.c 选择工作空间对应的文件夹,保存. 3,点击FileView,右击Source File,点

MSIL 教程(二):数组、分支、循环、使用不安全代码和如何调用Win32 API(转)

转自:http://www.cnblogs.com/Yahong111/archive/2007/08/16/857574.html 续上文[翻译]MSIL 教程(一) ,本文继续讲解数组.分支.循环.使用不安全代码和如何调用Win32 API 数组 本程序分配一个int型的数组并给他的元素赋值,然后打印出元素和数组的长度. 命令: newarr type— 生成一个元素类型为type 的数组.数组的大小必须在调用该命令前装入堆栈.该命令会把一个数组的引用装入堆栈. stelem.i4— 给一个

C#调用Win32 api学习总结

从.NET平台调用Win32 API Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微软留给我们直接控制Windows的接口. 一.    基础知识 Win32 API是C语言(注意,不是C++语言,尽管C语言是C++语言的子集)函数集. 1. Win32 API函数放在哪里? Win32 API函数是Windows的核心,比如我们看到的窗体.按钮.对话框什么的,都是依靠Win32函

C# call Win32 api时,-1如何转换为DWORD

当使用(uint)-1时,编译器会给出警告:常量-1无法转换为uint,使用unchecked语句重写.DWORD在转换为C#类型时为uint,既然无法使用uint强制转型(-1),那就需要其他办法了.既然编译器给出的提示是使用unchecked语句重写,可以一试.之前没有使用过unchecked语句,所以不熟悉其用法.看了下参考文档,MSDN是这样描述unchecked的: The unchecked keyword is used to suppress overflow-checking

C#调用win32 api 操作其它窗口

实现以下功能: 找到窗体 找到控件(也叫子窗体) 获取内容 获取位置 设置 位置 内容 鼠标点击 示范 1. 找窗体 以操作系统自带的计算器为例 string clWindow = "CalcFrame"; //整个窗口的类名 string tlWindow = "计算器"; //窗口标题 IntPtr ParenthWnd = FindWindow(clWindow, tlWindow); 这样就得到了窗口的句柄 ParenthWnd ,如果 ParenthWnd

C#调用win32 api程序实例

1.声明static extern 方法,使用DllImport特性 class MyClass { [DllImport("kernel32", SetLastError = true)] public static extern int GetCurrentDirectory(int a, StringBuilder b); } 2.调用 StringBuilder sb=new StringBuilder {Length = 250}; MyClass.GetCurrentDir

gdb调用程序崩溃时的内存镜像core文件

/* * gcc gdb_test.c -o gdb_test -g * ./gdb_test * Segmentation fault (core dumped) * gdb gdb_test -c core.5521 * Core was generated by `gdb_test'. * Program terminated with signal 11, Segmentation fault. * #0 0x000000000040048b in main (argc=1, argv=

C语言调用WIN32 API学习之3创建复选框

上一节学习了创建按钮,下面我们来在窗口上创建复选框,并进行点击动作. 1,打开VC++6.0,点击 文件->打开工作空间 选择example1,点击确定,打开工程. 2,添加代码 首先定义全局变量 HWND button1,checkBox1,checkBox2; int checkBoxValve; 更改回调函数如下 LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)//回调函数定义{ swi

C语言调用WIN32 API教程之2创建按钮

上一节学习了创建窗口,下面我们来在窗口上创建按钮,并进行点击动作. 1,打开VC++6.0,点击 文件->打开工作空间 选择example1,点击确定,打开工程. 2,添加代码 首先定义全局变量 HWND button1; 更改回调函数如下 LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)//回调函数定义{ switch(uMsg) { case WM_CREATE: button1=Crea