假设我有以下代码在多线程COM应用程序的一个线程中运行:
// Thread 1
struct foo {
int (*DoSomething)(void ** parm);
};
HMODULE fooHandle = LoadLibrary("pathToFoo");
typedef int (*loadUpFooFP)(foo ** fooPtrPtr);
loadUpFooFP loadUpFoo;
loadUpFoo = (loadUpFooFP)GetProcAddress(fooHandle, "fooLoader");
foo * myFoo;
loadUpFoo(&myFoo);
这一切都很好,我可以打电话
myFoo->DoSomething(&parmPtr);
这也有效!现在,另一个线程出现并加载其foo:
// Thread 2
foo * myFooInThread2;
loadUpFoo(&myFooInThread2);
这也很有效。在主题2中,我可以调用DoSomething:
// Thread 2
myFooInThread2->DoSomething(&anotherParmPtr);
现在,当线程1最终消失时,我遇到了问题。我注意到在Visual Studio中调试无法再评估DoSomething的地址。第一个线程死后,当我打电话:
myFooInThread2->DoSomething(&anotherParmPtr);
我遇到了访问冲突。 myFooInThread2指针仍然有效,但函数指针不是。这个函数指针是通过调用loadUpFoo来设置的,而loadUpFoo又是由LoadLibrary加载的一个dll。
我的问题是:我从哪里开始寻找失败的原因?外部DLL(我用LoadLibrary加载)在我的foo结构中设置函数指针的方式有问题吗?或者它是否与使用相同库的不同线程有关?或者,它可能以某种方式与我在此应用程序中使用COM相关(在第一个线程中调用CoUninitialize以某种方式释放此内存或库)?
如果看起来它可能是负责任的,我可以提供有关COM设置的更多详细信息。谢谢!
编辑:感谢您提供的建议。 foo结构是不透明的 - 我对它的实现并不是很了解。 foo结构在我导入的头文件中声明。没有我明确调用的引用计数方法,并且没有与LoadLibrary加载的库的其他交互。我很确定foo结构不是内存映射到某个COM类,但就像我说它不透明而且我不能肯定地说。
foo指针的生命周期得到妥善管理(未删除)。
foo结构是一个加密库,所以我不能再自由泄露了。在这一点上,我确信在不同的线程和COM应用程序中使用LoadLibrary没有任何本质上的错误(我认为函数指针内存清理是由我自己控制之外的库本身引起的) )。
答案 0 :(得分:3)
您显示的代码中没有与COM相关的内容。 LoadLibrary不是特定于线程的,因此一旦掌握了lib的句柄,就可以从所有线程中重用它。同样适用于指向fooLoader方法的指针。
然而,在fooLoader中肯定会有特定于COM的东西。此外,这里还不清楚foo实例的生命周期控制是什么。
从你提到COM和你看到的时髦行为的事实来看,我有一种偷偷摸摸的怀疑foo实际上是COM对象的vtable的内存映射,而fooLoader是DllGetClassObject或另一个创建COM对象的工厂方法..: - )
在任何情况下,foo实例变得无效的最可能的解释是它被重新计数并且DoSomething调用AddRef()/ Release()导致它自毁。
为了确切了解发生了什么,你必须提供更多关于fooLoader做什么的信息以及你认为你的代码与COM有关的原因。
答案 1 :(得分:2)
任何机会,线程1在关闭时调用FreeLibrary
(或ExitThreadAndFreeLibrary
)吗?如果是这样,您正在尝试调用不再映射到进程中的代码。您的对象看起来仍然很好,因为实例数据仍然存在,但其方法的代码将消失。
如果这是问题所在,您可以将线程1更改为不释放库。
或者,您可以让第二个帖子也调用LoadLibrary
。 LoadLibrary
和FreeLibrary
使用引用计数,因此如果您加载DLL 3次,则在释放3次之前不会卸载。引用计数是每个进程,因此您可以从不同的线程加载相同的库。额外的负载成本非常低。
答案 2 :(得分:0)
DoSomething
的值由您加载的库确定。你应该能够确定它指向的位置。查看Visual Studio中的调试输出。它不仅会告诉您何时,还会告诉您DLL的加载位置。你的功能真的指向你认为它应该指向的DLL吗?