如何在 C 中公开 C++ 函数指针?

Rik*_*ika 5 c c++ interop function-pointers c++11

我在 C++ 中定义了两种类型的函数指针,如下所示:

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);
};
Run Code Online (Sandbox Code Playgroud)

我希望能够在 C 中公开它们,但我似乎找不到这样做的方法。我的第一次尝试是将这些转换为 asvoid*然后将它们重新转换回它们的实际类型。但这似乎是个坏主意。所以我对如何进行这种转换一无所知。
此外,我需要提出的解决方案至少应该可以使用 C++11。

更新:

非常感谢您的回答。但是,我需要添加更多解释作为我所追求的。我知道extern "C"并且实际上这些C++功能已经在我的DLL. 但是,我遇到的问题是在 C 和 C++ 之间来回传递函数指针
一种方法是以 C 可以直接使用的方式定义函数指针。我需要更改例如:

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

到它的 C 兼容之一:

typedef void(*CCallbackFn)(bool, char*, int, unsigned char, int length);
typedef void(*CLogFunction)(char* string, int length);
Run Code Online (Sandbox Code Playgroud)

并改用这些。但是,这样做的缺点是,C++ 客户端也使用 DLL,这会妨碍将 C++ 的所有内容更改为与 C 兼容,这样做会失去 C++ 的优势。
相反,我想出了第二种方法。C++ 保持不变,但对于 C 链接和通过 C API 与其他语言交互,我自己进行转换。
那就是他们使用 C 风格,然后我在实现部分将其转换回 C++。为了进一步简化这一点,我也在 C++ 部分设计了一些默认值。意思是,假设缺少一个更好的例子,实例需要一个回调函数来记录发生的任何事情。我定义了一个回调函数,以防它不是由用户提供的,并为 C API 创建了两个函数,具体类似于以下内容:

typedef void(*CCallbackFn)(bool, char*, int, unsigned char, int length);
typedef void(*CLogFunction)(char* string, int length);
Run Code Online (Sandbox Code Playgroud)

例如,客户端可以使用 get_default_log_callback 并使用其返回到set_log_call_back. 基本上这里的想法是能够使用 C++ 已经定义的资产。我被困在这个转换过程中,如何将这种回调指针转换为 C 兼容类型(就像我展示的那样,例如,将指针强制转换为 void* 并编写一个接受 void* 的 C 包装器真的很容易然后将其重新转换为正确的类型。

我也想知道这种情况,以及这是一种好的做法还是不好的做法。

问题二:

另外我想知道是否可以从例如CCallbackFnCallbackFn?
假设我有一个形式的函数(例如我的 C 函数)CCalbackFn,但我想最终以CallbackFn形式形式使用它(更改它并调用接受的底层 C++ CallbackFn)?这可能吗 ?

Jes*_*uhl 7

C 不做/不能处理 C++ 名称修改(也不是与 C 类型不同的 C++ 类型)。你不能在暴露给 C 的任何东西中使用非 POD 类型(以及涉及在 C 中不可用的类型的普通函数指针)。你需要使用extern "C"暴露的东西,禁用名称修改(或者更确切地说,使用任何命名约定/修改你的C 编译器使用的当前平台)。

简而言之:extern "C"用于必须可从 C 调用的任何内容,确保以这种方式公开的任何内容仅使用您可以在 C 中编写/使用的类型。


Rik*_*ika 0

我最终提出了自己的解决方案,我自己将其称为“委托回调”方法!这里的想法是,您不直接使用 C 回调,而是创建一个转移,创建一个中间回调来充当两个 API 之间的转换器。例如,假设我的 C++ 类有一个仅接受具有以下签名的回调的方法:

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);
Run Code Online (Sandbox Code Playgroud)

现在我们想将其公开给 C,这是我们的 C 回调签名:

typedef void(*CCallbackFn)(bool, const char*, unsigned char*, int rows, int cols);
Run Code Online (Sandbox Code Playgroud)

现在我们如何从第一个转到第二个,反之亦然?我们在类型为 的 C++ 类中创建一个新的回调CallbackFn,并在其中执行 C 回调。因此,使用间接调用,我们可以轻松解耦 C 和 C++ API 之间的签名,并使用最适合各自的签名。

为了使其更具体,我们需要这样的东西:

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));
    }
}

Run Code Online (Sandbox Code Playgroud)

然后您可以像这样更新 C 回调列表,使用两个公开的函数 Add 和 Remove 分别添加和删除任何回调:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

回到我们的 C++ 类,AddCallback_C方法定义如下:

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);
}
Run Code Online (Sandbox Code Playgroud)

只需向回调列表添加/删除回调即可。就这样。现在,当我们实例化 C++ 代码时,我们需要将 加入DelegateCCallback到回调列表中,因此当所有 C++ 回调执行时,该回调也会执行,并且随之而来的是,它将循环遍历所有 C 回调并一一执行它们。

例如,在我的例子中,回调需要在 Python 模块中运行,因此在我的构造函数中我必须执行以下操作:


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);
                                                         }));

//...
}
Run Code Online (Sandbox Code Playgroud)

您可以按照自己的意愿喜欢它并合并线程等。