65.9K
CodeProject 正在变化。 阅读更多。
Home

智能指针和 COM 服务器卸载。第 2 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015年11月21日

CPOL

3分钟阅读

viewsIcon

9090

downloadIcon

184

在本文中,我将介绍一种使用智能指针处理 COM 服务器卸载问题的方法。

引言

我想提醒的是,在本系列文章中,我将研究 COM 服务器应用程序不卸载的情况。在前一篇文章中,我们实现了一个 COM 服务器应用程序并重现了非卸载行为。在本文的这一部分,我们将详细研究导致应用程序卸载或不卸载的原因。

目录

本文由几部分组成。以下是文章各部分的完整列表

COM 服务器卸载的奥秘

为了理解应用程序何时能正确卸载以及何时不能卸载,我们需要调查幕后发生了什么。让我们看看基本的TComObject类的构造函数和析构函数的代码

type
  TComObject = class(TObject, IUnknown, ...)
  ...
  public
    ...
    constructor CreateFromFactory(Factory: TComObjectFactory; const Controller: IUnknown);
    destructor Destroy; override;
    ...
  end;

implementation
...

{ TComObject }

constructor TComObject.CreateFromFactory(Factory: TComObjectFactory;
  const Controller: IUnknown);
begin
  ...
  if not FNonCountedObject then FFactory.ComServer.CountObject(True);
  ...
end;

destructor TComObject.Destroy;
begin
  if not OleUninitializing then
  begin
    if (FFactory <> nil) and not FNonCountedObject then
      FFactory.ComServer.CountObject(False);
    ...
  end;
end;

正如您所看到的,当通过 COM 工厂创建 TComObject 实例时,内部计数器会增加。并且当通过 COM 工厂创建的 TComObject 实例被销毁时,内部计数器会减少。当计数器变为零时,应用程序终止

function TComServer.CountObject(Created: Boolean): Integer;
begin
  if Created then
  begin
    Result := AtomicIncrement(FObjectCount);
    if (not IsInProcServer) and (StartMode = smAutomation)
      and Assigned(System.Win.ComObj.CoAddRefServerProcess) then
      System.Win.ComObj.CoAddRefServerProcess;
  end
  else
  begin
    Result := AtomicDecrement(FObjectCount);
    if (not IsInProcServer) and (StartMode = smAutomation)
      and Assigned(System.Win.ComObj.CoReleaseServerProcess) then
    begin
      if System.Win.ComObj.CoReleaseServerProcess = 0 then
        LastReleased;
    end
    else if Result = 0 then
      LastReleased;
  end;
end;

procedure TComServer.LastReleased;
var
  Shutdown: Boolean;
begin
  if not FIsInprocServer then
  begin
    Shutdown := FStartMode = smAutomation;
    try
      if Assigned(FOnLastRelease) then FOnLastRelease(Shutdown);
    finally
      if Shutdown then PostThreadMessage(MainThreadID, WM_QUIT, 0, 0);
    end;
  end;
end;

所以当内部对象计数器变为零时,应用程序终止。这只有在使用 COM 机制启动我们的应用程序时才会发生。但是,如果我​​们实现 TComServer.OnLastRelease 事件处理程序,我们可以改变这种行为(实际上我们在文章的第一部分中的示例应用程序中已经这样做了)。

Windows 应用程序卸载的奥秘

我们需要谈谈通常的 Windows 应用程序卸载行为。我创建了一个简单的测试应用程序,其中有一个在应用程序启动时创建的窗体。该窗体有一个按钮,用于创建另一个窗体实例并显示它。该窗体代码非常简单,如下所示

type
  TTestForm = class(TForm)
    ShowButton: TButton;
    procedure FormShow(Sender: TObject);
    procedure ShowButtonClick(Sender: TObject);
  end;

var
  TestForm: TTestForm;
  FormNumber: Integer;

implementation
...
procedure TTestForm.FormShow(Sender: TObject);
begin
  Self.Caption := IntToStr(FormNumber);
  Inc(FormNumber);
end;

procedure TTestForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end

procedure TTestForm.ShowButtonClick(Sender: TObject);
var
  TestForm: TTestForm;
begin
  TestForm := TTestForm.Create(nil);
  TestForm.Left := Self.Left + 50;
  TestForm.Top := Self.Top + 50;
  TestForm.Show;
