Disclosure Statement: This site contains affiliate links, which means that I may receive a commission if you make a purchase using these links. As an eBay Partner, I earn from qualifying purchases.

Second instance of .exe fails

Maksym
Posts: 3
Joined: Wed Dec 20, 2023 11:13 am

Second instance of .exe fails

Post by Maksym »

I'm on Windows 11, 64-bit. Delphi 2007. How to reproduce the issue: compile Simplebrowser2 demo in Delphi and run 2
instances of the compiled exe. The first one runs just fine, but the second one fails with an access violation every time on this line:

if GlobalCEFApp.StartMainProcess then

Version 119.4.7 is working just fine. So I had to go back to this version in my application. I tried versions 120.1.8 and 120.1.9 - the same issue on both. I didn't try any other versions between 119.4.7 and 120.1.8.
User avatar
salvadordf
Posts: 4056
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Second instance of .exe fails

Post by salvadordf »

Hi,

CEF has changed the way it initializes and now it checks if another app is running with the same RootCache setting.

This feature was added in CEF 120.1.8 :
https://www.briskbard.com/forum/viewtopic.php?p=9337#p9337

See the code comments for GlobalCEFApp.OnAlreadyRunningAppRelaunch, GlobalCEFApp.RootCache and GlobalCEFApp.Cache.
Maksym
Posts: 3
Joined: Wed Dec 20, 2023 11:13 am

Re: Second instance of .exe fails

Post by Maksym »

OK, thank you very much. So, basically, starting with version 120 there can be no multiple instances of the same app with the same RootCache folder. Am I right?
User avatar
salvadordf
Posts: 4056
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Second instance of .exe fails

Post by salvadordf »

Sharing a cache directory between several application instances was never supported by Chromium and it causes cache corruption.

If your application needs to open several browsers then run only one application instance and create new browsers in tabs, frames, child forms, etc.

Having multiple browsers in one application instance is the recommended way. See the TabbedBrowser2, ToolBoxBrowser and MDIBrowser demos for all the details.

Edit : More details in the official CEF forum : https://magpcss.org/ceforum/viewtopic.php?f=6&t=19665
Maksym
Posts: 3
Joined: Wed Dec 20, 2023 11:13 am

Re: Second instance of .exe fails

Post by Maksym »

OK, I've been trying to deal with issue for some time now. I'm on Delphi 2007, creating a 32-bit app. Here are my findings:

1. OnAlreadyRunningAppRelaunch is not called when I start the second instance. Here is the code:

Project.dpr:

----------

Code: Select all

CreateGlobalCEFApp;
  CanProceed := GlobalCefApp.StartMainProcess;
  if (CanProceed) then
  begin
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TMainForm, MainForm);
    Application.Run;
  end;

  DestroyGlobalCEFApp;
------------------

MainForm.pas:

------------------

Code: Select all

uses uCEFApplication;

procedure CreateGlobalCEFApp;
begin
  GlobalCEFApp := TCefApplication.Create;
  GlobalCEFApp.FrameworkDirPath := ExtractFilePath(ParamStr(0));
  GlobalCEFApp.ResourcesDirPath := ExtractFilePath(ParamStr(0));
  GlobalCEFApp.LocalesDirPath := ExtractFilePath(ParamStr(0)) + PathDelim + 'locales';
  GlobalCEFApp.RootCache := ExtractFilePath(ParamStr(0)) + 'Chrome Root';
  GlobalCEFApp.Cache := IncludeTrailingPathDelimiter(GlobalCEFApp.RootCache) + 'TheCache';

  GlobalCEFApp.OnWebKitInitialized := MainForm.GlobalCEFApp_OnWebKitInitialized;
  GlobalCEFApp.OnContextInitialized := MainForm.GlobalCEFApp_OnContextInitialized;
  GlobalCEFApp.OnAlreadyRunningAppRelaunch := MainForm.GlobalCEFApp_OnAlreadyRunningAppRelaunch;
end;

{ TMainForm }

procedure TMainForm.CEFInitializedMsg(var aMessage: TMessage);
begin
  Cursor := crDefault;
  Screen.Cursor := crDefault;

  OpenBrowserButton.Enabled := True;
end;

procedure TMainForm.GlobalCEFApp_OnAlreadyRunningAppRelaunch(
  const commandLine: ICefCommandLine; const current_directory: ustring;
  var aResult: boolean);
begin
  ShowMessage('GlobalCEFApp_OnAlreadyRunningAppRelaunch');
end;

procedure TMainForm.GlobalCEFApp_OnContextInitialized;
begin
  if (MainForm <> nil) and MainForm.HandleAllocated then
    PostMessage(MainForm.Handle, CEF_INITIALIZED, 0, 0);
end;

procedure TMainForm.GlobalCEFApp_OnWebKitInitialized;
begin
  ShowMessage('GlobalCEFApp_OnWebKitInitialized');
end;
----------------

Neither GlobalCEFApp_OnWebKitInitialized, nor GlobalCEFApp_OnAlreadyRunningAppRelaunch is getting called.

Another interesing finding. I would be just fine if I could create a mutext before even trying to initialize and start Chromium. But this code, if placed before the CreateGlobalCEFApp call, completely messes up Chromium - it simply doesn't work:

Code: Select all

  CreateMutex(nil, False, PAnsiChar('MutexChromium120'));
  if (GetLastError = ERROR_ALREADY_EXISTS) then
    Halt(0);
So, could you please give an example of how I should prevent the second instance from starting? All I need to do is pass the command line to the first instance and close the app.

