关于IcmpSendEcho2的使用和回调问题

news2024/9/21 13:39:58

由于我的需求是短时间内ping多台机子,所以需要异步执行,微软提供的例子是同步方式的,根据微软官方提供的icmpSendEcho2 函数的信息
在这里插入图片描述
,我需要定义一个空的宏PIO_APC_ROUTINE_DEFINED ,定义完之后,编译又出现“未声明的标识符”,最后上网查需要定义两个数据类型。

typedef struct _IO_STATUS_BLOCK
 {
    union {
        long Status;
        void *  Pointer;
    };
    unsigned long *Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;

typedef void (__stdcall* PIO_APC_ROUTINE) (
    void* ApcContext, 
    PIO_STATUS_BLOCK IoStatusBlock, 
    unsigned long Reserved
    );

这样就可以编译通过了,但需要注意的是你最后需要调用SleepEx,你设置的回调函数才会被执行。

下列代码根据微软官方例子改编

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define PIO_APC_ROUTINE_DEFINED//必须要添加在对应的头文件之前,或者在VS解决方案的属性->C/C++->预处理器里添加
typedef
struct _IO_STATUS_BLOCK {
    union {
        long Status;
        void* Pointer;
    };
    unsigned long* Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef
void
(__stdcall* PIO_APC_ROUTINE) (
    void* ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    unsigned long Reserved
    );
#include <winsock2.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <stdio.h>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

int CALLBACK ReplyCame(PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG Reserved)
{
    //char* szAddr = (char*)ApcContext;//可以不转,直接用ApcContext去打印
    printf("Replay Came for %s...... \n", ApcContext);
    g_bReply = TRUE;
    return 0;
}

int __cdecl main(int argc, char** argv)
{
    HANDLE hIcmpFile;
    unsigned long ipaddr = INADDR_NONE;
    DWORD dwRetVal = 0;
    DWORD dwError = 0;
    char SendData[] = "Data Buffer";
    LPVOID ReplyBuffer = NULL;
    DWORD ReplySize = 0;

    char csIP[] = "192.168.1.103";

    ipaddr = inet_addr(csIP);
    if (ipaddr == INADDR_NONE) {
        printf("usage: %s IP address\n", csIP);
        return 1;
    }

    hIcmpFile = IcmpCreateFile();
    if (hIcmpFile == INVALID_HANDLE_VALUE) {
        printf("\tUnable to open handle.\n");
        printf("IcmpCreatefile returned error: %ld\n", GetLastError());
        return 1;
    }
    // Allocate space for at a single reply
    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData) + 8;
    ReplyBuffer = (VOID*)malloc(ReplySize);
    if (ReplyBuffer == NULL) {
        printf("\tUnable to allocate memory for reply buffer\n");
        return 1;
    }

    dwRetVal = IcmpSendEcho2(hIcmpFile, NULL, (PIO_APC_ROUTINE)ReplyCame, NULL,
        ipaddr, SendData, sizeof(SendData), NULL,
        ReplyBuffer, ReplySize, 1000);
    if (dwRetVal != 0) {
        PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
        struct in_addr ReplyAddr;
        ReplyAddr.S_un.S_addr = pEchoReply->Address;
        printf("\tSent icmp message to %s\n", argv[1]);
        if (dwRetVal > 1) {
            printf("\tReceived %ld icmp message responses\n", dwRetVal);
            printf("\tInformation from the first response:\n");
        }
        else {
            printf("\tReceived %ld icmp message response\n", dwRetVal);
            printf("\tInformation from this response:\n");
        }
        printf("\t  Received from %s\n", inet_ntoa(ReplyAddr));
        printf("\t  Status = %ld  ", pEchoReply->Status);
        switch (pEchoReply->Status) {
        case IP_DEST_HOST_UNREACHABLE:
            printf("(Destination host was unreachable)\n");
            break;
        case IP_DEST_NET_UNREACHABLE:
            printf("(Destination Network was unreachable)\n");
            break;
        case IP_REQ_TIMED_OUT:
            printf("(Request timed out)\n");
            break;
        default:
            printf("\n");
            break;
        }

        printf("\t  Roundtrip time = %ld milliseconds\n",
            pEchoReply->RoundTripTime);
    }
    else {
        dwError = GetLastError();
        if (dwError != ERROR_IO_PENDING)//调用IcmpSendEcho2使用回调的方式时,返回ERROR_IO_PENDING是正常的结果,并不是错误
        {
            printf("Call to IcmpSendEcho2 failed.\n");
            switch (dwError) {
            case IP_BUF_TOO_SMALL:
                printf("\tReplyBufferSize too small\n");
                break;
            case IP_REQ_TIMED_OUT:
                printf("\tRequest timed out\n");
                break;
            default:
                printf("\tExtended error returned: %ld\n", dwError);
                break;
            }
            return 1;
        }
    }
    SleepEx(1000, TRUE);//没这行代码回调不会被执行
    printf("全部处理完成。\n");
    system("pause");
    return 0;
}

遇到的问题和解决方式:

  • 程序一回调就崩溃
    编译没问题,回调也调用的,但调用完就崩,崩在了不知名的位置。后来上网查了,看到有说是__stdcall和__cdecl的问题。因为汇编是通过入栈出栈的方式调用函数的,在调用函数前,把参数压入栈,执行call指令才会跳到函数体执行,这时函数自己再通过出栈的方式使用参数,而__stdcall和__cdecl的区别就是在函数执行完后栈顶的处理方式不同。C/C++的函数默认是__cdecl。

__cdecl需要在函数返回之后,根据之前压栈的参数去调整栈顶。如下图
在这里插入图片描述

__stdcall是函数内部自己根据参数调整栈顶。如下图
在这里插入图片描述

也就说遵守__cdecl规定的人A 去写汇编,它写的函数并不会在ret时调整栈顶,它会在call指令之后,加一条指令去调整栈顶,让栈顶恢复到调用函数之前。而遵守__stdcall规定的人B 去写汇编,它就会在自己的函数里面调整栈顶,让栈顶恢复到调用函数之前。而当着两种规定的人A B一起写代码的时候,当B用call指令去调用A的函数时,由于B默认就A的函数是自己处理去调整栈顶的,实际上A并没有,所以当函数返回的时候,栈顶可能已经不是调用函数之前的栈顶了,如果后续从栈顶拿数据进行操作的时候,可能就因为操作非法地址崩了。所以现在要做的就是让它ret之后,栈顶能恢复到调用之前就行了,用什么方式都无所谓。
由于我们使用的不是源文件,而是库,所以它的指令已经是固定的了,如果它是B去写的汇编,那我们就要按B的方式去写函数,也就是函数声明要加__stdcall,同时函数的参数也要保证ret后,栈顶能恢复到调用之前就行了。
VS有个调试功能叫反汇编,需要看汇编代码的,可以在调试->窗口->反汇编,但需要代码编译成功并运行的时候才会有。

  • 未定义标识符PIO_APC_ROUTINE
    在微软官方文档中没有找到PIO_APC_ROUTINE的定义,这定义也是在别人帖子上看到的。根据我们前面讲的,我们知道只要函数ret之后,栈顶能恢复到call之前就行,所以你怎么定义这个PIO_APC_ROUTINE都行,只要能让栈顶恢复就不会崩,甚至能把参数缩短到两个(4个字节+8个字节),因为它需要将堆栈调整0CH字节。将参数强转,也可以根据你的想法去用。
  • 回调不执行
    它的异步不是完成了就会去执行回调函数,而是需要你用SleepEx函数才会执行回调(目前只尝试到这种方式可以),而且IcmpSendEcho2和SleepEx需要在同一个线程里。所以,如果你想它执行回调,同时又不想阻塞在某个位置,那么只能用其他线程去执行IcmpSendEcho2和SleepEx

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础:回调函数

因为在看Android代码的时候发现了许多关于回调函数的知识, 所以去了解了一下. 对于我来说不太好懂, 因为我觉得看的那些博文的讲法对我来说很绕, 所以我在理解了之后想写一篇关于回调函数的博文来给和我一样理解能力稍差的人一点帮助. 回调函数的作用其实就是将需要这个功能的调…

【JavaGuide面试总结】Redis篇·中

【JavaGuide面试总结】Redis篇中1.Redis 单线程模型了解吗&#xff1f;2.Redis6.0 之后为何引入了多线程&#xff1f;3.Redis 是如何判断数据是否过期的呢&#xff1f;4.过期的数据的删除策略了解么&#xff1f;5.Redis 内存淘汰机制了解么&#xff1f;6.什么是 RDB 持久化&…

【Python+Appium】自动化测试框架

目录&#xff1a;导读 appium简介 设计思路 测试框架设计 测试框架目录结构 测试框架思维导图 测试结果展示 appium简介 Appium 是一个开源的、跨平台的测试框架&#xff0c;可以用来测试 Native App、混合应用、移动 Web 应用&#xff08;H5 应用&#xff09;等&#xf…

Spring之依赖注入源码解析

Spring之依赖注入源码解析 依赖注入原理流程图&#xff1a; https://www.processon.com/view/link/5f899fa5f346fb06e1d8f570 Spring 中有几种依赖注入的方式&#xff1f; 首先分为两种&#xff1a; 1、手动注入 2、自动注入 1、手动注入 在 XML 中定义 Bean 时&#xff0c…

Gartner 再度预测2023低代码趋势,真的会赚钱吗?

2023年&#xff0c;从业者对低代码的发展充满了想象&#xff0c;人们认为&#xff0c;未来低代码的商业价值不可估量。 此话并非空穴来风。据Gartner的最新报告显示&#xff0c;到2023年&#xff0c;超过70%的企业将采用低代码作为他们发展战略的关键目标之一&#xff1b;到202…

训练自己的中文word2vec(词向量)--skip-gram方法

训练自己的中文word2vec&#xff08;词向量&#xff09;–skip-gram方法 什么是词向量 ​ 将单词映射/嵌入&#xff08;Embedding&#xff09;到一个新的空间&#xff0c;形成词向量&#xff0c;以此来表示词的语义信息&#xff0c;在这个新的空间中&#xff0c;语义相同的单…

双塔多目标MVKE

MVKE&#xff1a;Mixture of Virtual-Kernel Experts for Multi-Objective User Profile Modeling MVKE论文中是给用户打tag标记&#xff0c;构建用户画像。使用的也是经典的双塔模型&#xff0c;另外在双塔的基础上面叠加了ctr和cvr的多个目标。但是论文最大的创新点是在用户…

基于龙芯 CPU 的气井控制器的软件设计(三)

4.1 系统软件的总体设计 基于龙芯 CPU 的气井控制器的设计需要开发测试硬件模块的测试软件&#xff0c;主要对 RTC 模块、存储器模块、4G 通信、以太网通信、UART 串口以及 AI 模块进行了驱动程序和 应用程序设计。将各个模块设计为不同的任务&#xff0c;龙芯 RTU 软件设计流程…

Redis 监听过期的key(KeyExpirationEventMessageListener)

目录一、简介二、maven依赖三、编码实现3.1、application.properties3.2、Redis配置类3.3、监听器3.4、服务类3.5、工具类四、测试4.1、测试类4.2、单实例4.3、多实例结语一、简介 本文今天主要是讲Redis中对过期key的监听&#xff0c;可能很多小伙伴不会&#xff0c;或者使用会…

day15_常用类

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、代码块[了解] 三、API 四、Object 五、包装类 六、数学和随机 零、 复习昨日 抽象接口修饰符abstractinterface是不是类类接口属性正常属性没…

Leetcode(每日一题)——1139. 最大的以 1 为边界的正方形

摘要 1139. 最大的以 1 为边界的正方形 一、以1为边界的最大正方形 1.1 动态规划 第530题需要正方形所有网格中的数字都是1&#xff0c;只要搞懂动态规划的原理&#xff0c;代码就非常简洁。而这题只要正方形4条边的网格都是1即可&#xff0c;中间是什么数字不用管。 这题…

Hive的安装与配置

一、配置Hadoop环境先看看伪分布式下的集群环境有没有错误的情况&#xff1a;输入命令&#xff1a;start-all.sh jps查看伪分布式的所有进程是否完善二、解压并配置HiveHive压缩包→ https://pan.baidu.com/s/1eOF_ICZV8rV-CEh3nX-7Xw 提取码: m31e 复制这段内容后打开百度网盘…

逆向 xx音乐 aversionid

逆向 xx音乐 aversionid 版本 7.2.0 版本 7.22.0 第一步&#xff0c;charles 抓包 目标字段 aversionid 加固平台 com.stub.StubApp 360加固s.h.e.l.l.S 爱加密com.secneo.apkwrapper.ApplicationWrapper 梆梆加固com.tencent.StubShell.TxAppEntry 腾讯加固 第二步&…

【网络编程】Java快速上手InetAddress类

概念 Java具有较好的网络编程模型/库&#xff0c;其中非常重要的一个API便是InetAddress。在Java.net 网络编程中中有许多类都使用到了InetAddress 这个类代表一个互联网协议&#xff08;IP&#xff09;地址。 IP地址是一个32&#xff08;IPV4&#xff09;位或128&#xff08;…

求职季哪种 Python 程序员能拿高薪?

本文以Python爬虫、数据分析、后端、数据挖掘、全栈开发、运维开发、高级开发工程师、大数据、机器学习、架构师这10个岗位&#xff0c;从拉勾网上爬取了相应的职位信息和任职要求&#xff0c;并通过数据分析可视化&#xff0c;直观地展示了这10个职位的平均薪资和学历、工作经…

02 Context的使用

对于 HTTP 服务而言&#xff0c;超时往往是造成服务不可用、甚至系统瘫痪的罪魁祸首。 context 标准库设计思路 为了防止雪崩&#xff0c;context 标准库的解决思路是&#xff1a;在整个树形逻辑链条中&#xff0c;用上下文控制器 Context&#xff0c;实现每个节点的信息传递…

Package ‘oniguruma‘, required by ‘virtual:world‘, not found

一、操作系统环境 OS版本信息&#xff1a;Rocky Linux 9.1 PHP版本&#xff1a;8.0.26 安装的依赖&#xff1a; dnf -y install libXpm-devel libXext-devel gmp gmp-devel libicu* icu* net-snmp-devel libpng-devel libjpeg-devel freetype-devel libxslt-devel sqlite…

真正意义上的数字零售,最为重要的一点就是要回归零售本身

互联网浪潮的退却并未真正将人们的思维带离互联网的牢笼&#xff0c;相反&#xff0c;越来越多的人依然在用互联网式的眼光看待后互联网时代的事物。尽管这样一种做法可以在一定程度上取得一定的效果&#xff0c;但是&#xff0c;如果仅仅只是用互联网思维来揣度这一切&#xf…

基于虚拟机机的代码保护技术

虚拟机保护技术是基于x86汇编系统的可执行代码转换为字节码指令系统的代码&#xff0c;以达到保护原有指令不被轻易逆向和篡改的目的。 字节码&#xff08;Byte-code&#xff09;是一种包含执行程序&#xff0c;由一序列 op 代码/数据对组成的 &#xff0c;是一种中间码。字节是…

《第一行代码》 第五章:详解广播机制

如果你了解网络通信原理应该会知道&#xff0c;在一个 IP 网络范围中最大的IP 地址是被保留作为广播地址来使用的。比如某个网络的 IP 范围是 192.168.0XXX&#xff0c;子网掩码是255.255.255.0那么这个网络的广播地址就是 192.168.0255广播数据包会被发送到同-网络上的所有端口…