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