0

我有一个使用 html 帮助用 Delphi 2007 编写的程序。它经常在退出时挂起(即使实际上并未调用 html 帮助),我将问题追溯到 Windows.pas 的最终部分中的此调用

finalization
  if HtmlHelpModule <> 0 then FreeLibrary(HtmlHelpModule);
end.

由于在 hhctrl.ocx 的卸载代码深处存在 NTWaitFormMultipleObjects,主线程在此调用中挂起。还有其他线程(我的代码都没有创建)显然等待相同,所以我的程序挂起。我猜其中一些线程是由 ADO 和/或 Microsoft SQL Server 客户端库创建的。

我找到了一种解决方法:对 LoadLibrary('hhctrl.ocx') 的附加调用,因此在 Windows.pas 中对 FreeLibrary 的调用实际上并没有卸载 dll,而只是将引用计数减少到 1。虽然这似乎有效,但确实如此不是感觉不对。

这是一个已知问题吗?有合适的解决方案吗?

(是的,我用谷歌搜索,但没有发现任何帮助。这似乎描述了一个类似的问题 https://social.msdn.microsoft.com/Forums/en-US/7bce34a2-50a0-411d-872f-0626360d5415/dll-sometimes -hangs-on-unload?forum=vcgeneral 使用不同的 DLL。)

编辑:更多信息:

该问题显然仅在程序中从未调用过 html 帮助时才会出现(因此未调用 LoadLibrary('hhctl.ocx'))。关闭时,htmlhelp.pas 中的最终代码会尝试关闭所有 htmlhelp 查看器窗口(其中没有),并首次调用 HtmlHelp 函数。这会导致调用 windows.pas 中的 LoadLibrary。如果我在程序中显示任何 htmlhelp,一切正常。所以,我认为这可能是在 RTL 的最终确定中调用 LoadLibrary('hhctl.ocx') 的问题。但我不知道如何避免这种情况。

4

2 回答 2

0
finalization
  if HtmlHelpModule <> 0 then FreeLibrary(HtmlHelpModule);
end.

这是问题的主要根源。任何人都不应在初始化或终结部分(显式或隐式)使用 LoadLibrary/FreeLibrary。

简而言之,这是因为初始化和终结代码在特殊条件下运行,在 DllMain 函数内部,加载器锁处于活动状态。在这些情况下,不应该调用上述两个函数(实际上,即使 GetModuleHandle 和 GetProcAddress 也可能失败!),不使用锁,不启动或终止线程。您可以从此StackOverflow 答案中了解有关加载程序锁的信息。我还推荐 Chris Brumme 的帖子Startup, Shutdown and related things进行综合研究。

那么,Embarcadero 对这个 bug 负责,我们能做些什么呢?最简单的解决方法是(正如您已经发现的那样)始终从您的程序中调用 HtmlHelp,或者只是 LoadLibrary('hhctrl.ocx'),记住不要将此调用放在任何单元的初始化部分。

于 2017-08-08T13:36:20.707 回答
-1

通常,当主机应用程序关闭时,Windows 会自动关闭该应用程序打开的所有帮助窗口。有一个问题...这可能会导致访问冲突。

我不是 Delphi 程序员 - 更忙于帮助创作 (CHM) 和 VB。您可以尝试使用 HH_INITIALIZE、HH_UNINITIALIZE 命令。这些都记录在 HH Workshop 在线帮助中。但是 - 请检查您的代码是否为 HH_CLOSE 或 HH_CLOSE_ALL。

提前调用 HH_CLOSE_ALL。在 HH_CLOSE_ALL 和您对 UnloadLibrary 的调用之间获得更多空间。在 VB 和 Delphi 中,您将在 Form QueryUnload上而不是在 Form Close 或 Destroy 上执行调用。

一种解决方法是在 CloseQuery() 事件中手动或更早地关闭 HH 窗口,并使用 sleep(0) 给 HTMLHelp 几个周期以稳定下来。

//Will close all Help windows opened by the application - no handle required
HtmlHelp(0, nil, HH_CLOSE_ALL, 0);

或者

//This runs a little faster
if IsWindow(_HHwinHwnd) then
SendMessage( _HHwinHwnd, wm_close, 0, 0 );

样本:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   //if IsWindow(_HHwinHwnd) then
   //  SendMessage( _HHwinHwnd, wm_close, 0, 0 );
   HtmlHelpA(0, nil, HH_CLOSE_ALL, 0);
   Sleep(0);
end;

不要在关机时盲目调用 HH_CLOSE_ALL。如果用户没有安装 HTML 帮助,那么此调用将使您的应用程序崩溃。这是更安全的代码。请注意,我们在调用 HtmlHelp() 之前检查是否安装了 HH;

procedure HHCloseAll;
begin
  If @HH.HtmlHelp <> Nil then  //HH API is available
    begin
    HH.HtmlHelp(0, nil, HH_CLOSE_ALL, 0);
    Sleep(0); 
  end;
end;
于 2016-01-30T13:20:57.660 回答