Reputation: 21
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
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.
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
Reputation: 3685
There are many options here, a few them are:
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.
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.
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.
Enumerate processes where process' path is same as yours, and send a WM_COPYDATA message containing your filename.
Upvotes: 2