Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i tell if i'm being called during DLL_PROCESS_DETACH after ExitProcess has been called?

i have a unit in Delphi that (has the option) to provide a single global object:

var
   InternalThemeParkServices: TThemeParkServices;

function ThemeParkServices: TThemeParkServices;
begin
   if InternalThemeParkServices= nil then
      InternalThemeParkServices := TThemeParkServices.Create();
   Result := InternalThemeParkServices ;
end;

...

initialization
finalization
   FreeAndNil(InternalThemeServices);
end.

And i destroy myself during process shutdown.

Note: Another code variant is:

var
   InternalThemeParkServices: IThemeParkServices;

function ThemeParkServices: TThemeParkServices;
begin
   if InternalThemeParkServices= nil then
      InternalThemeParkServices := TThemeParkServices.Create();
   Result := InternalThemeParkServices ;
end;

Where the interface variable is implicitly destroyed when it's reference count goes to zero during program shutdown

When my object is no longer used (i.e. during its destructor), i call call various WinAPI functions.

The problem is that if someone uses my class from a DLL (something which i cannot control), then anything being called during:

finalization

is the Delphi moral equivalent of DLL_PROCESS_DETACH. There are all kinds of things i should not be doing during DLL_PROCESS_DETACH when the process is terminating (e.g. CoCreateInstance).

i know Embarcadero uses:

initialization 
   if not IsLibrary then
   begin
      ...

Which i perhaps i could adapt, changing my code from:

var
   InternalThemeParkServices: IThemeParkServices;

(using implicit cleanup), to:

var
   InternalThemeParkServices: IThemeParkServices;
...
finalization
   if IsLibrary then
      Pointer(InternalThemeParkServices) := nil; //defeat reference counting and let the object leak
end;

and let it leak.

But is this the best resolution? i assume it means that if the dll running my code is unloaded (but not during process shutdown) that i'll leak memory. If the dll is attached and detached, i'll leak each time.

What i really want is Delphi to run its finalization blocks before ExitProcess/DllMain(DLL_PROCESS_DETACH). Is this possible?

Bonus Chatter

@pa deciphered the Delphi application shutdown scheme:

The hierarchy of shutdown is as follows

  Application.Terminate()
    performs some unidentified housekeeping of application
    calls Halt()

  Halt()
    calls ExitProc if set
    alerts the user in case of runtime error
    get rid of PackageLoad call contexts that might be pending
    finalize all units
    clear all exception handlers
    call ExitprocessProc if set
    and finally, call ExitProcess() from 'kernel32.dll'

  ExitProcess() 
    unloads all DLLs
    uses TerminateProcess() to kill the process

With DLLs being unloaded after a call to ExitProcess - because Windows is the one who does it.

like image 447
Ian Boyd Avatar asked Feb 20 '23 16:02

Ian Boyd


1 Answers

To tell if you're being called during DLL_PROCESS_DETACH after ExitProcess has been called, you can write initialization code for your library so that your code is executed when FreeLibrary is called from the main program. The 'lpReserved' parameter will be '1' if ExitProcess have already been called, '0' otherwise:

..
var
  SaveDllProcEx: TDllProcEx;

procedure DllMainEx(Reason: Integer; Reserved: Integer);
begin
  if (Reason = DLL_PROCESS_DETACH) and (Reserved = 0) then
    // Main app is still running, cleanup.

  if Assigned(SaveDllProcEx) then
    SaveDllProcEx(Reason, Reserved);
end;

initialization

if IsLibrary then begin
  SaveDllProcEx := DllProcEx;
  DllProcEx := @DllMainEx;
end;


From DllMain entry point:

The lpReserved parameter indicates whether the DLL is being unloaded as a result of a FreeLibrary call, a failure to load, or process termination.

like image 121
Sertac Akyuz Avatar answered Feb 24 '23 23:02

Sertac Akyuz