我正在使用粒子模拟库。通过以下库函数将交互添加到粒子的方式:
AddInteraction(ParticleSet particleSet, void(*interaction)(xyz* p, xyz* v))
我现在想将成员函数传递给AddInteraction。我明白如果不改变库函数就不可能做到这一点。更改 图书馆是我想要避免的,但如果变化很小,我可以邮寄图书馆的作者并要求它实施。
我想写一个如何完成它的简单例子。可能有不同的解决方案:
Lambda表达式。这些都是完美的,但是图书馆使用的是CUDA和NVCC,它们还不支持Lamda表达。
函子。我认为仿函数可以完成这项工作。但是,我还没有设法以他想要的方式让他们工作。例如, 我无法获得有效的仿函数列表。 参数化调用者下的http://www.tutok.sk/fastgl/callback.html中描述了此问题:
'如果某个组件有很多回调关系,那么将它们全部参数化很快就变得不可行了。考虑一个想要维护的按钮 单击事件时要通知的动态列表。由于被调用者类型内置于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;
}
答案 0 :(得分:4)
可以使库函数回调成为成员函数,而无需修改库。该技术非常通用,基于自修改代码。这是一种罕见的情况,其中自修改代码是合理的,事实上,由一些广泛使用的库(如Windows上的ATL和WTL)应用。
用两个词来说:你想创建一个像这样的函数的副本:
void callback(xyz* p, xyz* v) {
YourClass *ptr = (YourClass*)0xABCDEF00; // this pointer
ptr->member_callback(p, v);
}
为YourClass
的每个实例。然后您可以将其传递给库:
AddInteraction(particleSet, this->a_pointer_to_an_instance_of_that_function);
一旦熟悉机器代码,这种方法很简单,但它不可移植(但可以在任何合理的架构上实现)。
有关详细信息,请参阅description and implementation for Windows。
编辑:以下是如何在不知道汇编的情况下为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);
}
在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
第一列是代码的内存地址,第二列是机器代码(您可能需要通过右键单击&gt;显示代码字节在MSVC中启用它),第三列是反汇编代码。我们需要的是第二栏。您可以从这里复制它或使用任何其他方法(如目标文件列表)。
此代码对应于具有默认调用约定的函数,接收两个指针(此处类型无关紧要),不返回任何内容,并对通过0xBBBBBBBB
的地址0xAAAAAAAA
的函数执行调用}作为第一个参数。瞧!这正是我们的thunk看起来的样子!
使用上面的机器代码初始化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
将AA AA AA AA
替换为this
地址,将BB BB BB BB
替换为指向以下函数的指针。为避免出现字句问题,请使用未对齐的访问权限。
void delegate(YourClass *that, xyz p, xyz* v) {
that->member(p, v);
}
我们在单个函数指针中对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
答案 1 :(得分:1)
您所描述的内容是不可能的,因为库不知道它必须将this
参数传递给您的成员函数。如果interaction
接受为用户保留的参数,则可以执行此操作。如果在任何给定时间有一个对象实例调用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);
}
答案 2 :(得分:0)
通常,回调函数有一个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
}
};