What is the reason FreeLibrary for hhctrl.ocx hangs on program shutdown? And how can I avoid this?

896 views Asked by At

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.

2

There are 2 answers

5
help-info.de On

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;
0
Ekaterina Zaytseva On
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.