Thank you in advance!
Last edited by Maksym on Mon Jan 15, 2024 1:32 pm, edited 1 time in total.
User avatar
salvadordf
Posts: 4056
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Second instance of .exe fails

Post by salvadordf »

I tested this event and GlobalCEFApp.OnAlreadyRunningAppRelaunch is triggered on the first application instance.

The second instance throws an exception inside the CEF library while executing GlobalCEFApp.InitializeLibrary and it should return false in GlobalCEFApp.StartMainProcess. This should cause that the second application to exit early without even initializing the main application form.

Your mutex code looks good but Chromium uses the same executable for all the subprocesses and the application should only try to create the mutex in the main browser process. Use this function and create the mutex when it returns FALSE :

Code: Select all

function IsCEFSubprocess : boolean;
var
  TempValue : ustring;
begin
  Result := GetCommandLineSwitchValue('type', TempValue) and (length(TempValue) > 0);
end;
User avatar
salvadordf
Posts: 4056
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Second instance of .exe fails

Post by salvadordf »

I just added that function to uCEFMiscFunctions.pas and it's available on GitHub.
Maksym
Posts: 3
Joined: Wed Dec 20, 2023 11:13 am

Re: Second instance of .exe fails

Post by Maksym »

I tested this event and GlobalCEFApp.OnAlreadyRunningAppRelaunch is triggered on the first application instance.
Oh, yes. You are right. It does get triggered on the first instance. My mistake. Is there any way to get the command line of the second instance in this function? For example, instace one is launched like this:

C:\My App\MyApp.exe

and the second instance is launched like this:

C:\My App\MyApp.exe filename=d:\folder\file.txt

So, my task is to process "filename=d:\folder\file.txt" in the first instance and close the second instance quitly without any error messages. Right now it fails on both tasks. I do not get "filename=d:\folder\file.txt" in the commandLine parameter in the first instance's "OnAlreadyRunningAppRelaunch" function, plus I cannot "try except" the ceation and the start of the global CEF app. The exception seems to be happening on the other thread :(

Code: Select all

    try
      CreateGlobalCEFApp;
      CanProceed := GlobalCEFApp.StartMainProcess
    except
      on E: Exception do
      begin
        CanProceed := False; //this line never executes
      end;
    end;
The way I used to deal with it was creating a mutex and if it already exists - I send a Windows message with the command line of the second instance to the first instance, then closing the second instance without even getting to the creation of the GlobalCEFApp. What is the correct way of doing it with Chromium 120+?
Last edited by Maksym on Tue Jan 16, 2024 11:23 am, edited 3 times in total.
Maksym
Posts: 3
Joined: Wed Dec 20, 2023 11:13 am

Re: Second instance of .exe fails

Post by Maksym »

Correction. I can actually "try except" the creation and the start of the GlobalCEFApp. madExcept was preventing this. But I would still like to know how to accomplish my task "the correct way", without having to rely on the exception because I'd like to keep madExcept in the application.

Thank you very much for taking your time to answer my (probably stupid) questions!
Last edited by Maksym on Tue Jan 16, 2024 11:42 am, edited 2 times in total.
User avatar
salvadordf
Posts: 4056
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Second instance of .exe fails

Post by salvadordf »

Both ways are correct and both work.

If you prefer to keep using the mutex you must call IsCEFSubprocess before trying to create the mutex like this :

Code: Select all

begin 
  if IsCEFSubprocess or CreateMyMutex then
    begin
      CreateGlobalCEFApp;
      
      if GlobalCEFApp.StarMainProcess then
        begin
          Application.Initialize;
          Application.CreateForm(TMainForm, MainForm);
          Application.Run;
        end;
        
      DestroyGlobalCEFApp;  
    end;
end;  
If you prefer to use the GlobalCEFApp.OnAlreadyRunningAppRelaunch event then you can read all the command line switches from the second instance in the first app instance.
You can use several methods with commandLine to get that information, for example:
  • commandLine.GetSwitchValue('customswitch')
  • commandLine.CommandLineString
Read the code comments for ICefCommandLine to know all the details.

You can test this event with MiniBrowser :

Code: Select all

procedure GlobalCEFApp_OnAlreadyRunningAppRelaunch(const commandLine: ICefCommandLine; const current_directory: ustring; var aResult: boolean);
begin
  OutputDebugString({$IFDEF DELPHI12_UP}PWideChar{$ELSE}PAnsiChar{$ENDIF}('GlobalCEFApp_OnAlreadyRunningAppRelaunch: ' + commandLine.GetSwitchValue('customswitch') + chr(0)));
  aResult := True;
end;

procedure CreateGlobalCEFApp;
begin
  GlobalCEFApp                            := TCefApplication.Create;
  GlobalCEFApp.cache                      := 'cache';
  GlobalCEFApp.EnablePrintPreview         := True;
  GlobalCEFApp.EnableGPU                  := True;
  GlobalCEFApp.LogFile                    := 'debug.log';
  GlobalCEFApp.LogSeverity                := LOGSEVERITY_INFO;
  GlobalCEFApp.UncaughtExceptionStackSize := 50;
  GlobalCEFApp.OnUncaughtException        := GlobalCEFApp_OnUncaughtException;
  GlobalCEFApp.OnAlreadyRunningAppRelaunch := GlobalCEFApp_OnAlreadyRunningAppRelaunch;
  //GlobalCEFApp.ChromeRuntime       := True;
end;
Run the first instance inside the Delphi IDE to see the "customswitch" value in the console.
Run the second instance with this command :

Code: Select all

MiniBrowser.exe --customswitch=1234
Post Reply