Un petit composant (un de plus!) pour n'autoriser qu'un seul lancement d'une application. Je sais qu'il en existe déjà plein, mais celui-ci a un design particulier que je n'ai pas trouvé dans les autres: il est possible, lors du lancement d'une 2ème instance, de récupérer sa ligne de commande depuis la première instance, et de choisir si on en autorise le lancement ou non.
Un scénario tout bête: on imagine qu'on a programmé un éditeur multi-document associé au type de fichier *.xxx
-L'utilisateur double-clique sur le fichier a.xxx (donc une première instance de l'éditeur se lance et ouvre le fichier a.xxx).
-L'utilisateur double-clique sur le fichier b.xxx, une 2ème instance de l'éditeur est bloquée par la première qui, en récupérant la ligne de commande de l'instance bloquée, ouvre un 2ème fichier: b.xxx
-L'utilisateur lance manuellement l'éditeur (sans paramètre, donc). Cette fois-ci, on décide que la 2ème instance ne sera pas bloquée (en effet, si l'utilisateur choisit de le faire explicitement, c'est qu'il a une bonne raison de le faire a priori...)
Mon composant permet donc de faire tout cela simplement par l'intermédiaire de con événement OnBlockInstance. Avant la création de la fiche principale (c'est à dire dans le source du programme même, avant la ligne Application.Initialize) il faut rajoutter ceci:
if not TestRunOnce('Nom') then
Exit;
A la place de 'Nom' il faut mettre un identificateur unique pour votre application. Voilà la définition de la classe TRunonce:
TBlockInstanceEvent=procedure(Sender:TObject;Params:TStrings;var Allow:Boolean) of object;
TRunOnce=class(TComponent)
public
constructor Create(AOwner:TComponent);override;
function IsMaster: Boolean;
destructor Destroy;override;
published
property OnBlockInstance:TBlockInstanceEvent read FOnBlockInstance write SetOnBlockInstance;
property OnAcquireLock:TNotifyEvent read FOnAcquireLock write SetOnAcquireLock;
end;
REGLES D'UTILISATION:
+ Une seule instance du composant peut être créée par application.
+ Avant toute création d'une instance du composant, la fonction TestRunOnce doit être appelée. Sinon, une exception est déclenchée.
+ La fonction TestRunOnce ne peut être appelée qu'une seule fois.
Le non-respect d'une seule de ces règle entraîne une exception EInvalidOperation
Source / Exemple :
{ Code-source du programme }
program Project1;
uses
Forms,
RunOnce,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
{ Il faut ABSOLUMENT que la fonction TestRunOnce soit appelée avant la création du composant TRunOnce. Sinon, une exception est déclenchée.
Son résultat permet de savoir si on est la première instance lancée, ou si la première instance a autorisé le lancement }
if not TestRunOnce('TestRunOnce') then
Exit;
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
{ Code-source de la fiche principale (celle qui crée le composant) }
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, RunOnce;
type
TForm1 = class(TForm)
procedure RunOnce1BlockInstance(Sender: TObject; Params: TStrings;
var Allow: Boolean);
procedure RunOnce1AcquireLock(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
RunOnce1: TRunOnce;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.RunOnce1BlockInstance(Sender: TObject; Params: TStrings;
var Allow: Boolean);
begin
{ événement appelé lors du lancement d'une 2ème instance du programme. On pose la question à l'utilisateur pour autoriser
ou non le 2ème lancement. Params contient la ligne de commande qui a servi à lancer cette 2ème instance }
if MessageBox(0,
PChar('Voulez vous autoriser le 2ème lancement de "'+Params[0]+'" ?'),
'Confirmation',
MB_YESNO or MB_ICONQUESTION or MB_SYSTEMMODAL)=ID_YES then
Allow:=True;
end;
procedure TForm1.RunOnce1AcquireLock(Sender: TObject);
begin
{ événement appelé lorsque l'instance en cours devient celle qui décide si d'autres ont le droit de se lancer ou non.
Ce cas peut se produire si une ou plusieurs instances ont été autorisées, et si la première instance
(celle qui les a autorisées) a été fermée avant les autres. }
Caption:='INSTANCE MAITRE';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
{ Le composant est créé à l'exécution pour ne pas être obligé d'installer le package pour compiler l'exemple }
RunOnce1:=TRunOnce.Create(Self);
RunOnce1.OnBlockInstance:=RunOnce1BlockInstance;
RunOnce1.OnAcquireLock:=RunOnce1AcquireLock;
if RunOnce1.IsMaster then
Caption:='INSTANCE MAITRE';
end;
end.
Conclusion :
Commentaires bienvenus ;-)
Quelques mots sur la façon dont le tout est gèré:
La synchronisation entre processus se fait par l'intermédiaire de 2 mutex nommés (leurs noms incluent la chaîne passée à TestRunOnce).
La récupération des paramètres de la ligne de commande se fait par l'intermédiaire d'un pipe nommé.
6 oct. 2006 à 12:58
6 oct. 2006 à 11:30
Le principe du "system modal" a disparu (pour les progs mode user 'normaux') avec win3.1 et le 16 bits en général.
6 oct. 2006 à 10:18
if (Params.IndexOf('/S')=-1) and ( (Params.IndexOf('/F')>-1) or (MessageBox(...)=ID_YES) ) then
Allow:=True;
Ici, /S est prioritaire sur /F et empèche la 2ème instance à tous les coups (et là encore empèchera la boîte de dialogue de s'afficher). Pour que ça marche, il faut bien sûr que l'option "évaluation booléenne complète" du compilateur soit désactivée (mais c'est le cas par défaut).
En tout cas merci pour les notes!
6 oct. 2006 à 10:13
procedure TForm1.RunOnce1BlockInstance(Sender: TObject; Params: TStrings;
var Allow: Boolean);
begin
if (Params.IndexOf('/F')>-1) or (MessageBox(0,
PChar('Voulez vous autoriser le 2ème lancement de "'+Params[0]+'" ?'),
'Confirmation',
MB_YESNO or MB_ICONQUESTION or MB_SYSTEMMODAL)=ID_YES) then
Allow:=True;
end;
Avec cette version-là, lorsque tu mets le switch /F dans sa ligne de commande, la 2ème instance est toujours autorisée.
6 oct. 2006 à 00:41
On apprend toujours beaucoup de choses avec tes codes. (Merci de cette nourriture pour neurones)
Je n'ai pas encore tout bien compris :-)
Mais je ne désespère pas (je vais avoir besoin d'un peut plus de temps) pour bien comprendre le tout (surtout l'utilisation de Interface pour passer les infos d'une appli à l'autre)
Par contre je pense que le niveau initié est un peut "léger"
Ce qui serait bien ce serait de pouvoir s'affranchir de la boite de dialogue par un switch dans la ligne de commande (Ex: /F /S [F:Force S:SendParam])
En tous cas 10/10 très bien
@+
Cirec
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.