C++ 通过3d party library回调成员函数

C++ 通过3d party library回调成员函数,c++,function,callback,member,C++,Function,Callback,Member,我正在使用粒子模拟库。通过以下库函数将交互添加到粒子的方式: AddInteraction(ParticleSet particleSet, void(*interaction)(xyz* p, xyz* v)) 现在我想向AddInteraction传递一个成员函数。我知道,如果不改变库函数,这是不可能做到的。改变 这个库是我想要避免的,但是如果更改很小,我可以将它发送给库的作者并要求实现它 我想写一个简单的例子来说明如何做到这一点。可能有不同的解决方案: Lambda表达式。这些将是完美

我正在使用粒子模拟库。通过以下库函数将交互添加到粒子的方式:

AddInteraction(ParticleSet particleSet, void(*interaction)(xyz* p, xyz* v))
现在我想向AddInteraction传递一个成员函数。我知道,如果不改变库函数,这是不可能做到的。改变 这个库是我想要避免的,但是如果更改很小,我可以将它发送给库的作者并要求实现它

我想写一个简单的例子来说明如何做到这一点。可能有不同的解决方案:

  • Lambda表达式。这些将是完美的,但是库使用CUDA和NVCC,它们还不支持Lamda表达式

  • 函子。我认为函子可以做这项工作。然而,我还没有设法让他们以我想要的方式工作。例如 我无法得到一个要工作的函子列表。此问题在“参数化调用者”下描述:

'如果一个组件有许多回调关系,那么很快就无法将它们全部参数化。考虑一个要维护的按钮 单击事件时要通知的被叫方的动态列表。由于被调用方类型内置于按钮类类型中,因此此列表必须是同构的或无类型的。”

我必须承认,我不理解那个网站上写的所有东西,所以那里可能有答案

  • 使用3d派对库。例如,如中所述的“使用模板函数库的回调”,或使用Boost::函数。但是,这些可能需要对我正在使用的粒子库进行重大更改,对吗
还有其他选择吗?或者有没有办法让上述解决方案之一发挥作用

非常感谢

编辑:

谢谢你的建议。他们帮了我的忙,但我看不出他们是如何完全解决我的问题的。具体地说,我正在努力使这项工作发挥作用:

std::vector<void(*)(xyz*,xyz*)> interactionList;

void AddInteraction(void(*func)(xyz*,xyz*))
{
    interactionList.push_back(func);
}

void Simulate()
{
    for(size_t i = 0; i < interactionList.size(); i++)
    {
        interactionList[i](0,0); //Would normally be called for every particle
    }
}

class Cell {
public:
    void UpdateParticle(xyz* p, xyz* v);
    Cell()
    {
        AddInteraction(this->UpdateParticle); //Does not work
    }
};

int main()
{
    Cell cell1;
    Cell cell2;

    for(int i = 0; i < 100; i++)
    {
        Simulate();
    }

    return 1;
}
std::向量交互列表;
void AddInteraction(void(*func)(xyz*,xyz*))
{
交互列表。推回(func);
}
void模拟()
{
对于(size_t i=0;iUpdateParticle);//不起作用
}
};
int main()
{
细胞1;
细胞2;
对于(int i=0;i<100;i++)
{
模拟();
}
返回1;
}

您所描述的是不可能的,因为库不知道它必须将
参数传递给您的成员函数。如果
交互
接受为用户保留的参数,则可以执行此操作。如果有一个对象实例在任何给定时间调用
AddInteraction
,则可以存储指向该实例的指针:

Object *Object::only_instance;

void Object::AddInteractionCaller() {
   only_instance = this;
   AddInteraction(set, interaction_fn); 
}

void interaction_fn(xyz* p, xyz* v) {
  only_instance->interaction(p, v);
}

通常,回调函数有一个void*参数,该参数允许客户端代码作为它可能需要的任何其他信息的占位符

这样,客户机代码可以传入他们想要的任何内容,并在调用回调时将其重新转换回原始类型。注意,调用代码知道原始类型

