How can I expose C++ function pointers in C?

≯℡__Kan透↙ 提交于 2020-07-03 07:05:31

问题


I have two types of function pointers defined in my C++ that look like this:

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);
typedef std::function<void(std::string)> LogFunction;
Class Core{
...
void myfunc1(LogFunction lg1, CallbackFn callback, int x, std::string y);
};

and I want to be able to expose them in C but I can't seem to find a way to do so. My first try was to cast these as void* and then recast them back to their actual type. but this seems like a bad idea. So I'm clueless as how to go about this conversion.
Also the solution that I need to come-up with should be doable using C++11 at the very least.

Update:

Thank you very much for your answers. However I need to add a bit more explanation as what I'm after. I know about extern "C" and in fact the C++ functions are exposed using this already in my DLL. However, the problem I had was to pass the function pointers back and forth between the C and C++.
One way was to define function pointers in a way that can be directly usable by C. That is I needed to change for example :

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);
typedef std::function<void(std::string)> LogFunction;

to its C compatible one :

typedef void(*CCallbackFn)(bool, char*, int, unsigned char, int length);
typedef void(*CLogFunction)(char* string, int length);

and use these instead. However, the disadvantage of doing this is that, the DLL is also used by C++ clients and this would be a hindrance to change everything C++ to be compatible by C, I'd lose the advantages of C++ by doing this.
Instead I though of coming up with a second way. The C++ stays the same, but for C linkage and interacting with other languages through C API, I do the conversion myself.
That is they use C style and then I convert this back to C++ in the implementation part. In order to further simplify this so I designed some defaults on C++ part as well. Meaning, suppose for the lack of a better example, the instance needs a callback function to log whatever happens. I define a callback function in case it was not given by the user and create two functions for C API specifically something roughly similar to this:

//in core.cpp for example
include "Core.h"
...

