Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Smart pointers and COM-server unloading. Part 2

0.00/5 (No votes)
21 Nov 2015 1  
In the article I describe an approach to handle COM-server unloading issues using smart pointers.

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
...

{ 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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here