0

I am trying to design telnet-client.c in CPP. While initializing the telnet client, an event handler having the structure telnet_event_handler_t must be passed as shown below (see implementation here):

static void _event_handler(telnet_t *telnet, telnet_event_t *ev,
        void *user_data) {
    // process callback here
}

telnet = telnet_init(telopts, _event_handler, 0, &sock_fd);

In my CPP code, I am calling telnet_init inside a class constructor using std::bind as shown below:

telnet = telnet_init(telnetOpts,
                     std::bind(&TelnetClient::telnetEvent, this, _1, _2, _3),
                     0, &sock_fd);

However, std::bind throws compilation errors shown below:

Scanning dependencies of target client
[ 33%] Building CXX object CMakeFiles/client.dir/src/simple_telnet_client.cpp.o
/home/ravi/simple_telnet_client/src/simple_telnet_client.cpp: In constructor ‘simple_telnet_client::TelnetClient::TelnetClient(const string&, const string&, int)’:
/home/ravi/simple_telnet_client/src/simple_telnet_client.cpp:32:33: error: cannot convert ‘std::_Bind_helper<false, void (simple_telnet_client::TelnetClient::*)(telnet_t*, telnet_event_t*, void*), simple_telnet_client::TelnetClient*, const std::_Placeholder<1>&, const std::_Placeholder<2>&, const std::_Placeholder<3>&>::type’ {aka ‘std::_Bind<void (simple_telnet_client::TelnetClient::*(simple_telnet_client::TelnetClient*, std::_Placeholder<1>, std::_Placeholder<2>, std::_Placeholder<3>))(telnet_t*, telnet_event_t*, void*)>’} to ‘telnet_event_handler_t’ {aka ‘void (*)(telnet_t*, telnet_event_t*, void*)’}
   32 |                        std::bind(&TelnetClient::telnetEvent, this, _1, _2, _3),
      |                        ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                 |
      |                                 std::_Bind_helper<false, void (simple_telnet_client::TelnetClient::*)(telnet_t*, telnet_event_t*, void*), simple_telnet_client::TelnetClient*, const std::_Placeholder<1>&, const std::_Placeholder<2>&, const std::_Placeholder<3>&>::type {aka std::_Bind<void (simple_telnet_client::TelnetClient::*(simple_telnet_client::TelnetClient*, std::_Placeholder<1>, std::_Placeholder<2>, std::_Placeholder<3>))(telnet_t*, telnet_event_t*, void*)>}
In file included from /home/ravi/simple_telnet_client/include/simple_telnet_client/simple_telnet_client.hpp:10,
                 from /home/ravi/simple_telnet_client/src/simple_telnet_client.cpp:5:
/usr/include/libtelnet.h:376:26: note:   initializing argument 2 of ‘telnet_t* telnet_init(const telnet_telopt_t*, telnet_event_handler_t, unsigned char, void*)’
  376 |   telnet_event_handler_t eh, unsigned char flags, void *user_data);
      |   ~~~~~~~~~~~~~~~~~~~~~~~^~
make[2]: *** [CMakeFiles/client.dir/build.make:63: CMakeFiles/client.dir/src/simple_telnet_client.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:76: CMakeFiles/client.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

I am using GCC/G++ 9.4.0 on Ubuntu 20.04.4 LTS.

Update

Defining telnetEvent as friend seems to be working. BUT when using the friend, the send function fails by saying "send() failed: Socket operation on non-socket".

The telnetEvent is defined as shown below:

void telnetEvent(telnet_t *telnet, telnet_event_t *event, void *user_data) {
  TelnetClient *client = static_cast<TelnetClient *>(user_data);

  switch (event->type) {
    // data must be sent
    case TELNET_EV_SEND:
      client->send_all(event->data.buffer, event->data.size);
      break;
    // more cases here
  }
}

In header file:

friend void telnetEvent(telnet_t *telnet, telnet_event_t *event, void *user_data);

In class constructor:

sock_fd = get_socket();
telnet = telnet_init(telnetOpts, telnetEvent, 0, &sock_fd);

Implementation of send_all:

int TelnetClient::send_all(const char *buffer, size_t size) {
  int ret_val = -1;  // default value

  // get the current address
  char *current = (char *)buffer;

  // send data
  while (size > 0) {
    ret_val = send(sock_fd, buffer, size, 0);
    if (ret_val == 0 || errno == EINTR) {
      // try again
      continue;
    } else if (ret_val == -1) {
      fprintf(stderr, "send() failed: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
    }

    // update pointer and size to see if we've got more to send
    buffer += ret_val;
    size -= ret_val;
  }
  return ret_val;
}

Question

How to call telnet_init from a class?

ravi
  • 6,140
  • 18
  • 77
  • 154
  • 1
    And what is your question? No, this is not possible to do with std::bind. Do you understand what `user_data` is for? – KamilCuk Aug 04 '22 at 08:12
  • @KamilCuk: I also tried making `socket_fd` as an input to `send_all`, and then used `client->send_all(client->sock_fd, ...` but the socket still complains "send() failed: Socket operation on non-socket". Any suggestions, please. – ravi Aug 04 '22 at 08:47
  • https://stackoverflow.com/questions/1000663/using-a-c-class-member-function-as-a-c-callback-function https://stackoverflow.com/questions/20290476/how-to-use-a-stdfunction-as-a-c-style-callback https://stackoverflow.com/questions/18848983/is-it-possible-to-bind-this-to-class-member-function-to-make-a-callback-to-c https://stackoverflow.com/questions/33530465/working-with-stdfunction-on-c-libraries `static_cast(user_data);` Soooo you passed `&sock_fd` and you are casting it to `TelnetClient` - that's invalid, assuming `sock_fd` is an `int`. – KamilCuk Aug 04 '22 at 08:49
  • @KamilCuk: The `user_data` saved me. Now `telnetEvent` is declared as `friend`, and init is called as `telnet = telnet_init(telnetOpts, telnetEvent, 0, this);`. Works perfectly. Let me know, your thoughts, please. – ravi Aug 05 '22 at 03:46

1 Answers1

1

Do a trampoline to jump:

class TelnetClient {
   static void trampoline(telnet_t *telnet, telnet_event_t *event, void *user_data) {
        TelnetClient *me = static_cast<TelnetClient *>(user_data);
        me->telnetEvent(telnet, event);
   }

    TelnetClient() {
       telnet_init(telnetOpts, &trampoline, 0, this);
    }

    void telnetEvent(telnet_t *telnet, telnet_event_t *event);
};
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Wowww. Impressive. This is very clever. May I request you to provide an explanation about it? – ravi Aug 05 '22 at 08:10
  • Explanation about which part? – KamilCuk Aug 05 '22 at 08:11
  • The `static trampoline` function, please. – ravi Aug 05 '22 at 08:59
  • 1
    The function has to be static so it is convertible to a regular function pointer -> doesn't depend on any hidden data like a class instance to execute and isn't a member function pointer -> doesn't need `this` pointer to call it. A trampoline is just that - it is called, and jumped to another function. `user_data` holds `this`, so we can convert `user_data` to a pointer to the class instance and call member function on it. – KamilCuk Aug 05 '22 at 09:02