如何将 C++ 回调传递给 C 库函数?

Posted

技术标签:

【中文标题】如何将 C++ 回调传递给 C 库函数?【英文标题】:How do I pass a C++ callback to a C library function? 【发布时间】:2012-05-17 16:39:10 【问题描述】:

我正在使用 C++ 开发我的代码,并希望使用 MPFIT 非线性曲线拟合库,该库是用 C 开发的,但允许在 C++ 中编译。

例如我有一个名为“myClass”的类,而这个类有一个函数myClass::Execute()

我在 myClass.h 文件中包含“mpfit.h”。并尝试从 Execute() 调用一个名为 mpfit 的函数。

int status = mpfit(ErrorFunction, num1, num2, xsub_1D, 0, 0, (void *) &variables, &result);

问题是 ErrorFunction 是 myClass 的一个函数。所以当我尝试使用它时编译器会出错。我试图将 ErrorFunction 带出类对象,但这次我得到了下面给出的错误:

ErrorFunction 在类之外时出错:

错误 4 错误 C2664: 'mpfit' : 无法从 'int 转换参数 1 (__cdecl *)(int,int,double *,double,double *,void *)' 到 'mp_func'

ErrorFunction 在类内时出错:

Error   3   error C3867: 'myClass::ErrorFunction': function call missing argument list; use '&myClass::ErrorFunction' to

误差函数定义:

int ErrorFunction(int dummy1, int dummy2, double* xsub, double *diff, double **dvec, void *vars)

如何调用这个函数并将其解析成 mpfit,它是一个 C 函数?

mp_func 定义为:

/* Enforce type of fitting function */
typedef int (*mp_func)(int m, /* Number of functions (elts of fvec) */
               int n, /* Number of variables (elts of x) */
               double *x,      /* I - Parameters */
               double *fvec,   /* O - function values */
               double **dvec,  /* O - function derivatives (optional)*/
               void *private_data); /* I/O - function private data*/

【问题讨论】:

作为一个附带问题,请注意不要在 C++ 回调函数中抛出任何未捕获的异常。根据架构,您可能会发现如果没有段错误或类似情况,它们将无法通过 C 调用堆栈。 @Component10:即使他们确实通过了 C 调用堆栈,C 代码也有可能由于异常而泄漏内存。因此,如果他们确实在没有段错误的情况下成功,这并不是什么大优势,尽管它可能对调试有所帮助。 【参考方案1】:

确保您的调用约定匹配。 C 库使用 C 调用约定或 cdecl (__cdecl)。如果您在 C++ 中使用 mp_func typedef,它可能默认为编译器的标准调用约定,或 stdcall (__stdcall)。创建一个新的 typedef 或将其更改为以下内容:

typedef int __cdecl (*mp_func)(int m, /* Number of functions (elts of fvec) */
               int n, /* Number of variables (elts of x) */
               double *x,      /* I - Parameters */
               double *fvec,   /* O - function values */
               double **dvec,  /* O - function derivatives (optional)*/
               void *private_data); /* I/O - function private data*/

并且在声明ErrorFunction的时候,也要声明为__cdecl:

int __cdecl ErrorFunction(int, int, double*, double *, double **, void *);

如果调用 mpfit 函数时编译器仍然报错,您可以尝试使用 cdecl 将函数指针转换为 mp_func 类型定义:

int status = mpfit((mp_func)ErrorFunction, num1, num2, xsub_1D, 0, 0, (void *) &variables, &result);

【讨论】:

谢谢。我在类外定义 ErrorFunction 时使用了 mpfit((mp_func)ErrorFunction.....) 。它有效。 很高兴听到。此外,在传递回调函数时,如果它们也被定义为静态,则它们只能是类成员。【参考方案2】:

鉴于您已经展示了mpfit()mp_func 的定义,您需要使用mp_funcprivate_data 参数来传递您的类的this 指针。您当前正在使用该参数来传递您的 variables 项目。让variables 成为您班级的成员(如果还没有的话),然后将this 传递给mpfit()

class MyClass

private:
    TheDataType variables;
    static int ErrorFunction(int m, int n, double *x, double *fvec, double **dvec, MyClass *pThis);
public:
    void DoIt();
;

void MyClass::DoIt()

    // ...
    int status = mpfit((mp_func)&ErrorFunction, num1, num2, xsub_1D, 0, 0, this, &result); 
    // ...


int MyClass::ErrorFunction(int m, int n, double* x, double *fvec, double **dvec, MyClass *pThis)

    // use pThis->variables as needed ...
 

或者:

class MyClass

private:
    static int MPFitErrorFunction(int m, int n, double *x, double *fvec, double **dvec, MyClass *pThis);
    int MyErrorFunction(int m, int n, double *x, double *fvec, double **dvec);
public:
    void DoIt();
;

void MyClass::DoIt()

    // ...
    int status = mpfit((mp_func)&MPFitErrorFunction, num1, num2, xsub_1D, 0, 0, this, &result); 
    // ...


int MyClass::MPFitErrorFunction(int m, int n, double* x, double *fvec, double **dvec, MyClass *pThis)

    return pThis->MyErrorFunction(m, n, x, fvec, dvec);
 

int MyClass::MyErrorFunction(int m, int n, double* x, double *fvec, double **dvec)

    // use this->variables as needed ...
 

【讨论】:

