1

DLL registration with regsvr32.exe freezes when unit HtmlHelpViewer is used in DLL sources in Delphi XE or Delphi XE2 Update 3. Just add the unit to interface uses list. The main project (that uses DLL) freezes on exit too.

How to fix the issue?

Thanks for the help!

STEPS TO REPRODUCE THE ISSUE AND ISSUE IN SUGGESTED FIX:

1). Please create the following DLL:

library Test;

uses
  ComServ,
  HtmlHelpFixer,
  HtmlHelpViewer;

exports
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;

begin
end.

2). Also create the following BPL linked to this DLL (by -LUTestBpl dcc32 parameter for example):

package TestBpl;

requires
  Vcl;

end.

3). Then just execute: regsvr32.exe /s Test.dll. OS Windows 7 32-bit.

Dmitry
  • 14,306
  • 23
  • 105
  • 189

2 Answers2

9

Update

According to the latest comments on the QC report submitted by Altaveron, this problem will be resolved in the next Delphi update, update 4. And indeed, Altaveron now confirms that update 4 does resolve the issue.


This is a known problem with the MS HTML help control, hhctrl.ocx. The best description of it that I am aware of is at the HelpWare FAR HTML FAQ. There are many QC reports describing the issue: 48983, 67463, 78998, 89616.

According to the latest QC report, this is fixed in XE2 but you report otherwise and I'd be inclined to believe you. Especially as a comparison of the source for the HtmlHelpViewer unit from XE and XE2 reveals no changes that appear related to this issue.

It's quite hard to work around the issue since the code that needs to be modified is buried deep inside the HtmlHelpViewer unit. I've had to resort to patching the HtmlHelp API call. Like this:

unit HtmlHelpFixer;

interface

implementation

uses
  Windows;

function HtmlHelp(hWndCaller: HWND; pszFile: PWideChar; uCommand: UINT; dwData: DWORD): HWND;
begin
  if uCommand=HH_CLOSE_ALL then begin
    //don't call HtmlHelpW because it can result in a hang due to a bug in hhctrl.ocx
    Result := 0;
  end else begin
    Result := HtmlHelpW(hWndCaller, pszFile, uCommand, dwData);
  end;
end;

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

procedure RedirectHtmlHelp;
var
  HtmlHelp: function(hWndCaller: HWND; pszFile: PWideChar; uCommand: UINT; dwData: DWORD_PTR): HWND;
begin
  HtmlHelp := Windows.HtmlHelp;
  RedirectProcedure(@HtmlHelp, @HtmlHelpFixer.HtmlHelp);
end;

initialization
  RedirectHtmlHelp;

end.

Include this unit early in your .dpr uses list, before any unit that does anything with HTML help.

The version of the code that I use does a little more and takes steps to ensure that any open help windows are closed when the DLL unloads. This no longer happens because we have stopped sending HH_CLOSE_ALL.

You will want to make sure that any help windows are shut down then keep track of the window handles returned by HtmlHelp calls, which you can now intercept. Then at shutdown send a WM_CLOSE message to those windows which replaces the missing HH_CLOSE_ALL call to HtmlHelp.

However, I believe that the code above should get you over your immediate hurdle with regsvr32 which won't be showing help windows.

