0

I have got a program written in Delphi 2007 that uses html help. Very often it hangs on exit (even though html help wasn't actually called) and I traced the problem down to this call in the finalization section of Windows.pas

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

The main thread hangs in this call because of a NTWaitFormMultipleObjects deep inside the unload code of the hhctrl.ocx. There are other threads (none of which my code creates) that apparently wait for the same, so my program hangs. I guess some of these threads are created by ADO and/or the Microsoft SQL Server client libraries.

I found one workaround: An additioal call to LoadLibrary('hhctrl.ocx'), so the call to FreeLibrary in Windows.pas does not actually unload the dll but only decrements the reference count to 1. While this seems to work, it does not not feel right.

Is this a known problem? Is there a proper solution?

(Yes, I googled, but found nothing that helped. This seems to describe a similar problem https://social.msdn.microsoft.com/Forums/en-US/7bce34a2-50a0-411d-872f-0626360d5415/dll-sometimes-hangs-on-unload?forum=vcgeneral with a different DLL.)

EDIT: Some more info:

The problem apparently only occurs when the html help is never called within the program (so LoadLibrary('hhctl.ocx') wasn't called). On shutdown, the finalization code in htmlhelp.pas tries to close all htmlhelp viewer windows (of which there are none) and issues the first call ever to the HtmlHelp function. This leads to a call to LoadLibrary in windows.pas. If I show any htmlhelp in the program, everything works fine. So, I think this might be a problem with calling LoadLibrary('hhctl.ocx') within the finalization of the RTL. But I have no idea how I can avoid this.

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
  • The additional call to `LoadLibrary('hhctrl.ocx')` might just cause a memory leak (actually depend on the way you call it), and that is probably a very ugly workaround to ignore the error. Please, post code you use to initialize the library. – quasoft Jan 29 '16 at 15:41
  • Can you show a [mcve]? Lots of people use D2007 without such problems. What's different about your program? – David Heffernan Jan 29 '16 at 15:42
  • @DavidHeffernan no, unfortunately I can't. It's a very complex program (the most complex we currently maintain) and I was unable reproduce the problem with anything simple. I have never before encountered this either and I have used Delphi 2007 a lot myself. I was hoping somebody else might have encountered the same problem with a simpler program and was able to find a solution. – dummzeuch Jan 29 '16 at 15:49
  • @quasoft the code is the standard code of the Delphi 2007 RTL in Windows.pas, line 31980 and following. My program doesn't do anything special. But you might actually have helped me with this question, because I just found that the library only gets loaded from the finalization code of HtmlHelpViewer.pas I'll investigate further. – dummzeuch Jan 29 '16 at 15:53
  • Why don't you debug the problem? – David Heffernan Jan 29 '16 at 15:56
  • If in doubt with memory management, try using FastMM. Here is a starter: http://delphibistro.com/?p=186 – quasoft Jan 29 '16 at 15:58
  • @DavidHeffernan guess what I am currently trying to do? – dummzeuch Jan 29 '16 at 16:02
  • @quasoft FastMm does not report any memory leak at all. That's not the issue. – dummzeuch Jan 29 '16 at 16:09
  • The `HtmlHelpViewer` unit has been a never ending source of problems. I simply don't use it and implement an application wide `OnHelp` myself. Naturally that calls `HtmlHelp`, but I make sure that the code I use works. In your case you could throw in a "do nothing" call to `Windows.HtmlHelp` and force the library to load that way. – David Heffernan Jan 29 '16 at 16:30
  • @DavidHeffernan sounds like a reasonable workaround. Better than the additional LoadLibrary. I'll try that on Monday. Thanks. – dummzeuch Jan 29 '16 at 18:58

2 Answers2

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

This is the main source of the problem. No one should use LoadLibrary/FreeLibrary in initialization or finalization sections (either explicitly or implicitly).

In short, this is because initialization and finalization code runs under special conditions, inside of DllMain function, where loader lock is active. Under these conditions, one should NOT call two above functions (actually, even GetModuleHandle and GetProcAddress may fail!), NOT use locks and NOT start up or terminate threads. You can learn abour loader locks from this StackOverflow answer. I also recommend Chris Brumme's post Startup, Shutdown and related matters for comprehensive research.

So, Embarcadero is responsible for this bug, what can we do? The simplest workaround is (as you've already discovered) to always call HtmlHelp from within your program, or simply LoadLibrary('hhctrl.ocx'), remembering NOT to put this call in initialization part of any unit.

-1

Normally when a host application closes, Windows closes all Help windows opened by this application automatically. There is a problem ... This can cause access violations.

I'm not a Delphi programmer - more busy in help authoring (CHM's) and VB. You can try using the HH_INITIALIZE, HH_UNINITIALIZE commands. These are documented in the HH Workshop online help. But - please check your code for HH_CLOSE or HH_CLOSE_ALL too.

Call HH_CLOSE_ALL earlier. Get more space between the HH_CLOSE_ALL and your call to UnloadLibrary. In VB and Delphi you would perform the call on the Form QueryUnload not on the Form Close or Destroy.

A work around is to close the HH windows by hand or earlier in the CloseQuery() Event and use sleep(0) to give a few cycles for HTMLHelp to settle down.

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

or

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

sample:

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;

Don't blindly call HH_CLOSE_ALL on shutdown. If a user does not have HTML Help installed then this call will crash your application. Here is safer code. Notice we are checking if HH is installed before calling HtmlHelp();

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;
help-info.de
  • 6,695
  • 16
  • 39
  • 41
  • Thanks for the reply, but that's not the problem I'm facing. The Delphi RTL already calls HtmlHelp(0, nil, HH_CLOSE_ALL, 0) in the finalization. Unfortuately that actually seems to cause the problem. See my edit to the question why that is so. – dummzeuch Jan 31 '16 at 14:40
  • @dummzeuch You wrote "The problem apparently only occurs when the html help is never called within the program ..." As I mentioned above in one of the samples code "if IsWindow(_HHwinHwnd) then ..." . Can you check in your code if HTMLHelp window exists and then giving it a HH_CLOSE_ALL only when this is true? – help-info.de Jan 31 '16 at 15:58
  • @dummzeuch Does the problem occur in compiled code or while running the IDE? – help-info.de Jan 31 '16 at 16:00
  • I cannot change the RTL code. (Or rather I would rather not.). This code does not check for the HTMLHelp window. The problem occurs in compiled code. – dummzeuch Jan 31 '16 at 16:34
  • OK, don't touch a running system - but I thought testing a Sleep(0); is easy. Following http://bbs.cnpack.org/archiver/?tid-1900.html has some further Information. – help-info.de Jan 31 '16 at 16:57