9

An Example:

If you have Visual Studio ( 2010 ) open and running, and then double click a misc *.cs file on your PC desktop, the file will open in the current running instance of Visual Studio, instead of opening another instance of VS.

How can I get my own C# program to mimic this behavior ?

In other words, if I have a file type such as *.myfile associated with my program, and the user double-clicks the *.myfile in Windows Explorer, and.... my program is already running..... it should open the file without Windows starting another instance of my program. If the program was not running, then Windows can start an instance normally.

Note that multiple instances of my program are allowed - same as Visual Studio.

Any suggestions would be appreciated !!

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
user1689175
  • 767
  • 10
  • 18
  • Did you try to check the currently running processes in the system and if you already have your program running maybe pass some info to it? To clarify, you do this at the start of your program before you show any UI and if there is an instance just pass the info to it and close the new instance – Jafar Kofahi Nov 14 '14 at 21:27
  • @Jafar Kofahi - Thanks for the response. Based on your comment, it sounds like a second instance of the program would need to start briefly, check if the file can be passed to another running instance, then pass the file and shutdown if found. I thought about this, but the program is quite large. I guess I was looking for a way to tell Windows not to start another instance, then get a notification somehow if the user double-clicked the associated file. – user1689175 Nov 14 '14 at 21:32
  • Typically this is implemented using [single instance applications](http://stackoverflow.com/questions/424368/opening-a-known-file-type-into-running-instance-of-custom-app-net), which is not what you want. Check this out - [What is the simplest method of inter-process communication between 2 C# processes?](http://stackoverflow.com/questions/528652/what-is-the-simplest-method-of-inter-process-communication-between-2-c-sharp-pro). – Victor Zakharov Nov 14 '14 at 21:34
  • @Neolisk - Thanks for the link. This is the fall back plan if I can't find a way to tell Windows to suppress launching a second instance, and just get a notification. – user1689175 Nov 14 '14 at 21:47

3 Answers3

4

If you look at what is registered for for .cs file in registry you will see that it is not the Visual Studio. For Express edition e.g. the registered application is 'VCSExpress.exe' and the studio is running in in 'WDExpress.exe'. In advanced versions I think the studio runs as 'devenv.exe'. The interesting point is that there are two applications: your UI application and a kind of launcher application. I don't know how VS does it, but I could imagine this way: launcher communicates with UI by any kind of interprocess communication e.g. named pipes. (See here) Maybe try this:

  • Launcher application (your file extension is registered with it) tries to open a pipe to UI application as client.
  • If it fails, it starts a new instance of UI application and passes file name as parameter. UI application start server side of named pipe
  • If pipe is opened successful i.e. there is already running a UI instance, launcher sends file name to existing UI process via pipe.
  • Launcher exits after passing the job to UI
Fratyx
  • 5,717
  • 1
  • 12
  • 22
  • It doesn't seem like there is away around having 'some' program launch when the user double clicks an associated file, and so having a separate loader program start in the background makes the most sense. The program can be kept tiny, and so it will load very fast. As a bonus, I can then use the same program to coordinate auto-updates of the main application. Thanks for investigating the Visual Studio behavior. Since this technically answers my question ( how to mimic Visual Studio ), I will mark this as the answer. Thanks Fratyx !! – user1689175 Nov 16 '14 at 04:33
1

I made some project-template which implements this stuff, using windows-messaging. The template is huge and contains some other stuff (like localization, updates, formclosing, clipboard, and an interface for documents, this way the actions in the MDI can be easily forwarded to the MDI-children). If you want to view the template, try this link (or this link)

Some of the code:

Win32.cs:

public partial class Win32
{
    //public const int WM_CLOSE = 16;
    //public const int BN_CLICKED = 245;
    public const int WM_COPYDATA = 0x004A;

    public struct CopyDataStruct : IDisposable
    {
        public IntPtr dwData;
        public int cbData;
        public IntPtr lpData;

        public void Dispose()
        {
            if (this.lpData != IntPtr.Zero)
            {
                LocalFree(this.lpData);
                this.lpData = IntPtr.Zero;
            }
        }
    }

    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref CopyDataStruct lParam);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LocalAlloc(int flag, int size);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LocalFree(IntPtr p);

}

