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





5.00/5 (1投票)
在本文中,我将介绍一种使用智能指针处理 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 服务器运行时才会实现此行为)。
在下一部分文章中,我将介绍智能指针的概念。
我们的最终目标是产生一些工具,使解决应用程序卸载问题比现在更容易。