【问题标题】:Kill all the threads caused by hanging DLL Delphi杀死所有挂起DLL Delphi引起的线程
【发布时间】:2020-06-12 10:28:20
【问题描述】:

我有一个调用 DLL 的 Delphi 代码。 DLL 是动态链接的。

在我的代码的开头,我通过

启动/打开 DLL
DllHandle:= LoadLibrary(DllFileName);
  • 如果 DLL 文件不存在,我会得到一个DLLHandle=0,这很好。我可以在主程序中继续我的下一行。
  • 如果 DLL 文件存在并正确初始化,我会得到一个non-zero DLLHandle,这也很好。

但是,DLL 内部时不时会出现问题,这意味着文件存在但 DLL 没有响应。 所以我的主要应用程序挂起。

为了避免这种情况,我研究了多线程。

我的主程序有一个新类型 TMyThread 继承自 TThread 并覆盖 Create, Destroy, Execute

我的主程序即时创建一个新线程 (ChildThread)。 ChildThread 的Execute 方法正在调用上面提到的LoadLibrary。

通过这样做,我可以继续我的主程序并稍等片刻,看看ChildThread 是否返回一个非零的 DLL 句柄。 如果 ChildThread 中的 DLLhandle 在几秒钟后仍然为零,我想终止线程并释放 DLL 并重试。

进一步调查我可以看到 Delphi IDE 中的线程列表。最初我的主程序下有 4 个线程。当我创建 ChildTread 时,会出现一个新的。几秒钟后(当调用 Execute 并在 ChildTread 中调用 loadlibrary 时)又出现了两个线程。我猜主 DLL 正在调用其他线程。

在这个阶段,我无法访问 ChildTread,因此我无法在 Execute 方法中使用 while 循环来查看它是否已终止并退出线程。因为该线程中的实际代码已经挂在执行中。

问题: ChildTread 挂起,但我的主程序仍在继续。我想从我的主程序中杀死 ChildThread(可能是它启动的所有其他线程)并重试 DllHandle:= LoadLibrary(DllFileName);

【问题讨论】:

  • 这是错误的解决方案。您不能在单个进程中完成这项工作。您需要将其移至单独的进程中。您需要通过流程获得的隔离。
  • 有没有办法在 Delphi 中做到这一点?
  • 是的,但这不是微不足道的。一种选择是进程外 COM 服务器。这会照顾所有的 IPC。如果是我,我会修复 DLL(或者可能是您使用它的方式)。
  • 我不明白为什么杀死我创建的线程(或简单的函数调用)如此难以杀死。所以在 Delphi 中我知道哪些线程 ID 应该被杀死但不能被杀死?。
  • "我不明白为什么杀死我创建的线程(或简单的函数调用)是如此难以杀死" --- 因为通常你不会简单地"杀死”线程。

标签: multithreading delphi dll loadlibrary


【解决方案1】:

我的主程序动态创建一个新线程 (ChildThread)。 ChildThread 的 Execute 方法正在调用上面提到的 LoadLibrary。

var thread := TMyThread.Create(...);

在这个阶段,我无法访问 ChildTread,因此我无法在 Execute 方法中使用 while 循环来查看它是否已终止并退出线程。因为该线程中的实际代码已经挂在执行中。

if (thread <> nil) 
and DetectWhetherThreadIsHanging(thread)    // <- this is your code
then begin
  TerminateThread(thread.Handle,1);
  thread.Free;
end;

未测试。甚至没有编译。但朝那个方向发展。

推荐阅读MSDN TerminateThread() function

TerminateThread 是一个危险的函数,只能在最极端的情况下使用。仅当您确切知道目标线程在做什么时才应该调用 TerminateThread,并且您可以控制目标线程在终止时可能正在运行的所有代码。例如,TerminateThread 可能会导致以下问题 [...]

最后,再说一遍:你不能简单地杀死线程。如果 DLL 在加载时卡住,则可能是有原因或 DLL 中存在错误。在 99% 的情况下,当人们出于算法原因感到需要“杀死”一个线程或进程时,这是错误的方法。

编辑和可能的解决方法

但是 LoadLibrary(DLL) 调用了其他线程。他们没有被杀死。我认为因此 DLL 没有被释放,所以我不能再次调用 loadlibrary(dll)

我能想象到的唯一真正的解决方案是这样的,但它对你来说可能不是一个可行的解决方案。这主要取决于DLL的作用,但由于您不告诉我们我们不知道。

  1. 将 DLL 包装到单独的 (!) EXE 中
  2. 从实际程序启动该 EXE,并在第二个 EXE 失败时自行终止
  3. 重复该过程,直到最终成功
  4. 在第二个进程中对那个 DLL 做任何你必须做的事情,并在两个进程之间使用某种 IPC。

【讨论】:

  • 1- dll 是外部的并且由于有时无法获得许可证而失败。我们无法更改此 2-您的建议会杀死调用 DLL 的第一个线程,但 LoadLibrary(DLL) 还会调用其他线程。他们没有被杀死。我认为因此 DLL 没有被释放,所以我不能再次调用 loadlibrary(dll)
  • @user2375049 1- 您需要联系 DLL 作者来解决该问题。 DLL 在加载过程中不应尝试联系或以其他方式验证许可证。这应该在 DLL 首先完全加载后完成。 DLL 的加载逻辑应该保持在最低限度,DLL 的入口点可以合法/安全地做的事情只有这么多。
  • @user2375049 2- 无法识别哪个模块创建了哪个线程。 JensG 建议您杀死正在加载 DLL 的线程。如果 DLL 本身创建了额外的线程,则没有可靠的方法将这些线程与 DLL 匹配并杀死它们。我的意思是,您可以在加载 DLL 之前枚举线程,然后再次枚举,然后杀死您检测到的任何新线程,但不能保证这些不仅仅是为您自己的进程创建的额外系统线程。跨度>
  • "您的建议会杀死调用 DLL 的第一个线程" - 我知道,但我遵循了问题描述中的陈述(并引用了它们)。我从未声称它会杀死所有线程。我什至不鼓励你考虑这个代码,但我仍然把它给你。因为你可能会学到一些东西。
  • @DavidHeffernan:同意。从来都不是这个想法。我只回答了这个问题。
猜你喜欢
  • 2012-01-31
  • 1970-01-01
  • 2016-10-09
  • 1970-01-01
  • 1970-01-01
  • 2012-03-12
  • 2014-05-18
  • 1970-01-01
  • 2011-11-12
相关资源
最近更新 更多