Program.cs:

static class Program
{
    static Mutex mutex = new Mutex(true, guid());
    static string guid()
    {
        // http://stackoverflow.com/questions/502303/how-do-i-programmatically-get-the-guid-of-an-application-in-net2-0
        Assembly assembly = Assembly.GetExecutingAssembly();
        var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
        return attribute.Value;
    }

    static int MainWindowHandle
    {
        get
        {
            return Settings.Default.hwnd;
        }
        set
        {
            Settings sett = Settings.Default;
            sett.hwnd = value;
            sett.Save();
        }
    }
    public static string GetFileName()
    {
        ActivationArguments a = AppDomain.CurrentDomain.SetupInformation.ActivationArguments;
        // aangeklikt bestand achterhalen
        string[] args = a == null ? null : a.ActivationData;
        return args == null ? "" : args[0];
    }

    [STAThread]
    static void Main()
    {
        if (mutex.WaitOne(TimeSpan.Zero, true))
        {
            #region standaard
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            #endregion
            #region Culture instellen
            string cult = CultureInfo.CurrentCulture.Name;
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(cult);
            Thread.CurrentThread.CurrentCulture = new CultureInfo(cult);
            #endregion
            MainForm frm = new MainForm();
            MainWindowHandle = (int)frm.Handle;
            Application.Run(frm);
            MainWindowHandle = 0;
            mutex.ReleaseMutex();
        }
        else
        {
            int hwnd = 0;
            while (hwnd == 0)
            {
                Thread.Sleep(600);
                hwnd = MainWindowHandle;
            }
            if (hwnd != 0)
            {
                Win32.CopyDataStruct cds = new Win32.CopyDataStruct();
                try
                {
                    string data = GetFileName();
                    cds.cbData = (data.Length + 1) * 2; // number of bytes
                    cds.lpData = Win32.LocalAlloc(0x40, cds.cbData); // known local-pointer in RAM
                    Marshal.Copy(data.ToCharArray(), 0, cds.lpData, data.Length); // Copy data to preserved local-pointer
                    cds.dwData = (IntPtr)1;
                    Win32.SendMessage((IntPtr)hwnd, Win32.WM_COPYDATA, IntPtr.Zero, ref cds);
                }
                finally
                {
                    cds.Dispose();
                }
            }
        }
    }
}

MainFrom.cs:

[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case Win32.WM_COPYDATA:
            Win32.CopyDataStruct st = (Win32.CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(Win32.CopyDataStruct));
            string strData = Marshal.PtrToStringUni(st.lpData);
            OpenFile(strData);
            Activate();
            break;
        default:
            // let the base class deal with it
            base.WndProc(ref m);
            break;
    }
}

It even works when launching up to 15 files at once.

0

It's been almost 20 years since I had to do something like this, but IIRC, you do something like this:

  1. Before anything else, create a Mailslot (or any other convenient IPC tool)
  2. If you are asked to open a document of the type that should go to an existing instance and if there are no other Mailslots, you continue on
  3. If there IS a Mailslot, you send the Mailslot an open message with the file info and then you exit.
  4. Write code to respond to Mailslot open messages.

If you do the steps before you create windows, it should act like what you want.

plinth
  • 48,267
  • 11
  • 78
  • 120
  • 1
    Thanks for the answer. This is still technically launching another instance of the program, however it will only communicate to the first and shut down. You are right that I can perform this check before any windows are created, and it will be invisible to the user. I will mark this as an accepted answer if no one can suggest a way to do this without launching additional instances. – user1689175 Nov 14 '14 at 21:45