0

I am trying to create a DLL in Delphi XE2 which will popup a form with a TWebBrowser component in it. When the WebBrowser.Navigate2 method is called the finalization section of the unit (or any unit) is not called when the application ends. If Navigate2 is not called, the finalization section happens just fine.

The dll is being called from C++ (VS 2010 MFC console at the moment) and linked via in import library.

There are other ways of doing this, but I would like to reuse the code we already have written.

Does anyone have any idea what is going on?

Thanks.

Here is a simple recreation of the problem:

library DisplayPatientAlertsIntf;
exports DisplayPatientAlertsA name 'DisplayPatientAlertsA@4';

begin
end.

unit uAlertWindow;

interface

uses
  Winapi.ActiveX,
  Forms,
  SHDocVw,
  Graphics, Controls;

function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;

implementation

var ts : TStringList;

function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;
  var Form1 : TForm;
      WebBrowser1 : TWebBrowser;
      DidCoInit : Boolean;
begin
  DidCoInit := Succeeded(CoInitialize(nil));
  try
    Form1 := TForm.Create(nil);
    try
      WebBrowser1 := TWebBrowser.Create(nil);
      try
        WebBrowser1.ParentWindow := Form1.Handle;
        WebBrowser1.Align := alClient;
        WebBrowser1.Navigate2('file://c:\temp.html');
        Form1.ShowModal;
      finally
        WebBrowser1.Free;
      end;
    finally
      Form1.Free;
    end;
  finally
    if DidCoInit then
      CoUninitialize;
  end;
  Result := 0;
end;

initialization
  ts := TStringList.Create;

finalization
  ts.Free;

end.

Update 2013.03.19 While solving another problem (dbExpress drivers in a dll), I changed it from a statically linked dll with an import library to a dynamically loaded dll and everything started working.

