关于IcmpSendEcho2的使用和回调问题

由于我的需求是短时间内ping多台机子,所以需要异步执行,微软提供的例子是同步方式的,根据微软官方提供的icmpSendEcho2 函数的信息
关于IcmpSendEcho2的使用和回调问题_第1张图片
,我需要定义一个空的宏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 
#include 
#include 
#include 

#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需要在函数返回之后,根据之前压栈的参数去调整栈顶。如下图
关于IcmpSendEcho2的使用和回调问题_第2张图片

__stdcall是函数内部自己根据参数调整栈顶。如下图
关于IcmpSendEcho2的使用和回调问题_第3张图片

也就说遵守__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

你可能感兴趣的:(C,C++,microsoft,c++,windows)