关于IcmpSendEcho2的使用和回调问题

8 篇文章 0 订阅
文章详细描述了在Windows环境下,使用IcmpSendEcho2函数进行异步ping多台主机时遇到的问题,包括如何定义PIO_APC_ROUTINE回调函数以解决编译错误,以及理解__stdcall和__cdecl调用约定在函数调用中的作用。同时,指出了回调函数执行需要调用SleepEx函数,并强调了线程同步的重要性。
摘要由CSDN通过智能技术生成

由于我的需求是短时间内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);
    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, csIP,
        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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用IcmpSendEcho函数,您需要做以下几步: 1. 加载icmp.dll库文件。 2. 创建ICMP Echo请求数据包。 3. 打开ICMP句柄。 4. 使用IcmpSendEcho函数发送ICMP Echo请求。 5. 处理IcmpSendEcho函数返回的ICMP Echo响应数据包。 下面是一个简单的使用IcmpSendEcho函数的示例代码: ``` #include <winsock2.h> #include <iphlpapi.h> #include <icmpapi.h> #include <stdio.h> #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") int main() { HANDLE icmpHandle = IcmpCreateFile(); if (icmpHandle == INVALID_HANDLE_VALUE) { printf("Failed to create ICMP handle.\n"); return 1; } char ipAddress[] = "www.google.com"; DWORD ipAddr = inet_addr(ipAddress); if (ipAddr == INADDR_NONE) { hostent* host = gethostbyname(ipAddress); if (host == NULL) { printf("Failed to get IP address of %s.\n", ipAddress); return 1; } ipAddr = *((DWORD*)host->h_addr); } const int bufferSize = 32; char buffer[bufferSize]; memset(buffer, 0, bufferSize); int replySize = sizeof(ICMP_ECHO_REPLY) + bufferSize; LPVOID replyBuffer = (void*)malloc(replySize); if (replyBuffer == NULL) { printf("Failed to allocate memory for reply buffer.\n"); return 1; } ICMP_ECHO_REPLY* reply = (ICMP_ECHO_REPLY*)replyBuffer; int result = IcmpSendEcho(icmpHandle, ipAddr, buffer, bufferSize, NULL, replyBuffer, replySize, 1000); if (result == 0) { printf("IcmpSendEcho failed with error: %ld\n", GetLastError()); return 1; } printf("Reply from %s: bytes=%d time=%ldms TTL=%d\n", ipAddress, reply->DataSize, reply->RoundTripTime, reply->Options.Ttl); free(replyBuffer); IcmpCloseHandle(icmpHandle); return 0; } ``` 这个示例代码会尝试向www.google.com发送ICMP Echo请求,并输出响应数据包的信息。注意,这个示例代码仅供参考,并不保证在所有环境中都能正常工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值