0

I've got a C library that takes a function pointer to register commands. I want to use this in my C++ application. I've tried to use std::function in combination with std::bind to create a C compatible function pointer that will call my member function inside a class. When trying to pass the std::function, I get an compilation error.

// The way the C library typedef's the function
typedef int (*console_cmd_func_t)(int argc, char **argv);

// Function to register the callback needs a struct
struct {
    console_cmd_func_t func;
} console_cmd_t;


void console_cmd_register(const console_cmd_t *cmd) {
    // Register the command
}

// In my C++ class
typedef std::function<int(int argc, char **argv)> ConsoleFunction;
ConsoleFunction fn = std::bind(&MyClass::consoleCommandHandler, this, std::placeholders::_1, std::placeholders::_2);

const esp_console_cmd_t runCommand = {
    .func = fn
};
console_cmd_register(&runCommand);

However, this results in the following error:

cannot convert 'ConsoleFunction' {aka 'std::function<int(int, char**)>'} to 'console_cmd_func_t' {aka 'int (*)(int, char**)'} in initialization

Obviously its not the same definition. If I try to correct that however:

typedef std::function<console_cmd_func_t> ConsoleFunction;

I get the following error:

variable 'ConsoleFunction fn' has initializer but incomplete type

How can I successfully register the command?

273K
  • 29,503
  • 10
  • 41
  • 64
WesleyE
  • 1,384
  • 1
  • 12
  • 29
  • 3
    The C callback either needs to have an extra pointer parameter (commonly `void *userdata`), OR you must utilize global variables to store your callbacks. – HolyBlackCat Feb 17 '22 at 19:08
  • Unfortunately this C library has no way to add `*userdata` for this register function. Am I right in understanding from you answer that if that is the case, this library won't support me providing any bound member functions? – WesleyE Feb 17 '22 at 19:14
  • 2
    `std::bind` and `std::function` just aren't compatible with function pointers. These features encode more information than can be stored in a simple function pointer. – François Andrieux Feb 17 '22 at 19:18
  • 2
    There is no way in C++ to pass a class member function to a C function that expects a function pointer if that is the only parameter to the fucntion. – NathanOliver Feb 17 '22 at 19:19
  • It is neither possible in C nor in C++ to bind extra data to a function. The API of the C's library is broken by design. Period. The only solution is to pass the extra data in a global variable and hope it will suffice. – tstanisl Feb 17 '22 at 19:42
  • That is not the ONLY way. You could create a thunk - ie, a block of executable memory and data storage - where the desired data (such as an object pointer) is stored inside the thunk's storage, and the thunk's code accesses that data as needed. Then you can pass a pointer to that thunk's code anywhere a plain function pointer is needed. But, this is a fairly advanced technique, I only mention it for completeness, as there are plenty of C/C++ libraries that utilize this technique to pass around user data when other ways are not feasible. – Remy Lebeau Feb 17 '22 at 22:54

1 Answers1

1
struct {
    console_cmd_func_t func;
} console_cmd_t;


void console_cmd_register(const console_cmd_t *cmd) {
    // Register the command
}

Your C program is ill-formed. I'm going to assume that console_cmd_t isn't actually an instance of an unnamed struct as is depicted in the quoted code, but is rather a typedef name:

typedef struct {
    console_cmd_func_t func;
} console_cmd_t;

How can I successfully register the command?

By using the types that the functions expect. They don't expect a std::function, so you may not use std::function. There's also no way to register a non-static member function, nor a capturing lambda.

A working example (assuming the correction noted above):

int my_callback(int, char **);
console_cmd_t my_struct {
    .func = my_callback,
};
console_cmd_register(&my_struct);

In order to call a non-static member function, you would typically pass a pointer to the class as an argument into the callback. If the C API doesn't allow passing user defined arguments, then the only option is to use global state. Example:

static MyClass gobal_instance{};

int my_callback(int argc, char **argv)
{
    gobal_instance.consoleCommandHandler(argc, argv);
}

To avoid global state, you need to re-design the C library.

eerorika
  • 232,697
  • 12
  • 197
  • 326