user1606027
user1606027

Reputation: 21

open file with a running process

I am making an application that can open a custom document. I connected the document extension to the application (using registry), but when I open the document, it is always opened with a new instance of the application.

I want some logic that can open a document running the current process if it exists. I dont mean a single instance. It should be able to run by multiple instances. Like IE or chrome, it should be able to open an HTML file with tab when the process is running, but it can also run a new instance.

How can I do it?

Upvotes: 0

Views: 2449

Answers (2)

Radin Gospodinov
Radin Gospodinov

Reputation: 2323

This article contains a good description (images taken from there as well).

The approach uses ThreadPool object with EventWaitHandle object to pass messages (objects) between processes (.Net Remoting).
When the application starts, it uses CreateSingleInstance() to call the existing instance OR register itself as single instance application.

Flow diagram

public static bool CreateSingleInstance( string name, EventHandler<InstanceCallbackEventArgs> callback )
{
    EventWaitHandle eventWaitHandle = null;
    int curSessionId = System.Diagnostics.Process.GetCurrentProcess().SessionId;
    name += curSessionId;

    string eventName = string.Format( "{0}-{1}", Environment.MachineName, name );

    // If there is another instance
    InstanceProxy.IsFirstInstance = false;

    InstanceProxy.CommandLineArgs = Environment.GetCommandLineArgs();

    try
    {
        //try to open a handle with the eventName
        eventWaitHandle = EventWaitHandle.OpenExisting( eventName );
    }
    catch
    {
        InstanceProxy.IsFirstInstance = true;
    }

    if( InstanceProxy.IsFirstInstance )
    {
        eventWaitHandle = new EventWaitHandle( false, EventResetMode.AutoReset, eventName );

        // register wait handle for this instance (process)               
        ThreadPool.RegisterWaitForSingleObject( eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false );


        eventWaitHandle.Close();

        // register shared type (used to pass data between processes)          
        RegisterRemoteType( name );
    }
    else
    {
      // here will be the code for the second instance/
    }

    return InstanceProxy.IsFirstInstance;
}
private static void RegisterRemoteType( string uri )
{
    // register remote channel (net-pipes)
    var serverChannel = new IpcServerChannel( Environment.MachineName + uri );
    ChannelServices.RegisterChannel( serverChannel, true );

    // register shared type
    RemotingConfiguration.RegisterWellKnownServiceType(
        typeof( InstanceProxy ), uri, WellKnownObjectMode.Singleton );

    // close channel, on process exit
    Process process = Process.GetCurrentProcess();
    process.Exited += delegate
    {
        ChannelServices.UnregisterChannel( serverChannel );
    };
}
[Serializable]
[System.Security.Permissions.PermissionSet( System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust" )]
internal class InstanceProxy : MarshalByRefObject
{
    private static bool firstInstance;
    private static string[] arrCommandLineArgs;       
    public static bool IsFirstInstance
    {
        get
        {
            return firstInstance;
        }

        set
        {
            firstInstance = value;
        }
    }     
    public static string[] CommandLineArgs
    {
        get
        {
            return arrCommandLineArgs;
        }
        set
        {
            arrCommandLineArgs = value;
        }
    }

    public void SetCommandLineArgs( bool isFirstInstance, string[] commandLineArgs )
    {
        firstInstance = isFirstInstance;
        arrCommandLineArgs = commandLineArgs;
    }
}
public class InstanceCallbackEventArgs : EventArgs
{
    private  bool firstInstance;
    private  string[] arrCommandLineArgs;

    internal InstanceCallbackEventArgs( bool isFirstInstance, string[] commandLineArgs )
    {
        firstInstance = isFirstInstance;
        arrCommandLineArgs = commandLineArgs;
    }

    public bool IsFirstInstance
    {
        get
        {
            return firstInstance;
        }

        set
        {
            firstInstance = value;
        }
    }

    public string[] CommandLineArgs
    {
        get
        {
            return arrCommandLineArgs;
        }
        set
        {
            arrCommandLineArgs = value;
        }
    }
}

Upvotes: 3

Erdogan Kurtur
Erdogan Kurtur

Reputation: 3685

There are many options here, a few them are:

  1. Try use DDE which is ancient history but it is still used by many applications like MS Office. DDE commands are registered on open command for file extension (look HKEY_CLASSES_ROOT\Excel.Sheet.8\shell\Open for example). If application hasn't already been started, it is launched by OS, and DDE command is submitted. If launched, DDE command is submitted to running instance which is registered as a DDE server.

  2. When your process starts try to create an IpcChannel with a predefined name. If your process is launched with file argument, pass file name to running process via IpcChannel. Problem is only one process can create IpcChannel with same name. If that process quits, other processes are left without an open channel.

  3. Every process creates an IpcChannel using process id. When your process starts with a file argument, you enumerate processes where process' path is same as yours, then connect to that process using IpcChannel (where name can be obtained by looking at process id), and then pass filename to it.

  4. Enumerate processes where process' path is same as yours, and send a WM_COPYDATA message containing your filename.

Upvotes: 2

Related Questions