0 c++ function callback member
我正在使用粒子模拟库。通过以下库函数将交互添加到粒子的方式:
AddInteraction(ParticleSet particleSet, void(*interaction)(xyz* p, xyz* v))
Run Code Online (Sandbox Code Playgroud)
我现在想将成员函数传递给AddInteraction。我知道,不更改库函数是不可能做到的。我想避免更改库,但是如果更改很小,我可以邮寄该库的作者并要求将其实施。
我想写一个简单的例子来说明如何做到这一点。可能有不同的解决方案:
Lambda表达式。这些将是完美的,但是该库使用CUDA和NVCC,它们尚不支持Lamda表达式。
函子。我认为函子可以胜任。但是,我还没有设法让它们按照我想要的方式工作。例如,我找不到工作的函子列表。http://www.tutok.sk/fastgl/callback.html中的 “ 参数化调用方”下对此问题进行了说明:
如果一个组件具有许多回调关系,则很快就无法对其全部参数化。考虑一个Button,它希望维护一个动态的被叫者列表,以便在单击事件时得到通知。由于被调用者类型内置在Button类类型中,因此此列表必须是同构的或无类型的。
我不得不承认我不理解该网站上写的所有内容,因此那里可能会有答案。
还有其他选择吗?还是有办法使上述解决方案之一起作用?
非常感谢!
编辑:
感谢您的建议。他们有帮助,但我看不出他们如何为我的问题提供完整的解决方案。具体来说,我正在尝试使其工作:
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;
}
Run Code Online (Sandbox Code Playgroud)
这是可能使该库函数回调成员函数不修改库。该技术非常通用,并且基于自我修改代码。在极少数情况下,需要对自修改代码进行辩护,并且实际上,已被一些广泛使用的库(例如Windows上的ATL和WTL)应用了。
用两个词来说:您想要创建一个函数副本,如下所示:
void callback(xyz* p, xyz* v) {
YourClass *ptr = (YourClass*)0xABCDEF00; // this pointer
ptr->member_callback(p, v);
}
Run Code Online (Sandbox Code Playgroud)
对于的每个实例YourClass。然后,您可以像往常一样将其传递给库:
AddInteraction(particleSet, this->a_pointer_to_an_instance_of_that_function);
Run Code Online (Sandbox Code Playgroud)
一旦您熟悉了机器代码,这种方法就很简单,但是它不是可移植的(但可以在任何合理的体系结构上实现)。
编辑:这是一种无需实际知道汇编就可以为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);
}
Run Code Online (Sandbox Code Playgroud)
放置一个断点f并运行。查看调试器中的汇编代码。在我的机器上,它看起来像这样:
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
Run Code Online (Sandbox Code Playgroud)
第一列是代码的内存地址,第二列是机器代码(您可能需要通过右键单击>显示代码字节在MSVC中启用它),第三列是反汇编的代码。我们需要的是第二列。您可以从此处复制它,也可以使用任何其他方法(例如目标文件列表)。
此代码对应于具有默认调用约定的函数,接收两个指针(此处的类型无关紧要),不返回任何内容,并在作为第一个参数的地址0xBBBBBBBB传递时执行对函数的调用0xAAAAAAAA。瞧!这就是我们的重击应该是什么样子!
使用上面的机器代码初始化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
Run Code Online (Sandbox Code Playgroud)
并替换AA AA AA AA为this地址和BB BB BB BB指向以下函数的指针。为避免出现周期性问题,请使用未对齐的访问权限。
void delegate(YourClass *that, xyz p, xyz* v) {
that->member(p, v);
}
Run Code Online (Sandbox Code Playgroud)
我们神奇地编码that并f在单个函数指针内!而且由于最后一个调用很可能是内联的,因此与该void*方法相比,整个过程仅花费了一个函数调用。
?警告 ?您在f上面可以写的内容有一些限制。任何将被编译为具有相对寻址的机器代码的代码将不起作用。但是以上内容足以完成任务。
注意:可以直接调用成员函数而不使用该delegate函数,如CodeProject文章中所述。但是,由于成员函数指针的复杂性,我宁愿不这样做。
注意:此方法生成的代码不是最佳的。与上述等效的最佳代码是:
00401026 68 AA AA AA AA push 0AAAAAAAAh
0040102B BA BB BB BB BB mov edx,0BBBBBBBBh
00401030 FF E2 jmp edx
Run Code Online (Sandbox Code Playgroud)