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
Second instance of .exe fails
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.
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.
- salvadordf
- Posts: 4304
- Joined: Thu Feb 02, 2017 12:24 pm
- Location: Spain
- Contact:
Re: Second instance of .exe fails
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.
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.
Re: Second instance of .exe fails
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?
- salvadordf
- Posts: 4304
- Joined: Thu Feb 02, 2017 12:24 pm
- Location: Spain
- Contact:
Re: Second instance of .exe fails
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
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
Re: Second instance of .exe fails
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:
----------
------------------
MainForm.pas:
------------------
----------------
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:
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!
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);
Thank you in advance!
Last edited by Maksym on Mon Jan 15, 2024 1:32 pm, edited 1 time in total.
- salvadordf
- Posts: 4304
- Joined: Thu Feb 02, 2017 12:24 pm
- Location: Spain
- Contact:
Re: Second instance of .exe fails
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 :
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;
- salvadordf
- Posts: 4304
- Joined: Thu Feb 02, 2017 12:24 pm
- Location: Spain
- Contact:
Re: Second instance of .exe fails
I just added that function to uCEFMiscFunctions.pas and it's available on GitHub.
Re: Second instance of .exe fails
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:I tested this event and GlobalCEFApp.OnAlreadyRunningAppRelaunch is triggered on the first application instance.
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;
Last edited by Maksym on Tue Jan 16, 2024 11:23 am, edited 3 times in total.
Re: Second instance of .exe fails
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!
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.
- salvadordf
- Posts: 4304
- Joined: Thu Feb 02, 2017 12:24 pm
- Location: Spain
- Contact:
Re: Second instance of .exe fails
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 :
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:
You can test this event with MiniBrowser :
Run the first instance inside the Delphi IDE to see the "customswitch" value in the console.
Run the second instance with this command :
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;
You can use several methods with commandLine to get that information, for example:
- commandLine.GetSwitchValue('customswitch')
- commandLine.CommandLineString
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 second instance with this command :
Code: Select all
MiniBrowser.exe --customswitch=1234