Smart pointers and COM-server unloading. Part 2
Nov 21, 2015
6 min read
Windows
Dev
Intermediate
Advanced
Delphi

by Denis Murashov
Contributor
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:
- Smart pointers and COM-server unloading. Part 1
- Smart pointers and COM-server unloading. Part 2
- Smart pointers and COM-server unloading. Part 3
- Smart pointers and COM-server unloading. Part 4
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 ... { 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;
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.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)