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 = + 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, ) ==
ERROR_INSUFFICIENT_BUFFER) {
FREE(pIpForwardTable);
pIpForwardTable = (MIB_IPFORWARDTABLE *) MALLOC(dwSize);
if (pIpForwardTable == NULL) {
printf("Error allocating memory\n");
return ;
}
} /* Note that the IPv4 addresses returned in
* GetIpForwardTable entries are in network byte order
*/
if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, )) == NO_ERROR) {
printf("\tNumber of entries: %d\n",
(int) pIpForwardTable->dwNumEntries);
for (i = ; 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 = ; 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个字节。
这样内存布局就正确了。
完整代码如下:
//一定要指定编码,否则默认是ASCII格式
[DllImport("iphlpapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref ulong pdwSize, bool bOrder); [DllImport("Ntdll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr RtlIpv4AddressToString(ref IN_ADDR Addr, IntPtr s); [DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern string inet_ntoa(IN_ADDR inAddr); public IEnumerable<Win32.MibIpForwardRow> ForwardTable()
{
ulong TableLength()
{
ulong _len = ;
GetIpForwardTable(IntPtr.Zero, ref _len, false);
return _len;
} var buffSize = TableLength();
var tablePtr = Marshal.AllocHGlobal((int)buffSize);
GetIpForwardTable(tablePtr, ref buffSize, false); var table = Marshal.PtrToStructure<MibIpForwardTable>(tablePtr);
var list = new List<MibIpForwardRow>((int)table.dwNumEntries);
var begin = table.table = tablePtr + sizeof(uint);
for (var i = ; i < table.dwNumEntries; i++)
{
list.Add(Marshal.PtrToStructure<MibIpForwardRow>(begin));
begin += Marshal.SizeOf<MibIpForwardRow>();
} Marshal.FreeHGlobal(tablePtr);
return list;
} public static string ToInAddrString(this uint value)
{
var addr = new IN_ADDR() { S_addr = value };
var ptr = Marshal.AllocHGlobal();
RtlIpv4AddressToString(ref addr, ptr);
var ip = Marshal.PtrToStringUni(ptr);
Marshal.FreeHGlobal(ptr);
return ip;
} static void Main(string[] args)
{
var route = new Router();
foreach (var r in route.ForwardTable())
Console.WriteLine(r.dwForwardDest.ToInAddrString());
}
输出:
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
请按任意键继续. . .
C#调用Win32 api时的内存操作的更多相关文章
- MSIL 教程(二):数组、分支、循环、使用不安全代码和如何调用Win32 API(转)
转自:http://www.cnblogs.com/Yahong111/archive/2007/08/16/857574.html 续上文[翻译]MSIL 教程(一) ,本文继续讲解数组.分支.循环 ...
- C#中调用Windows API时的数据类型对应关系
原文 C#中调用Windows API时的数据类型对应关系 BOOL=System.Int32 BOOLEAN=System.Int32 BYTE=System.UInt16 CHAR=System. ...
- 在Angular.js中的H5页面调用Web api时跨域问题处理
/// <summary> /// 被请求时 /// 在Angular.js中的H5页面调用Web api时跨域问题处理 /// </summary> /// <para ...
- Python调用win32 API绘制正弦波
Python调用win32 API新建窗口与直接创建窗口的流程相同 流程:注册窗口→创建窗口→显示窗口→更新窗口→消息循环 代码: # -*- coding: utf-8 -*- import win ...
- C#调用Win32 api学习总结
从.NET平台调用Win32 API Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微 ...
- C# call Win32 api时,-1如何转换为DWORD
当使用(uint)-1时,编译器会给出警告:常量-1无法转换为uint,使用unchecked语句重写.DWORD在转换为C#类型时为uint,既然无法使用uint强制转型(-1),那就需要其他办法了 ...
- nodejs 调用win32 api
video 教程文件 win32 api >node -v v12.16.1 >npm install -g node-gyp >npm i @saleae/ffi >node ...
- C#调用win32 api 操作其它窗口
实现以下功能: 找到窗体 找到控件(也叫子窗体) 获取内容 获取位置 设置 位置 内容 鼠标点击 示范 1. 找窗体 以操作系统自带的计算器为例 string clWindow = "Cal ...
- 调用Hybris API时遇到的错误消息Cannot find user with uid如何解决
今天工作中试图调用Commerce Cloud的user creation API用代码创建Hybris用户时,遇到下面这个错误消息. 我觉得很奇怪,因为backoffice里能查到这个id为jerr ...
随机推荐
- JavaMail发送邮件后再通过JavaMail接收格式问题
复杂邮件发送问题 转载请标明出处!https://www.cnblogs.com/dream-saddle/p/10978113.html 关于 JavaMail 如何发送邮件这里就不赘述了,网上有很 ...
- Ubuntu 16.04安装Chrome浏览器
一.先有一个hosts能访问Google 参考:http://www.cnblogs.com/EasonJim/p/5999060.html 二.安装方法有两种,如下所示: 1.下载deb包(推荐) ...
- GNS3和Cisco IOU搭建路由交换实验-IOU篇
http://www.mamicode.com/info-detail-605879.html
- Nginx源码分析:3张图看懂启动及进程工作原理
编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」. 导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...
- 深度优先遍历DFS
深度优先遍历,这个跟树中的遍历类似,做深度遍历就是访问一个节点之后,在访问这个节点的子节点,依次下去是一个递归的过程. 具体代码: void DFS(MGraph g ,int i) { in ...
- TDSTCPServerTransport 的Filters
TDSTCPServerTransport 的Filters TDSTCPServerTransport 的 Filter 属性,可以对传递的数据进行加密,压缩,再修改等,有 点注入的概念.默认情况下 ...
- HDU 1248寒冰王座-全然背包或记忆化搜索
寒冰王座 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...
- ZOJ 3201
id=15737" target="_blank">Tree of Tree Time Limit: 1000MS Memory Limit: 32768KB ...
- VBS调用Windows API函数
Demon's Blog 忘记了,喜欢一个人的感觉 Demon's Blog » 程序设计 » VBS调用Windows API函数 « 用VBS修改Windows用户密码 在VB中创建和使用 ...
- 一款炫酷Loading动画--载入失败
简单介绍 上一篇文章一款炫酷Loading动画–载入成功.给大家介绍了成功动画的绘制过程,这篇文章将接着介绍载入失败特效的制作. 相比成功动画,有了前面的经验,失败动画的过程就显得比較简单了. 动画结 ...