extern "C"
{
 Core * core;
 ...

 Core_API void* get_default_log_callback()
 {
   return (void*) core->SomeDefaultCallback();  
 } 

 Core_API void* set_log_callback(void* fn)
 {
    // convert/cast that to the c++ callback type
    // CallbackFn, 
     core->SetCallback(fn_converted);  
 }

and the client could for example use the get_default_log_callback and use its return to set_log_call_back. Basically the idea here is to be able to use the C++ already defined assets. I was stuck at this conversion process, how to convert such callback pointers to a C compatible type ( like what I showed, it'd be really easy to just cast the pointer to void* for example and write a C wrapper that accepts void* and then recast it to the proper type.

I'd like to know about this scenario as well and whether this is a good practice or the otherwise a bad one.

Question two:

Also I'd like to know if it is possible to have a conversion from for example the CCallbackFn and CallbackFn?
Suppose I've got a function(my C function above e.g.) in a CCalbackFn form ,but I want to ultimately have it in CallbackFn form(change it and call the underlying C++ that accepts CallbackFn) ? is this possible ?


回答1:


C doesn't do / cannot handle C++ name mangling (nor C++ types that are not identical to C types). You cannot use non-POD types (and plain function pointers involving types not usable in C) in anything exposed to C. And you need to use extern "C" for the exposed stuff, to disable name mangling (or rather, use whatever naming convention/mangling your current platforms C compiler uses).

In short: use extern "C" for anything that must be callable from C and make sure anything exposed that way only uses types that you can write/use in C.




回答2:


You can expose a function to C by declaring it extern "C".

However, the function must only accept argument types that are valid in C.

From the look of the code above, you're going to have to express your callback in more C-like terms.




回答3:


In order to expose any C++ functions to C, you should wrap the C++ calls in C functions in a plain C++ library. And export only the C functions from it. Use a common header for C functions declarations inside and outside the library. These functions will be callable from any C environment. All the C++ types wrap in a class and pass a pointer to that class across function wrappers, as a handle to C++ environment. The pointer to class should be void* or just long. And only in C++ side you will reinterpret it to own environment class.
Update 1:

  1. You should keep C and C++ separated. It means don't do conversions between C and C++. Keep separated C versions and C++ versions of XX_log_callback functions. For instance your C++ functions uses std::string, py::array_t&. There is no way you can use it is C. No conversion available, and no way to take advantages of it in C. You can take advantage of C++ only in C++, so make a separate version for C++ only and one available for C developers.

  2. This is a by the way. There is a technique of passing around C++ interfaces to C and back to C++. But be attentive, it uses only C compatible return and argument types. It means creating a structure with a pointer to a table of function pointers. In C++ it is an interface but in C it is a struct. This technique is used in COM/OLE2 in Windows. https://www.codeproject.com/Articles/13601/COM-in-plain-C To use such a technique you should understand very well how to make a C++ class compatible with a C struct.

Now I will just copy/paste some pieces of code from the codeproject with little explanations. The rule of thumb when passing interfaces between C and C++, use only types compatible with C as function arguments and as return type. The first four bytes in the interface is a pointer to an array of functions, called Virtual Table:

typedef struct
{
   IExampleVtbl * lpVtbl;//<-- here is the pointer to virtual table
   DWORD          count;//<-- here the current class data starts
   char           buffer[80];
} IExample;

Here you add the pointers to functions in the virtual table. The IExampleVtbl is a structure filled with pointers, and binary it is equivalent to a contiguous array of pointers

static const IExampleVtbl IExample_Vtbl = {SetString, GetString};
IExample * example;

// Allocate the class
example = (IExample *)malloc(sizeof(IExample));

example->lpVtbl = &IExample_Vtbl;//<-- here you pass the pointer to virtual functions
example->count = 1; //<-- initialize class members
example->buffer[0] = 0;

Now this is how you call the methods:

char buffer[80];
example->lpVtbl->SetString(example, "Some text");
example->lpVtbl->GetString(example, buffer, sizeof(buffer));

Keep in mind, all of above is C. In the above example you refer explicitly the virtual table member, and also you pass it explicitly as first parameter in the functions. The C++ equivalent of call to GetString/SetString is:

example->SetString("Some text");
example->GetString(buffer, sizeof(buffer));

Here is the SetString/GetStrinf functions and the virtual table structure:

HRESULT STDMETHODCALLTYPE SetString(IExample *this, char * str)
{
   memcpy(this->buffer, str, length);//be attentive, it is almost pseudocode
   return(0);
}
HRESULT STDMETHODCALLTYPE GetString(IExample *this, char *buffer, int buffer_len)
{
   memcpy(str, this->buffer, length);//be attentive, it is almost pseudocode
   return(0);
}
typedef struct {
   SetStringPtr       *SetString;
   GetStringPtr       *GetString;
} IExampleVtbl;

The STDMETHODCALLTYPE is to make it compatible with C++ calling of member function classes, so you will be able to pass the IExample between C and C++. I believe this will be really a nightmare for the C programmers, but not an easy task for C++ counterparts.
To access that when interface is passed from C, you declare interface like this:

class IExample
{
public:
   virtual HRESULT  SetString(char * str) = 0;//<-- see first parameter gone away in both functions
   virtual HRESULT GetString(char *buffer, int buffer_len) = 0;
};

If you implement in C++ to pass in C equivalent of above code will be:

class IExample
{
    int count = 1; //<-- initialize class members
    char buffer[80] = "";
public:
   virtual HRESULT  SetString(char * str)
   {
      memcpy(this->buffer, str, length);//be attentive, it is almost pseudocode
      return(0);
   }
   virtual HRESULT GetString(char *buffer, int buffer_len)
   {
      memcpy(str, this->buffer, length);//be attentive, it is almost pseudocode
      return(0);
   }
};

One more thing. You don't use the C declaration in C++ and vice-versa. This is by the COM approach to address the issue. It might be not portable to different compilers but keep in mind, similar approach is done in the old CORBA. Only you should keep in mind. You create one interface for C and one for C++. On C++ part hide the C interface and on C hide the C++ interface. Pass around only the pointers.




回答4:


I ultimately came up with my own solution which I myself refer to as "Delegating Callbacks" approach! The idea here is that, instead of directly use the C callback, you create a diversion, you create an intermediate callback that acts as a translator between the two APIs. For example, suppose my C++ class has a method that accepts only callbacks with this signature :

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);

And now we want to expose this to C. and this is our C callback signature :

typedef void(*CCallbackFn)(bool, const char*, unsigned char*, int rows, int cols);

Now how do we go from the first to the second one or vice versa? We create a new callback in our C++ class of type CallbackFn, and inside it execute the C callbacks. So using an indirect call, we can easily decouple the signatures between the C and C++ APIs and use the ones that are most suitable for each.

To make it more concrete we need to have something like this:

CORE_API void Core::DelegateCCallback(bool status, std::string id, py::array_t<uint8_t>& img)
{
    //here is used a std::map to store my c-callbacks you can use
    //vector or anything else that you like
    for (auto item: this->callbackMap_c)
    {
        //item.first is our callback, so use it like a function 
        item.first(status, id.c_str(), img.mutable_data(), img.shape(0), img.shape(1));
    }
}

And you update your C callback list like this, using two exposed functions, Add and Remove to add and remove any callbacks respectively :

extern "C"
{
//Core is our C++ class for example
Core* core = nullptr;
...
    CORE_API void AddCallback(CCallbackFn callback)
    {
        core->AddCallback_C(callback);
    }

    CORE_API void RemoveCallback(CCallbackFn callback)
    {
        core->RemoveCallback_C(callback);
    }
}

and back in our C++ class, AddCallback_C methods are defined like:

CORE_API void Core::AddCallback_C(CCallbackFn callback)
{
    auto x = this->callbackMap_c.emplace(callback, typeid(callback).name());
}

CORE_API void Core::RemoveCallback_C(CCallbackFn callback)
{
    this->callbackMap_c.erase(callback);
}

Just adding/removing the callback to the callback list. That's all. Now when we instantiate our C++ Code, we need to add the DelegateCCallback to the callback list, so when all C++ callbacks are executed this one executes too and with it, it will loop through all the C callbacks and executes them one by one.

For example in my case, the callbacks needed to be run in a Python module, so in my constructor I had to do something like this:


CORE_API Core::Core(LogFunction logInfo)
{
    //....
    // add our 'Callback delegate' to the list of callbacks
    // that would run.  
    callbackPyList.attr("append")(py::cpp_function([this](bool status, std::string id, py::array_t<uint8_t>& img)
                                                         {
                                                            this->DelegateCCallback(status, id, img);
                                                         }));

//...
}

You can get fancy with this and incorporate threads, etc as you wish.



来源:https://stackoverflow.com/questions/61102468/how-can-i-expose-c-function-pointers-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!