4

I have a clickonce application, and I have set up several file handlers for this application (for the sake of this example, I want to handle files with either the .aaa or .bbb extensions).

If I select a single file with one of these extensions, my application starts up as expected, everything is good. But if I select multiple files and open them (either by hitting Enter or by right clicking and selecting Open), then multiple instances of my aopplication are started up - one instance per file that was selected.

This is not the behavior I expected, I want just one instance to start with multiple file entries in the AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData. Can this be achieved, or is my expectation incorrect?

Edit:
Just to elaborate: we have followed the single instance approach as mentioned by @Matthias, the first instance to start up creates a named server pipe. Subsequent instances then start up, detect that they are secondary, communicate their command line arguments (filename) through to the main instance via the named pipe, then quit. The main instance receives the filename via the named pipe, and does its thing (starts up a file import wizard).

The issue comes when a user selects several files (i.e. 5 files), then selects to open those files in the application. Instead of getting one secondary instance starting with 5 file names supplied on the command line, I'm getting 5 secondary instances of the application starting, each with a single filename on the command line. Each of these then creates a client named pipe and communicates that filename to the main instance - so the server named pipe receives 5 separate messages.

Follow up thoughts:
after chatting about this it occurs to me that maybe this is just the way registered file handlers work, maybe it is not related to clickonce. Maybe the solution is for the server named pipe to pause after receiving each message and to attempt to queue messages before actioning them?

slugster
  • 49,403
  • 14
  • 95
  • 145
  • This behavior also applies to win forms applications. Just tried that. I guess there's no way around this. – Matthias Dec 16 '11 at 03:10

2 Answers2

5

You can achieve this by implementing a single instance application. If the application is already running (second call), you can use named pipes to inform the application (first call) of a file open event.

EDIT

Found a code snippet from an earlier project. I want to underline that the code definitely needs improvements, but it should be a good point where you can start from.

In your static main:

        const string pipeName = "auDeo.Server";
        var ownCmd = string.Join(" ", args);

        try
        {
            using (var ipc = new IPC(pipeName))
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                var form = new ServerForm();

                ipc.MessageReceived += m =>
                {
                    var remoteCmd = Encoding.UTF8.GetString(m);
                    form.Invoke(remoteCmd);
                };
                if (!string.IsNullOrEmpty(ownCmd))
                    form.Invoke(ownCmd);

                Application.Run(form);
            }
        }
        catch (Exception)
        {
            //MessageBox.Show(e.ToString());
            if (string.IsNullOrEmpty(ownCmd))
                return;

            var msg = Encoding.UTF8.GetBytes(ownCmd);
            IPC.SendMessage(pipeName, msg);
        }

The IPC class:

public class IPC : IDisposable
{
    public IPC(string pipeName)
    {
        Stream = new NamedPipeServerStream(pipeName,
                                           PipeDirection.InOut,
                                           1,
                                           PipeTransmissionMode.Byte,
                                           PipeOptions.Asynchronous);

        AsyncCallback callback = null;

        callback = delegate(IAsyncResult ar)
                   {
                    try
                    {
                        Stream.EndWaitForConnection(ar);
                    }
                    catch (ObjectDisposedException)
                    {
                        return;
                    }

                    var buffer = new byte[2000];

                    var length = Stream.Read(buffer, 0, buffer.Length);

                    var message = new byte[length];

                    Array.Copy(buffer, message, length);

                    if (MessageReceived != null)
                        MessageReceived(message);

                    Stream.Disconnect();

                    // ReSharper disable AccessToModifiedClosure
                    Stream.BeginWaitForConnection(callback, null);
                    // ReSharper restore AccessToModifiedClosure
                   };

        Stream.BeginWaitForConnection(callback, null);
    }

    private NamedPipeServerStream Stream
    {
        get;
        set;
    }

    #region IDisposable Members

    public void Dispose()
    {
        if (Stream != null)
            Stream.Dispose();
    }

    #endregion

    public static void SendMessage(string pipeName, byte[] message)
    {
        using (var client = new NamedPipeClientStream(".", pipeName))
        {
            client.Connect();

            client.Write(message, 0, message.Length);

            client.Close();
        }
    }

    ~IPC()
    {
        Dispose();
    }

    public event MessageHandler MessageReceived;
}
Matthias
  • 15,919
  • 5
  • 39
  • 84
  • That is a good thought, but we are already doing exactly that. The problem is that we have multiple secondary instances being started up, they are using the named pipe to communicate the filename through to the main instance, this has the flow on effect of starting several instances of a file import wizard in the app. The desired behavior is for just one secondary instance to be started which then communicates the list of file names back to the main instance, resulting in one instance of the file import wizard. – slugster Dec 16 '11 at 02:35
  • But shouldn't it throw an exception, because you can only create one `NamedPipeServerStream` with a certain name ? – Matthias Dec 16 '11 at 02:37
0

The answer to the problem was to have a small delay at the server end of the pipe. In summary:

  • the first started instance of the app is the owner of the server end of the pipe, subsequent instances of the app are a client
  • When receiving a message from a client, a timer was started, if the timer was already started then it was reset. The passed file name is added to a list.
  • The timer delay was set to 2 seconds, once the tick event occurred (so it had been 2 seconds since the last client communication) the single instance server would take the appropriate action with the list of file names

This is not the behavior I expected, I want just one instance to start with multiple file entries in the AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData. Can this be achieved, or is my expectation incorrect?

My expectation was incorrect - you can only pass through a single file name to a registered file handler, each file name starts a separate instance of the handler.

slugster
  • 49,403
  • 14
  • 95
  • 145