【问题标题】:Is this C callback safe with C++ objects?这个 C 回调对于 C++ 对象是否安全?
【发布时间】:2014-11-06 19:06:51
【问题描述】:

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

我的代码sn-p:

// 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”之类的东西,我关心的是 callback F,最初在 C 中定义,我可以通过以这种方式测试函数和 C++ foo 对象? .

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

P.S. 我可以在回调函数中进行异常处理吗? (在“testfunction”中使用 try catch)。它适用于我的情况,但不确定它是否合法。

【问题讨论】:

  • 你的方式已经足够好了——没有限制让事情变得不必要的复杂。只要您的链接器设法构建最终的可执行文件或共享对象,您就可以以任何方式混合 c 和 c++。
  • 简短的回答是肯定的,这很好。但是,我会将__cdecl 添加到testfunction 的声明中,以防它不是给定平台上的默认值。
  • @lowtech 谢谢,无论如何,我经常听说混合 c 和 c++ 会导致问题...
  • @JamesKanze NOPE,它是合法的 C++,无需将其设为外部“C”。当您尝试链接 C 对象文件时使用外部“C”以及防止某些函数上的名称修改的方法
  • @lowtech 不符合标准。 §7.5/1:“具有不同语言链接的两个函数类型是不同的类型,即使它们在其他方面是相同的。”如果他正在使用的结构是在extern "C" 块中声明的,那么指向函数的指针就是指向extern "C" 函数的指针,并且具有与他的函数不同的类型。 (而且 FWIW,我使用了 C 和 C++ 之间调用约定不同的编译器。)

标签: c++ c numerical


【解决方案1】:

我不熟悉相关库,但总的来说, 将指向回调的指针和void* 传递给 一个 C 例程,它将使用 void* 回调回调, 为了确保安全,您需要做两件事:

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

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

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

对于您发布的确切示例,您唯一需要做的就是 就是将testfunction 声明为extern "C",而你们都是 对。如果您稍后开始使用多态对象, 但是,请注意第二点。

【讨论】:

  • 我一直在尝试使用 Ideone c++11 并感到惊讶。
  • 谢谢,我用的是g++,好像没有把testfunction指定为“extern C”,没有问题,只要gsl_integration_cquad的头文件中包含“ifdef __cplusplus extern C”。
  • 值得指出的是,C++ 异常可能会导致堆栈展开 C 代码,这可能没有为这种可能性做好准备。也就是说,回调上下文中的 C++ 异常可能会使工作区对象处于错误状态。
  • @lorniper g++ 和 VC++ 在这里都有问题。一个好的编译器至少会给你一个警告。我曾在不同的平台上工作过。 (Microsoft 使用非标准机制来指定调用约定,甚至认为 C++ 标准提供了所需的内容。这可能是因为它们在支持 C++ 之前很久就支持不同的约定。)
  • @cdhowie 好点。它适用于大多数编译器,但绝对不能保证。
【解决方案2】:

You can use

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

void*.

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

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

【讨论】:

  • 很多 C 库都提供了这样的 void* 接口,而且我经常使用静态转换,在我看来相当安全。也许您可以解释更多,因为我根本无法将接口更改为 C 库函数,那么我应该如何使用 C++ objs 提供 C 回调???
  • This answer 表示更喜欢static_cast
  • @πάνταῥεῖ 我会避免使用reinterpret_cast;它会起作用,但它会给阅读代码的人错误的信息。
  • @Barry 我也想说更喜欢static_cast,但你引用的答案就标准而言是完全错误的。 (void 一个对象类型。它是一个不完整的类型,无法完成,但void* 算作指向对象的指针。)
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2017-02-17
    • 1970-01-01
    • 2010-11-27
    • 2017-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    相关资源
    最近更新 更多