tmjac2
  • 146
  • 2
  • 11
  • I don't see the point in calling `Release` and `ProcessMessages`. Just call `Form1.Free`. And how did you make your lib file? – David Heffernan Mar 18 '13 at 19:19
  • Form1.Free with or without the Application.ProcessMessages didn't seem to solve the problem. The lib is created with lib (in Visual Studio Command Prompt) using a .def file LIBRARY DisplayPatientAlertsIntf EXPORTS DisplayPatientAlertsA@4 – tmjac2 Mar 18 '13 at 20:20
  • No, `Form1.Free` won't solve your problem. I just wondered why you were using `Release` and `ProcessMessages`. That seemed wrong to my eyes. – David Heffernan Mar 18 '13 at 20:22
  • according to the msdn you should (may) not call CoInitialize and CoUnitialize in DllMain. The initialization and finalization sections are in fact called within that function so don't be surprised of a side effect like the one you are experience. Go for Remy's suggestions they are the only one which will work! – mrabat Mar 18 '13 at 20:33
  • Thanks. I moved the CoInitialize and CoUninitialize into the actual procedure. It didn't seem to make any difference to the original problem. The program ends just fine, the finalization just never gets called. – tmjac2 Mar 18 '13 at 21:06
  • Why not use BPL as intended for Delphi ? – Arioch 'The Mar 19 '13 at 05:50
  • Since TWebBrowser is creating a few threads when you call Navigate2, [this](https://forums.embarcadero.com/thread.jspa?threadID=51512) and [this](http://blogs.remobjects.com/blogs/ck/2006/10/05/p121) might be related. I might be way off thought... – kobik Mar 19 '13 at 12:23

3 Answers3

6

Do not call CoInitialize() or CoUninitialize() during the DLL's initialization/finalization. That is a very bad place to do that, and besides, it is not the DLL's responsibility to call them anyway. It is the responsibility of the thread that is calling the DLL functions. If you must call them, then at least do so inside of your exported function instead.

As for the exported function itself, use WebBrowser1.Parent instead of WebBrowser1.ParentWindow, use Form1.Free instead of Form1.Release, and get rid of Application.ProcessMessages altogether.

And lastly, do not export the function using a manually decorated name. That is not the DLL's responsibility to do, either. Let the compiler handle the decorating. If there is a naming mismatch when importing the function, that needs to be addressed in the calling app, not the DLL itself.

Your misuse of both COM and the VCL (especially since the problem only occurs once the exported DLL function is called) are likely leading to deadlocks, preventing the DLL from unloading from memory correctly, and thus none of its finalization sections would be called because its DLL entry point is not able to be called. COM is very sensitive when it comes to its initialization/cleanup, so you have to make sure you do it correctly, and in the correct context.

Try this:

library DisplayPatientAlertsIntf;

uses
  uAlertWindow;

exports
  DisplayPatientAlertsA;

begin
end.

.

unit uAlertWindow;

interface

uses
  Winapi.ActiveX,
  Forms,
  SHDocVw,
  Graphics, Controls;

function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;

implementation

function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;
var
  Form1 : TForm;
  WebBrowser1 : TWebBrowser;
  DidCoInit: Boolean;
begin
  Result := 0;
  try
    DidCoInit = Succeeded(CoInitialize(nil));
    try    
      Form1 := TForm.Create(nil);
      try
        WebBrowser1 := TWebBrowser.Create(Form1);
        WebBrowser1.Parent := Form1;
        WebBrowser1.Align := alClient;
        WebBrowser1.Navigate2('file://c:\temp.html'); //This contains 'ASDF'
        Form1.ShowModal;
      finally
        Form1.Free;
      end;
    finally
      if DidCoInit then
        CoUninitialize;
    end;
  except
    Result := -1;
  end;
end;

end.
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • @DavidHeffernan: his misuse of both COM and the VCL, especially since the problem only occurs once the exported DLL function is called, are likely leading to deadlocks, preventing the DLL from unloading, and thus none of its finalization sections would be called. COM is very sensitive when it comes to its initialization/cleanup. – Remy Lebeau Mar 18 '13 at 20:50
  • 1
    I moved the CoInitialize and CoUninitialize into the actual procedure. It didn't seem to make any difference to the original problem. The program ends just fine, the finalization just never gets called. – tmjac2 Mar 18 '13 at 21:05
  • Add an event handler to the RTL's `DllProc/Ex()` callbacks and make sure they are being called with the `DLL_PROCESS_DETACH` flag. If they are not, then the DLL is not being unloaded at all, either because you did something to mismatch the DLL's reference count, or the DLL is deadlocked. – Remy Lebeau Mar 18 '13 at 23:26
0

Delphi does not make heavy use of plain DLLs and its support is basic and scarcely documented

While Delphi makes good work for EXE files, intercepting WinMain and bringing its semantics to Turbo Pascal style context, for DLL you have to do it manually.

Start with reading DLL-Main Microsoft documentation and tutorials.

Then you can add into your DLL.dpr something like

begin
  DLLProc := @DLLMain;
  DLLMain(DLL_PROCESS_ATTACH);
end.

And then in some unit of DLL you can implement it like this:

procedure DLLMain(dwReason: DWORD);
begin
  case dwReason of
  DLL_PROCESS_ATTACH:
    begin
      Application.HelpFile := HelpFileName;
      dmSelVars := TdmSelVars.Create(nil);
    end {= DLL_PROCESS_ATTACH =};

  DLL_PROCESS_DETACH:
    begin
      Application.Handle := 0;
      FreeAndNil(dmSelVars);
      g_pSvRec := nil;
    end {= DLL_PROCESS_DETACH =};
  end {= case =};
end {= DLLMain =};

PS. Why using DLL, when you can use Delphi-native (since 1997) BPL instead ? It solves many problems and it provides much better finalization support:

  • for manually-loaded packages (via LoadPackage(...) call) finalization called for all units by granted
  • for manually-loaded packages (via Project Options / Packages / Link with Runtime packages list ) finalization is called for all units, referenced in "uses" sections of your host EXE.

PPS. Starting MSIE for merely displaying one page - doesn't it look overkill ? Perhaps native HTML support would suffice, even if limited somewhat ? And it is capable of loading page from TStream or from String w/o tinkering with intermediate temporary files. (Well, MSIE is capable as well, though, after some scaffolding).

Community
  • 1
  • 1
Arioch 'The
  • 15,799
  • 35
  • 62
  • 4
    The host process is a VS exe. So packages are out. And Delphi has always supported building DLLs. – David Heffernan Mar 19 '13 at 07:28
  • @David, i told that Delphi supports building DLLs in the 1st line of the answer. So this part of your correction seems mis-aimed. – Arioch 'The Mar 19 '13 at 08:14
  • "Delphi does not make heavy use of plain DLLs and its support is basic and scarcely documented" In what way is its support basic? And what does "does not make heavy use of" mean? – David Heffernan Mar 19 '13 at 08:20
  • Finalization is basic feature of modern Delphi language - and it does not work without next to undocumented hacks. Type safety: almost none in DLL, providing for DLL hell. OOP almost none between DLL and BPL. And those are basic language features as well. IDE building blocks: mostly BPLs, not DLLs. VCL building blocks: almost exclusively BPLs, not DLLs. VCL themes: crashes after DLLs added to perfectly worked EXE+BPLs app (QC number do not remember outright). WinSxS manifests and deployment, fighting DLL Hell, where is IDE Wizard or property page for it ? etc, etc. – Arioch 'The Mar 19 '13 at 08:33
0

You might find that an exception is being raised when one of the units is being finalized, preventing other units from being finalized.

I'm not sure about XE2, but older versions of Delphi tended to be very fussy about the ComObj unit being "high up" in the uses/initialization so it would be one of the last to finalize.
The problem was that if ComObj was finalized too soon, it would CoUninitialize too soon - effectively ripping the rug from under other code that still expected COM to be initialized.

If the XE2 version of SHDocVw still uses ComObj in its implementation section, then ComObj will be initialized relatively 'late'. So that could very well be your problem. In which case simply adding it explicitly and high up in your source should do the trick.

Disillusioned
  • 14,635
  • 3
  • 43
  • 77