When the main form is closed then the application terminates which frees the main form which in turn frees the forms owned by it. The owned forms are not closed, just freed, therefore their OnClose event is normally not triggered at all.
If you see ShowMessage being called from the owned form's OnClose event but the dialog doesn't show up it's probably because the application is already terminated and no longer processing messages. This means that the owned form's OnClose event is triggered by somewhere in your own code but too late.
One way to reproduce this behaviour is to post WM_CLOSE message to the owned form from the main form's OnClose event. The message is then processed by the owned form at a later moment when the application is already terminated any attempt to call ShowMessage or any modal form has no effect anymore.
I agree with Michael that OnCloseQuery is better suited for the purpose of displaying a prompt to the user. Unfortunately this alone doesn't help since the owned forms are being freed not closed. You have to call their OnCloseQuery event manually, for example:
procedure TFormMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
I: Integer;
begin
CanClose := False;
for I := 0 to ComponentCount - 1 do
if Components[I] is TCustomForm then
if not TCustomForm(Components[I]).CloseQuery then
Exit;
CanClose := True; // or another check if the main form can be closed, too
end;