这种类型的接口允许只使用C代码,但如果需要,很容易将其封装在C++对象中。至少,图书馆作者应该提供这一点

编辑以回答OPs评论
下面我已经适当地修改了Cell类

class Cell  
{ 
public:
  static void UpdateParticle(void *stuff, xyz* p, xyz* v)  // this has to be static as others have mentioned, note the additional void * argument
  {
      Cell * c = (Cell *) stuff;
      // do work
  }  

  Cell()
  {
      AddInteraction(this, UpdateParticle); // note this takes a two items, both of which have to be saved for future use by AddInteraction and utilized by Simulate
  }
};  
可以使库函数调用成员函数,而无需修改库。这种技术(称为thunking)非常通用,可以看作是一种即时(JIT)编译。它被一些广泛使用的库(如Windows上的ATL和WTL)使用

基本上,您希望创建如下函数的副本:

void callback(xyz* p, xyz* v) {
    YourClass *ptr = (YourClass*)0xABCDEF00; // this pointer
    ptr->member_callback(p, v);
}
00401000   8B 44 24 08      mov         eax,dword ptr [esp+8] 
00401004   8B 4C 24 04      mov         ecx,dword ptr [esp+4] 
00401008   50               push        eax  
00401009   51               push        ecx  
0040100A   68 AA AA AA AA   push        0AAAAAAAAh 
0040100F   BA BB BB BB BB   mov         edx,0BBBBBBBBh 
00401014   FF D2            call        edx  
00401016   83 C4 0C         add         esp,0Ch 
00401019   C3               ret 
对于
YourClass
的每个实例。然后,您可以像往常一样将其传递到库:

AddInteraction(particleSet, this->a_pointer_to_an_instance_of_that_function);
一旦熟悉了机器代码,这种方法就很简单,但它不可移植(但可以在任何合理的体系结构上实现)

有关详细信息,请参阅

编辑:以下是如何生成thunk的机器代码,而无需实际了解程序集。在发行版中编译此代码:

void f(int *a, int *b)
{
    X *ptr = reinterpret_cast<X*>(0xAAAAAAAA);
    void (*fp)(void*, int*, int*) = reinterpret_cast<void(*)(void*, int*, int*)>(0xBBBBBBBB);
    fp(ptr, a, b);
}

int main()
{
    void (*volatile fp)(int*, int*) = f;
    fp(0, 0);
}
第一列是代码的内存地址,第二列是机器代码(可能需要通过右键单击>显示代码字节在MSVC中启用),第三列是反汇编代码。我们需要的是第二列。您可以从这里复制它或使用任何其他方法(如对象文件列表)

此代码对应于具有默认调用约定的函数,接收两个指针(此处的类型无关紧要),不返回任何内容,并执行对地址为
0xBBBBBBBB
的函数的调用,将
0xaaaaaa
作为第一个参数传递。瞧!这就是我们的恶棍的样子

使用上面的机器代码初始化thunk:

8B 44 24 08 8B 4C 24 04 50 51 68 AA AA AA AA BA BB BB BB BB FF 83 C4 0C C3
并用
this
地址替换
AA
,用指向以下函数的指针替换
BB
。要避免endianess问题,请使用未对齐的访问

void delegate(YourClass *that, xyz p, xyz* v) {
    that->member(p, v);
}
我们将
f
神奇地编码在一个函数指针中!由于最后一个调用很可能是内联的,所以与
void*
方法相比,整个过程只需要多调用一个函数

⚠ 警告⚠您可以在上面的
f
中写入的内容有一些限制。任何将编译成具有相对寻址的机器代码的代码都不会工作。但上述内容仅足以完成任务

注意:可以直接调用成员函数而不使用
委托
函数,如CodeProject文章中所述。但是,由于成员函数指针的复杂性,我宁愿不这样做

注意:此函数生成的代码
00401026 68 AA AA AA AA   push        0AAAAAAAAh 
0040102B BA BB BB BB BB   mov         edx,0BBBBBBBBh 
00401030 FF E2            jmp         edx