如何将 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_func
的private_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 可变对象的t
,const_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 库函数?的主要内容,如果未能解决你的问题,请参考以下文章