Feel free to do some experimentation! At the very least, the code above gives you entry points with which you can modify the behaviour of the HtmlHelpViewer unit.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    That's some great hacking there. (I mean that in the elegant sense, not in the deprecatory sense.) – Warren P Dec 24 '11 at 14:25
  • Code `function HtmlHelp(hWndCaller: HWND; pszFile: PWideChar; uCommand: UINT; dwData: DWORD): HWND; begin Result := 0 end;` doesn't help too. When I just delete HtmlHelpViewer unit from uses list - all is ok. (I have added HtmlHelpFixer before every HtmlHelpViewer unit.) – Dmitry Dec 26 '11 at 13:47
  • I think, it's required to patch more functions. But what functions? – Dmitry Dec 26 '11 at 13:48
  • Did you add it to the uses clause of the .dpr file? – David Heffernan Dec 26 '11 at 14:06
  • David, thanks for your help. I have added it everywhere, BPL dpk, DLL dpr, EXE dpr. No result:( – Dmitry Dec 26 '11 at 14:37
  • May you suggest another function's names to patch? – Dmitry Dec 26 '11 at 14:57
  • Well, it doesn't sound as though this is the answer to your problem. Patching functions at random won't help. In this case I wanted to stop the HH_CLOSE_ALL command. Can you reproduce the error with a trivial "do nothing" DLL? – David Heffernan Dec 26 '11 at 15:11
  • 1
    I can reproduce the behaviour and adding my unit fixes the problem. I created the library exactly as in your updated question. I ran under the debugger and see that the `HH_CLOSE_ALL` command results in a hang. Then I added my patching unit and see that the same call is redirected to my code which swallows the `HH_CLOSE_ALL` command. No hang. So, it all appears, on my system, exactly as I said in my answer. – David Heffernan Dec 27 '11 at 11:08
  • Can you try running under the debugger with Debug DCUs and stepping through the finalization of HtmlHelpViewer. The key routine is `THtmlHelpViewer.SoftShutDown`. Set a breakpoint in there and step through that with F7. You should end up in my code and see the test for `HH_CLOSE_ALL`. You'll need to modify the Run Parameters for the library project. Set Host Application to `C:\Windows\System32\regsvr32.exe` and Parameters to `/s test.dll` – David Heffernan Dec 27 '11 at 11:10
  • Steps to reproduce should be more detailed. I am trying to describe them... I use a link of run-time BPL with HtmlHelpViewer unit and DLL using this BPL. – Dmitry Dec 27 '11 at 14:13
  • Where do packages come into it. The hang can be reproduced with just the DLL source in your question. And my unit fixes that. Doesn't it? – David Heffernan Dec 27 '11 at 14:19
  • I have added `requires Vcl;` in empty (!) BPL to reproduce the issue. DLL should be just linked with BPL (for example by -LUTestBpl dcc32 parameter). – Dmitry Dec 27 '11 at 15:04
  • Only DLL works fine with your fix. Then just link an empty BPL (requires Vcl) and the issue will come again:( I have added BPL source to the main question. – Dmitry Dec 27 '11 at 15:09
  • I may also send complete sources described in the main question in a zip-file with a bat-file to run test. – Dmitry Dec 27 '11 at 15:13
  • Attach sources to your QC report and then the Emba people can make use of them too. – David Heffernan Dec 27 '11 at 15:17
  • Sorry, but there is no any link as 'Attach file' :( I think I have no rights. – Dmitry Dec 27 '11 at 15:20
  • Another way to reproduce the issue: just add dcc32 parameter `-LUVcl` to DLL. No BPL is required in this way. – Dmitry Dec 27 '11 at 15:23
  • You need to use the native QC client. Download it from the Emba site. I can reproduce the package issue. – David Heffernan Dec 27 '11 at 15:25
  • I think you may well have a different problem regarding packages. I can get the hang behaviour without any mention of HtmlHelpViewer simply by calling `HtmlHelp` from a DLL which is linked with runtime packages. I suggest that you submit the package problem as a separate QC report. I'm not sure I can help you with that. I believe I answered the question you originally asked. – David Heffernan Dec 27 '11 at 15:34
  • Actually, the package problem is most likely the same problem. I'll think about it a bit more and get back to you. – David Heffernan Dec 27 '11 at 16:40
  • May be we need to binary patch vcl160.bpl? – Dmitry Dec 27 '11 at 18:36
  • No we can do it fine at runtime. Be patient. – David Heffernan Dec 27 '11 at 18:40
  • I've come to the conclusion that this is non-trivial to achieve. The problem is that you need to modify the Windows unit packaged inside vcl160.bpl and that's hard to reach. At least I don't know how to do it. I think the best solution will involve something like MS Detours. – David Heffernan Dec 27 '11 at 19:41
  • No, I'm out of ideas. In any case, I do believe that I answered your original question. – David Heffernan Dec 27 '11 at 20:02
  • Does this problem only afflict you from regsvr32? – David Heffernan Dec 27 '11 at 20:24
  • The issue exists on EXE closure too. We must use run-time BPL because we will have 20 binary files about 50Mb else.. – Dmitry Dec 27 '11 at 20:53
  • Does there need to be intermediate DLL also? – David Heffernan Dec 27 '11 at 22:09
  • It seems very much to me that there needs to be a DLL involved. Straight exe to package doesn't seem to cause problems. Also, the DLL has to use HtmlHelpViewer for the issue to manifest. If the DLL doesn't, even if it uses vcl runtime package, it's all fine. – David Heffernan Dec 27 '11 at 22:19
  • A lot of DLLs are required. And them use the same BPL as EXE with help support... – Dmitry Dec 27 '11 at 22:46
  • I'm just going to add that I had this "hang" problem in a suite of apps that have no explicit DLL use. But the code fixes the problem, so I'm grateful. – mj2008 Jan 25 '12 at 09:40
  • @mj2008 I had other problems with HtmlHelpViewer recently. It's not very good in my experience. I've now given up on it. I know handle TApplication.OnHelp and call HtmlHelp directly. All works perfectly. – David Heffernan Jan 25 '12 at 09:42
0

Embarcadero have fixed this issue on Delphi XE2 Update 4. But now context help doesn't work on IDE while you're using BPL with HtmlHelpViewer unit on uses clause.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Dmitry
  • 14,306
  • 23
  • 105
  • 189
  • This is just for a quick help for users with the same problem. They should update Deplhi XE2 to solve it. David, thanks a lot for your help and investigation. Have you any solvation how to save help working on IDE when BPL should use HtmlHelpViewer unit? – Dmitry Jun 25 '12 at 20:29
  • OK, thanks for putting the accept back. I updated my answer to contain this information. HtmlHelpViewer in a BPL? The solution is not to include that unit in your BPL. If it is in shared code then simply remove it from the BPL using conditional compilation. – David Heffernan Jun 25 '12 at 20:32
  • David, thank you again. But BPL is design-time and run-time both. Should I create 2 separate BPLs - the first for design-time with conditional directives and the second for using on the application? Is this a Delphi XE2 issue and should it be fixed by Embarcadero? – Dmitry Jun 25 '12 at 20:36
  • The issue described on http://stackoverflow.com/questions/11084236/helpsystem-doesnt-work-in-delphi-xe2-with-a-few-projects – Dmitry Jun 25 '12 at 20:37
  • I'm not sure, but this is an excellent question and you should ask it as a new question. You'll not get any visibility by adding it to comments to a new answer to an old question. – David Heffernan Jun 25 '12 at 20:39
  • And for what it is worth, I simply abandoned HtmlHelpViewer and called `HtmlHelp` API directly. That blasted VCL unit was just too much hassle! – David Heffernan Jun 25 '12 at 20:43
  • Thanks, David. I'll try to do the same. – Dmitry Jun 25 '12 at 20:44
  • It might not be so easy to do so for you. I could simply provide an event handler for `Application.OnHelp`. That may be too draconian for your app. If you are in control of it all then my approach would work. But in which case I don't see any reason why you would be using packages. I think they just make life hard. I always statically link all the code into my apps. I still think this is well worth a question. – David Heffernan Jun 25 '12 at 20:47
  • We have a big project with a lot of BPLs and DLLs. Users may update only a part of them when they're using a slow internet connection. Also EXE loads all modules only if they're required on the fly. – Dmitry Jun 25 '12 at 20:52
  • David, why do you help people like me? – Dmitry Jun 25 '12 at 20:57
  • Because it's fun and rewarding! And I learn lots along the way. If you are in control of the top level application then you probably can just do this with `Application.OnHelp` and ditch `HtmlHelpViewer`. – David Heffernan Jun 25 '12 at 20:59
  • Our project uses IHtmlHelpTester. How to access to it without `HtmlHelpViewer` unit? :( – Dmitry Jun 25 '12 at 21:27