end;

你可以使用这个示例应用程序。事实是,你可以显示和关闭从第一个应用程序窗体创建的窗体,但应用程序不会发生任何事情。但是当你关闭第一个窗体时,即使还有其他窗体可见,应用程序也会终止。通过查看 VCL 源代码很容易解释这种行为

procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
...
begin
  ...
  if (FMainForm = nil) and (Instance is TForm) then
  begin
    FMainForm := TForm(Instance);
    ...
  end;
end;

procedure TCustomForm.Close;
...
begin
  ...
  if CloseAction <> caNone then
    if Application.MainForm = Self then Application.Terminate
  ...
end;

procedure TApplication.Terminate;
begin
  if CallTerminateProcs then
    PostQuitMessage(0);
end;

正如你所看到的,当创建第一个应用程序窗体时,它存储在 TApplication.FMainForm 字段中,当该窗体被销毁时,应用程序会终止。

此行为不是 Windows 本身的一部分。您可以实现一个像这样的 Windows 应用程序,但您可以实现另一种行为,实际上您可以完全控制情况。为了演示这种可能性,我实现了一个完全没有使用 VCL 编写的简单应用程序

const
  WINDOW_CLASS_NAME = 'TestWindowClass';
var
  WindowClass: TWndClass;
  LMessage: TMsg;

function WindowProc(const AWindowHandle: HWND; const AMessage: UINT; const WParam: WPARAM; const LParam: LPARAM): LRESULT; stdcall;
var
  Rect: TRect;
begin
 case AMessage of
   WM_LBUTTONDOWN:
     begin
       GetWindowRect(AWindowHandle, Rect);
       Rect.Left := Rect.Left + 50;
       Rect.Top := Rect.Top + 50;
       CreateWindow(WindowClass.lpszClassName, 'Test forms application',
         WS_OVERLAPPEDWINDOW or WS_VISIBLE, Rect.Left, Rect.Top, 640, 480, 0, 0, hInstance, nil);
     end;
   WM_CLOSE:
     begin
       Result := 0;
       PostQuitMessage(0);
     end
   else
     Result := DefWindowProc(AWindowHandle, AMessage, WParam, LParam);
 end;
end;

begin
  ZeroMemory(@WindowClass, SizeOf(WindowClass));
  with WindowClass do
  begin
    lpfnWndProc := @WindowProc;
    hInstance := SysInit.HInstance;
    lpszClassName := WINDOW_CLASS_NAME;
    hbrBackground := HBRUSH(COLOR_BACKGROUND);
  end;

  if Winapi.Windows.RegisterClass(WindowClass) = 0 then
  begin
    ExitCode := 1;
    Exit;
  end;

  if CreateWindow(WindowClass.lpszClassName, 'Test forms application',
       WS_OVERLAPPEDWINDOW or WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, nil) = 0 then
  begin
    ExitCode := 2;
    Exit
  end;

  while GetMessage(LMessage, 0, 0, 0) do
    DispatchMessage(LMessage);
end.

此应用程序在启动时显示窗体,你可以单击窗体客户端区域以显示另一个窗体。当你关闭任何窗体时,应用程序都会终止。您还可以注释掉 PostQuitMessage 调用,这样您将无法关闭任何应用程序窗口并终止应用程序。

所以现在你知道 Delphi 会在主窗体关闭时终止应用程序。有时这种行为不适合使用,我们需要摆脱它。例如,在我的测试卸载 COM 服务器应用程序中,我们创建了主应用程序窗体,但使其对用户不可见,以便他无法关闭它。这有助于我们确保只有一种强制应用程序终止的方法 - 通过释放所有入口点接口引用。

下一步

在本文的这一部分中,我们研究了 Windows 应用程序的卸载行为。我们现在知道,默认情况下,VCL 应用程序会在其主窗体关闭时终止(并且这种行为并不总是我们想要的)。当最后一个入口点对象接口引用被释放时,COM 服务器应用程序会终止(并且默认情况下,只有当应用程序作为 COM 服务器运行时才会实现此行为)。

下一部分文章中,我将介绍智能指针的概念。

我们的最终目标是产生一些工具,使解决应用程序卸载问题比现在更容易。

智能指针和 COM 服务器卸载。第 2 部分 - CodeProject - 代码之家
© . All rights reserved.