Rusty Nail
Rusty Nail

Reputation: 2710

Windows Shell, passing parameters to Running Instance of an Application

I wonder if someone can provide either a link or an explanation on how Windows Shell passes, for example, a url to the default application, Internet Explorer, Google Chrome, or a parameter through to a custom built application.

I have looked at examples like: Run shell commands using c# and get the info into string

The problem with the above link and other links like it, are they do not work as is expected or do not actually answer the actual question. For Example...

This Code work's as expected:

private string EXEPath = @"C:\Program Files (x86)\Internet Explorer\iexplore.exe";

Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = EXEPath,
Arguments = "http://www.google.com.au",
UseShellExecute = true,
RedirectStandardOutput = false,
CreateNoWindow = true
}
};

process.Start();

This link gives a good example of how to set the application to a single instance application: http://social.msdn.microsoft.com/Forums/vstudio/en-US/a5bcfc8a-bf69-4bbc-923d-f30f9ecf5f64/single-instance-application

Processing the args like so:

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args));
}

and in the Form1 then handling the args like so:

private string[] Args;

public MINION(string[] args)
{
InitializeComponent();

// Assign the incoming Arguments...
this.AppArgs = args;
}

public string[] AppArgs
{
set
{
this.Args = value;

if (this.Args.Length > 0)
{
foreach (string arg in this.Args)
{
// Do the processing here of each arg...
}
}
}
}

I am using Click Once. I have the app set with the VB Single Instance code in the above link. This Code works and only a Single Instance of the Application runs.

My Problem:

If I pass an argument to my Application, tests show the Process ID is not the same as the Process ID of the running instance. Thus showing that the instance of the Application that is already running is not the instance that Args get passed to with the code:

private string EXEPath = @"C:\Program Files (x86)\My App\My App.exe"; // Or actual path to EXE.

Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = EXEPath,
Arguments = "http://www.google.com.au",
UseShellExecute = true,
RedirectStandardOutput = false,
CreateNoWindow = true
}
};

process.Start();

So this code starts a new instance even if the code is in place to set the Application to a single instance app. The existing Application Instance running does not process the Args passed.

The new instance will display a Coded Messagebox("I have run..."), but then exits after that.

EDIT @loopedcode - First Answer. This is the Code I use to ensure I am talking to the same EXE at all times. This works a treat also. Unfortunately I have covered your suggestion already.

Process[] runningProcess = Process.GetProcessesByName("MyEXEName.exe");

Process proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = runningProcess[0].MainModule.FileName,
Arguments = "http://www.google.com.au",
UseShellExecute = true,
RedirectStandardOutput = false,
CreateNoWindow = true
}
};

proc.Start();

I can verify that exactly the same path is valid for the Running Instance and also the Shell Call to the EXE's Path.

EDIT - 11.08.14

I would like to expand the question a little.

When Passing the Parameter, I get all the way to the Processing of the argument in the new Process ID, then the code that switches the Instance to Single Instance Application back to the existing Instance kicks in. At this stage the Args are not passed to the existing instance.

If I use:

MessageBox.Show("Program ID: " + (Process.GetCurrentProcess().Id) + " Arg Passed: " + this.Args[0]);

It runs on the new Instance but not the Existing Instance.

Upvotes: 1

Views: 2307

Answers (2)

Rusty Nail
Rusty Nail

Reputation: 2710

Finally a solution. The solution comes from trial and error. The code I used to configure a single instance Application was the problem, or I should say my understanding of how this Code was written and worked. This is the Code as it was written on the Link above.

using System;   
using System.Collections.Generic;   
using System.Linq;   
using System.Windows.Forms;   
using Microsoft.VisualBasic.ApplicationServices;   

namespace WindowsFormsApplication10   
{   
static class Program   
{   
static Form1 MainForm;   

/// <summary>   
/// The main entry point for the application.   
/// </summary>   
[STAThread]   
static void Main()   
{   
Application.EnableVisualStyles();   
Application.SetCompatibleTextRenderingDefault(false);   
MainForm = new Form1();   
SingleInstanceApplication.Run(MainForm, NewInstanceHandler);   
}   

public static void NewInstanceHandler(object sender, StartupNextInstanceEventArgs e)   
{   
//You can add a method on your Form1 class to notify it has been started again   
//and perhaps pass parameters to it. That is if you need to know for instance    
//the startup parameters.   

//MainForm.NewInstance(e);   

e.BringToForeground = true;   
}   

public class SingleInstanceApplication : WindowsFormsApplicationBase   
{   
private SingleInstanceApplication()   
{   
base.IsSingleInstance = true;   
}   

public static void Run(Form f, StartupNextInstanceEventHandler startupHandler)   
{   
SingleInstanceApplication app = new SingleInstanceApplication();   
app.MainForm = f;   
app.StartupNextInstance += startupHandler;   
app.Run(Environment.GetCommandLineArgs());   
}   
}   
}   
}  

Passing command Line Arguments in this code is not quite as straight forward as it appears in the code. Certainly this is not as I would have expected it to be. The logical flow is just not as is expected. Not Logical.

app.Run(Environment.GetCommandLineArgs());

The above Code starts a NEW Instance of the Application, this is obvious. This is expected to be the Entry point but it is not.

public static void NewInstanceHandler(object sender, StartupNextInstanceEventArgs e)   
{   
//You can add a method on your Form1 class to notify it has been started again   
//and perhaps pass parameters to it. That is if you need to know for instance    
//the startup parameters.   

//MainForm.NewInstance(e);   

e.BringToForeground = true;   
}

The above code is processed as an event Handler (NOT of the NEW Instance but of the existing Instance of the Application) The dead give away is the line:

e.BringToForeground = true;   

So to fix the Problem I added some code in this Event Handler to process the Passed Arguments:

MainForm.AppArgs = new string[] { "Arg 1", "Arg 2" };

This test worked and I have adjusted this Code to accept the Arguments from:

static void Main(string[] args)

The args string Array.

So all should have worked as I had expected, adding third party code to my project threw a curve ball in the equation and it took some testing to find out the actual processes involved to get the right data through to the right part of the Application at the right time.

So the process is: EXE is Executed with Arguments --> Arguments are passed into the "Main" Method via string[] args in a new Process with a new Process ID --> args are set in a class Variable in the new Process with a new Process ID --> the new Process with a new Process ID starts and checks for an already existing Instance of the Application --> if there is an existing instance it is "brought forward" (This is where I was lost and I have passed my args to the existing Instance) and the new Process with a new Process ID exits.

I still, however, do not understand how the Arguments are passed from one instance to the other without any Process Specific Code? As the code:

MainForm.AppArgs 

Is actually in another Process with a different Process ID. Somehow it can still be accessed? Or maybe the Process is somehow instantiated at this point?

Upvotes: 1

loopedcode
loopedcode

Reputation: 4893

For this to work, executable path must match. If you are instantiating ClickOnce, then your executable path is not valid as you have in your example:

private string EXEPath = @"C:\Program Files (x86)\My App\My App.exe"; // Or actual path to EXE.

ClickOnce apps gets downloaded to user's profile app data location. That is where the exe would be executed from. Depending on Windows version, that could be (see this post):

Win7

"c:\users\username\AppData\Local\Apps\2.0\obfuscatedfoldername\obfuscatedfoldername"

Or XP:

"C:\Documents and Settings\username\LocalSettings\Apps\2.0\obfuscatedfoldername\obfuscatedfoldername"

Folder name (as in "obfuscatedfoldername") is generally based on application manifest signed key. If you retrieve the right folder name in your EXEPath, it should pass the arg and work correctly.

Upvotes: 1

Related Questions