你应该&MPFitErrorFunction转换成mp_func——你应该用正确的签名来声明它,即最后一个参数应该是void*而不是MyClass*。演员表不仅隐藏了潜在的错误,而且在技术上是未定义的行为。 我进行类型转换是因为void* 指针和MyClass* 指针在内存中是相同的,所以它只是避免在函数内部使用局部变量和类型转换:int MyClass::ErrorFunction(int m, int n, double* x, double *fvec, double **dvec, void *private_data) MyClass *pThis = (MyClass*) private_data; // use pThis->variables as needed ... @Remy:但演员阵容隐藏了错误。提问者最初在函数中有错误的签名(包括应该是double*double)。您想利用恰好在您的实现中起作用的东西(将参数作为void* 传递,但将其作为MyClass* 接收),因此您认为强制转换是无害的。但是将参数作为double* 传递并将其作为double 接收在任何实现上都不起作用,因此为其隐藏错误只会导致麻烦。而且它们都不能保证工作,尽管你的工作几乎无处不在。 这类似于编写const_cast<T*>(t) 的经典错误,并认为“所做的只是删除const”,并仔细编写代码以确保t 确实指向一个可变对象。一切都很好,但是如果你不小心使用了指向 volatile 可变对象的tconst_cast 也会删除你忘记考虑的 volatile 并且你有 UB。您的演员已禁用您知道并考虑的一种类型检查,但也禁用了其他会捕获错误的类型检查(例如,如果签名的其余部分或调用约定不匹配)。【参考方案3】:

看起来像:

int ErrorFunction(int dummy1, int dummy2, double* xsub, double diff, double *dvec, void *vars)

应该是:

int ErrorFunction(int dummy1, int dummy2, double* xsub, double *diff, double **dvec, void *vars)

匹配你的

typedef int (*mp_func)(int m, /* Number of functions (elts of fvec) */
               int n, /* Number of variables (elts of x) */
               double *x,      /* I - Parameters */
               double *fvec,   /* O - function values */
               double **dvec,  /* O - function derivatives (optional)*/
               void *private_data); /* I/O - function private data*/

【讨论】:

我进行了更改,但没有成功。同样的错误。还是谢谢 @Emre:你有同样的错误吗?您将ErrorFunction 的签名更改为将double* 作为第4 个参数,将double** 作为第5 个参数,但仍然出现错误“无法从'int (__cdecl *)(int,int,double *,double,double *,void *)' 转换?【参考方案4】:

您的回调必须声明为 extern "C" 才能正常工作。

编辑:我看到人们很难理解这个事实。标准说(7.5/1):

具有不同语言链接的两个函数类型是不同的类型 即使它们在其他方面相同。

【讨论】:

extern "C" 只需要关闭 C++ 名称修改,以便 C 代码可以调用 C++ 代码。由于调用是通过函数指针进行的,并且不需要链接器的帮助来连接调用者和被调用者,因此不需要extern "C" @Ferruccio:有一种观点认为语言链接等于名称修饰。这是错误的。该标准明确指出语言链接是 type 的一部分。具有不同语言链接的两个其他方面相同的函数具有不同的类型。 G++ 将它们视为相同,但这是 G++ 自己的问题。 bug 自 2001 年以来在 GCC bugzilla 中开放(已持续 11 年)。 是的,类型包括函数签名和调用约定,在 C 和 C++ 程序中默认为 __cdecl。我并不是说名称修饰就是链接的全部内容。我的意思是,由于调用约定已经匹配并且链接器不需要解析函数的名称,extern "C" 声明是不必要的。 @Ferruccio 当然有必要。您将函数指针作为参数传递给函数。实参的类型必须与形参的类型相匹配,否则编译器会报错。形参具有 C 链接,因为函数本身具有 C 链接。因此,您的函数指针必须是 C 链接。请注意,类型包括语言链接,而不仅仅是签名和调用约定。 在这种情况下,语言链接无关紧要,因为您传递的是内存地址而不是符号名称。然而,调用约定确实很重要。【参考方案5】:

对于 C++ - to - C 有一个标准的成语,使用 pimpl 成语:

foo_c.h:

#ifdef __cplusplus
extern "C" 
#endif
//forward declaration.  clients of foo_c.h should only hold pointers to Foo_c
typedef struct Foo_c Foo_c;

int someMethod(Foo_c* foo);
#ifdef __cplusplus

#endif

foo_c.cpp:

#include <foo.h>
struct Foo_c 
       Foo foo;

int someMethod(Foo_c* foo)  
     try 
         foo->foo.someMethod();
         return 0; //no error
     
     catch(...) 
         return 1; //error
     

(根据以下答案为外部“C”编辑。)

【讨论】:

你不需要 Foo_c 结构,你可以直接传递 Foo 指针。但无论哪种方式,这个习惯用法只有在 C 库允许您首先指定用户定义的值时才有效。看起来mpfit() 可能允许这样做,但许多 C 库不允许。

以上是关于如何将 C++ 回调传递给 C 库函数?的主要内容,如果未能解决你的问题,请参考以下文章

C,如何将多维数组传递给 CLR/类库项目中的函数

Ruby - 将方法作为回调传递给 DLL 库

将 Go 函数作为回调传递给 C

如何将 C# 中的方法指针传递给 C 库?

C#调用C++动态库接口函数和回调函数

C#调用C++动态库接口函数和回调函数