0

我正在尝试在 Delphi XE2 中创建一个 DLL,它将弹出一个包含 TWebBrowser 组件的表单。当调用 WebBrowser.Navigate2 方法时,单元(或任何单元)的终结部分在应用程序结束时不会被调用。如果未调用 Navigate2,则完成部分会正常进行。

dll 正在从 C++(目前是 VS 2010 MFC 控制台)调用,并通过导入库链接。

还有其他方法可以做到这一点,但我想重用我们已经编写的代码。

有谁知道发生了什么?

谢谢。

这是问题的简单再现:

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.

更新 2013.03.19 在解决另一个问题(dll 中的 dbExpress 驱动程序)时,我将其从带有导入库的静态链接 dll 更改为动态加载的 dll,一切都开始工作了。

4

3 回答 3

6

不要在 DLL 的初始化/完成过程中调用CoInitialize()或。CoUninitialize()这是一个非常糟糕的地方,而且无论如何调用它们不是 DLL 的责任。调用 DLL 函数的线程负责。如果您必须调用它们,那么至少在导出的函数中这样做。

至于导出的函数本身,使用WebBrowser1.Parent代替WebBrowser1.ParentWindow,使用Form1.Free代替Form1.Release,并完全摆脱Application.ProcessMessages

最后,不要使用手动修饰的名称导出函数。这也不是 DLL 的职责。让编译器处理装饰。如果在导入函数时命名不匹配,则需要在调用应用程序中解决,而不是 DLL 本身。

您对 COM 和 VCL 的滥用(特别是因为该问题仅在调用导出的 DLL 函数后才发生)可能会导致死锁,从而阻止 DLL 从内存中正确卸载,因此不会调用其终结部分,因为它的无法调用 DLL 入口点。COM 在初始化/清理方面非常敏感,因此您必须确保在正确的上下文中正确执行此操作。

尝试这个:

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.
于 2013-03-18T19:55:57.687 回答
0

您可能会发现当其中一个单元正在完成时会引发异常,从而阻止其他单元完成。

我不确定 XE2,但旧版本的 Delphi 往往对 ComObj 单元在使用/初始化中“高位”非常挑剔,因此它将是最后完成的单元之一。
问题在于,如果 ComObj 过早完成,它将 CoUninitialize 过早 - 有效地从仍然期望 COM 初始化的其他代码下撕下地毯。

如果 SHDocVw 的 XE2 版本在其实现部分仍然使用 ComObj,那么 ComObj 将相对“延迟”初始化。所以这很可能是你的问题。在这种情况下,只需在源代码中显式地添加它就可以了。

于 2013-03-19T17:10:54.187 回答
0

Delphi 没有大量使用普通的 DLL,它的支持是基本的,几乎没有文档记录

虽然 Delphi 对 EXE 文件做得很好,拦截 WinMain 并将其语义引入 Turbo Pascal 样式上下文,但对于 DLL,您必须手动完成。

从阅读DLL-Main Microsoft 文档和教程开始。

然后你可以添加到你的DLL.dpr东西

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

然后在某个 DLL 单元中,您可以像这样实现它:

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。当您可以使用 Delphi-native(自 1997 年以来)BPL 时,为什么要使用 DLL?它解决了许多问题,并提供了更好的终结支持:

  • 对于手动加载的包(通过LoadPackage(...)调用),所有单元都被授予完成
  • 对于手动加载的包(通过Project Options / Packages / Link with Runtime packageslist ),所有单元都需要完成,在主机 EXE 的“使用”部分中引用。

聚苯乙烯。启动 MSIE 仅显示一页 - 看起来是不是有点矫枉过正?也许原生 HTML 支持就足够了,即使有所限制?它能够从 TStream加载页面或从不String修改中间临时文件的情况下加载页面。(不过,经过一些脚手架,MSIE 也有能力)。

于 2013-03-19T06:06:57.203 回答