Introduction
I will remind that in this series of articles I research cases, when COM-server application doesn't unload. In the previous part of the article we implemented such a COM-server application and reproduced non-unloading behavior. In this part of the article we will research in details the reasons, which lead application to unload or not to unload.
Index
The article consists of several parts. Here is the full list of the article parts:
Unloading COM-server magic
In order to understand cases when application is unloading well and when application is not unloading we need to investigate what happens behind the scene. Let's look into the basic TComObject
class constructor and destructor code:
type
TComObject = class(TObject, IUnknown, ...)
...
public
...
constructor CreateFromFactory(Factory: TComObjectFactory; const Controller: IUnknown);
destructor Destroy; override;
...
end;
implementation
...
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;
As you see when TComObject
instance is created through COM factory internal counter is increased. And when TComObject
instance, which was created through COM factory is destroyed internal counter is decreased. When counter becomes zero, application is terminated:
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;
So when internal object counter becomes zero application is terminated. It happens only when our application is started using COM mechanisms. But we can change this behavior if we implement TComServer.OnLastRelease
event handler (and we actually have done it in the sample application in the first part of the article).
Unloading Windows-application magic
We need to say a bit about usual Windows-application unloading behavior. I created simple test application with one form, which is created on application startup. This form has button, which creates another form instance and shows it. The form code is as simple as this:
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;
You can play with this sample application. The fact is that you can show and close forms, which are created from the first application form and nothing happens to the application. But when you close the form which was the first one the application terminates even if there are other forms still visible. It is quite easy to explain such behavior looking into VCL source code:
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;
As you can see when first application form is created it is stored in TApplication.FMainForm
field and when this form is destroyed application is terminated.
This behavior is not a part of Windows itself. You can implement a windows application which will behave like this, but you can implement another kind of behavior, actually you have full control over the situation. To demonstrate such a possibility I implemented simple application, which is written absolutely without 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.
This application shows form on startup and you can click the form client area for another one form to be shown. When you close any form the application is terminated. And you also can comment out PostQuitMessage
call and than you will not be able to close any application window and terminate application.
So now you know that Delphi makes application to terminate when its main form is closed. Sometimes this behavior is not suited for usage and we need to get rid of it. For example in my test unloading COM-server application we created main application form but made it invisible for user so that he cannot close it. This helped us to ensure that there is only one way to force application to terminate - through releasing all entry-point interface references.
What's next
In this part of the article we researched the unloading behavior of Windows application. We now know that VCL application is by default terminated when its main form is closed (and this behavior is not always what is desired). And the COM-server application is terminated when last entry-point object interface reference is released (and by default this behavior is implemented only when application is run as COM-server).
In the next part of the article I will introduce the concept of smart pointers.
Our final goal is to produce some instrument which will make solving application unloading issues much easier than it is from the box.