对于C++对象,此 C 回调是否安全

Is this C callback safe with C++ objects?

本文关键字:回调 是否 安全 C++ 对象 对于      更新时间:2023-10-16

我的目的是从我的C++代码中调用一些C函数并传递一些C++对象。事实上,我正在使用GSL库的集成例程(用C编写(,请参阅此链接,

我的代码片段:

// main.cpp
#include <stdio.h>
#include <gsl/gsl_integration.h>
#include <myclass.h>
/* my test function. */
double testfunction ( double x , void *param ) {
    myclass *bar=static_cast<myclass*>(param);
    /*** do something with x and bar***/ 
    return val;
    }
int main ( int argc , char *argv[] ) {
    gsl_function F;  // defined in GSL: double (* function) (double x, void * params)
    /* initialize.*/
    gsl_integration_cquad_workspace *ws = 
    gsl_integration_cquad_workspace_alloc( 200 ) ;    
    /* Prepare test function. */
    myclass foo{}; // call myclass constructor
    F.function = &testfunction;
    F.params =   &foo;

    /* Call the routine. */
    gsl_integration_cquad( &F, 0.0,1.0,1.0e-10,1.0e-10,ws, &res,&abserr,&neval); 

    /* Free the workspace. */
    gsl_integration_cquad_workspace_free( ws );
    return 0;
    }

就我而言,直接调用gsl_integration_cquad似乎没问题,只要标头包含像"ifdef __cplusplus"这样的 sth,我关心的是最初在 C 中定义的回调 F,我是否可以以这种方式传递 testfunction 和 C++ foo 对象?

或者有没有更好的方法来做这种事情,也许是重载并使用函子?

附言我可以在回调函数中进行执行处理吗?(在"测试函数"中使用 try catch(。它适用于我的情况,但不确定它是否合法。

我不熟悉有问题的库,但总的来说,传递指向回调的指针和 void* 时一个C例程,它将用void*回调,您需要做两件事来确保其安全:

  • 您传递其地址的函数必须声明为外部"C"。不用很多编译器就可以侥幸逃脱,但是这是不合法的,一个好的编译器会抱怨。

  • 转换为void*的类型必须完全相同键入作为在回调中将其转换回的类型。 这经典错误是将类似 new Derived 的内容传递给C 函数,并将其强制转换为回调中的Base*。 这往返Derived*void*Base*未定义行为。 它在某些时候会起作用,但在其他时候,它会可能会崩溃,或导致任何其他问题。

  • 正如 cdhowie 在评论中指出的那样,您不想允许异常在 C 代码中传播。 再次,它可能有效。 但未必。

对于您发布的确切示例,您唯一需要做的就是就是宣告testfunctionextern "C",你们都是右。 如果以后开始使用多态对象,但是,请注意第二点。

您可以使用

myclass *bar=static_cast<myclass*>(param);

void*.

如果您的意思是通过 c 回调的 void* 指针传输 c++ 类指针,是的,执行static_cast<>安全的

当通过 c 代码传递时,不会丢失此类指针的 c++ 特定属性。尽管传递派生类指针并将静态强制转换回基类,但正如 Kanze 指出的那样@James无法正常工作。

void*可能只是由 C 库传递,而不查看指向的数据,因此如果它包含C++类,这不是问题。当您将void*正确投射到正确的日志时,应该没有任何问题。

为了确保回调函数本身兼容,可以将其声明为 extern "C" 。此外,还应确保回调函数不会引发异常,因为调用回调的 C 代码不会期望这些异常。

总之,我将代码拆分为一个执行实际工作的函数和另一个用作回调并处理与 C 库的接口的函数,例如:

#include <math.h>
double testfunction ( double x ,myclass *param ) {
    /*** do something with x and bar***/ 
    return val;
}
extern "C" double testfunction_callback ( double x , void *param ) {
    try {
       myclass *bar=reinterpret_cast<myclass*>(param);
       return testfunction(x, bar);
    }
    catch(...) {
       std::cerr << "Noooo..." << std::endl;
       return NAN;
    }
}