如何使用可变参数注册回调并传递值

问题描述

能否请您帮忙跟进。 我在第三个库头中有回调函数定义:

#ifdef __cplusplus
extern "C" {
#endif
typedef int (*SOCK_CLBK)(int,short,unsigned char*,int,...);
#ifdef __cplusplus
}
#endif

我用以下方式定义我的回调:

文件

template<typename T>
    int ReadTCP(int socket,short code,unsigned char* msg,int received,T a);

cpp 文件

template<>
    int ReadTCP(int socket,int a)
    {
        return 0;
    }

并在代码注册我的回调:

server->registerCallback(port,(SOCK_CLBK)(ReadTCP<int>),maxTcpsize);

这很好用,并且在需要时触发回调。

问题是,“int a”每次都包含随机值。 如何注册回调并传递我自己的特定值,例如 100000,这将发生在“a”中的回调中?

类似的东西

server->registerCallback(port,(SOCK_CLBK)(&std::bind(ReadTCP<int>,_1,_2,_3,_4,100000),maxTcpsize);

但这不起作用(触发运行时异常)。

我做错了什么?

库头:

#include "CSocket.h"

#ifndef WIN32
#include <pthread.h>
#else
#include <windows.h>
#include <WinSock.h>
#endif

#define CPP_TCP_MAX_CLIENTS 17
#define CPP_TCP_MAX_SIZE 1500 //In accordance with MTU definitoins

class DLLCPP_API CSERVER {
public:
    /**
     * default constructor
     */
    CSERVER ();
    /**
     * default destructor
     */
    virtual ~CSERVER ();

    /**! \fn int Create(unsigned int uiPort,bool bClbk);
    *   \brief creates singleton socket listener
    *   creates singleton socket listener,which is invoked within dedicated thread
    *   if bClbk is true,otherwise within the main thread.
    *   \param uiPort socket listener port
    *   \param fnClbk call-back function address. if not NULL then callback mode of operation. otherwise normal.
    *   \return 0 on success,error id otherwise.
    */
    int registerCallback(unsigned int uiPort,SOCK_CLBK fnClbk=NULL,int imsgMaxSize=512)throw (cmexception);
….
…
Send()…
….
...

protected:
#ifndef WIN32
    friend void* _fnTCPClbkThread(void *);  //call-back argument for pthread_create within runclbkThread.
#else
    friend DWORD WINAPI _fnTCPClbkThread( LPVOID lpParam );
#endif

    /**! \fn int IsPending(int iSock,bool& bFail)
     * \brief check pending connection on a non blocking socket
     *  actuall checks for errors and whether or not connection is ready for write operation.
     * \param iSock client socket connection to check.
     * \return: OK (0) if iSock ready for write operation or ERROR otherwise (still pending for instance)
     */
    int IsPending(int iSock)throw (cmexception);
    int runclbkThread();
    int CreateClbk()throw (cmexception);
    void ClbkThread();
private:
    typedef void (CSERVER::*PCLBKTHREAD)(void *);
    PCLBKTHREAD _pThreadClbk;
    int     _iServerSock;
    int     _iSock;         
    SOCK_CLBK   _fnClbk;
    unsigned int _uiPort;
    int _iAddrLen;
    bool _bClbkThreadalive;
    int _iClientConnectionsArr[CPP_TCP_MAX_CLIENTS];
    int _imsgMaxSize;
    struct sockaddr_in  _saddConnect ;
#ifdef WIN32
    WSADATA m_wsaData;
    HANDLE  _thread;
#else
    pthread_t _thread;
#endif

};

函数registerCallback 从中我可以推断出该类不存储任何稍后作为回调参数传递的用户数据......为什么他们有可变参数模板 - 不知道。

解决方法

首先,您使用 ReadTCP 函数模板的代码不正确。 SOCK_CLBK 是一种函数指针类型,它的参数列表末尾有一个 ellipsis,它不同于 int 具有的 ReadTCP<int>(或任何其他类型) .编译器不会编译失败,因为您将指向 ReadTCP<int> 的指针显式转换为 SOCK_CLBK,但调用在运行时失败(您要么在 int a 参数中收到随机值,要么崩溃) .

你的第二段带有 std::bind 的代码也是错误的,因为 std::bind 返回一个 函数对象,而不是一个函数指针。函数对象有operator(),所以可以像函数一样调用,但不能转换为函数指针(一方面,因为对象也包含数据,比如你绑定的参数)。

您必须定义一个接受可变数量参数的函数(即在其参数列表末尾有一个省略号)并将该函数作为回调传递。在该函数中,您可以处理传递的参数并可能调用代码中的其他专用函数,例如 ReadTCP

int ReadTCPCallback(int socket,short code,unsigned char* msg,int received,...)
{
    std::va_list args;
    va_start(args,received);

    // Use variable arguments here,using va_arg. Consult with the API
    // documentation to know what arguments are expected here. For the sake
    // of this example,let's assume an int argument is passed.
    int n = va_arg(args,int);

    int res = ReadTCP(socket,code,msg,received,n);

    // Always call va_end before returning once you're done with va_list
    va_end(args);

    return res;
}

如果你想在这个 API 中使用函数对象,那么你必须找到一种方法,通过第三方库将指向数据的指针传递给回调。该数据将包含与调用相关的绑定参数和其他状态。关于如何将用户的数据传递给回调,请参阅该第三方库的文档。

如果 API 不支持传递用户的数据(这会使其成为一个设计相当糟糕的 API),您可以将数据与 API 返回的与您的状态相对应的某个句柄相关联。例如,您可以维护一个全局 std::map 以将套接字文件描述符 (int) 映射到指向与该套接字或连接相关的数据的指针。