[RESOLVED] Single instance app works - still feels a bit like a hack though...
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 2 12 LastLast
Results 1 to 15 of 20

Thread: [RESOLVED] Single instance app works - still feels a bit like a hack though...

  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    [RESOLVED] Single instance app works - still feels a bit like a hack though...

    Adapting code and information from two CodeGuru posts and two off-site articles linked to by the second one of the CG posts (http://www.codeguru.com/forum/showthread.php?p=1472688, http://www.codeguru.com/forum/showthread.php?p=1890584, http://sanity-free.org/143/csharp_do...plication.html, http://www.codeproject.com/KB/cs/Sin...eAppMutex.aspx - thanks to all contributors ) I managed to add single instance behaviour to my app. Following is my code...

    This is a (singleton) class I used to encapsulate all the interop stuff:

    Code:
    // WndMessageHolder.h
    
    #pragma once
    
    #include <Windows.h>
    
    namespace Kalender
    {
    
    using namespace System;
    using namespace System::Windows::Forms;
    
    public ref class WndMessageHolder
    {
    public:
      static WndMessageHolder ^GetInstance();
    
      static Int32 GetLastError_();
    
      initonly int WM_BRINGUP;
    
      bool PostMessage_(IntPtr, int, IntPtr, IntPtr);
    
      // Taken strictly, the following doesn't really belong here...
      // Actually, it isn't even used as of now. It's a remnant from earlier experiments.
    
      bool SetForegroundWindow_(IWin32Window ^);
    
    protected:
      WndMessageHolder();
    
    private:
      static WndMessageHolder ^s_wmhInstance = nullptr;
    };
    
    }
    Code:
    // WndMessageHolder.cpp
    
    #include "StdAfx.h"
    
    #include "WndMessageHolder.h"
    
    #include <tchar.h>
    
    #pragma comment(lib, "user32.lib")
    
    using namespace Kalender;
    
    WndMessageHolder::WndMessageHolder(void)
    {
      TCHAR tstrBringUpMessage[46] = _T("{16BD1B45-1726-4fd0-8232-1AE422826B24}");
    #ifdef _DEBUG
      _tcscat_s(tstrBringUpMessage, sizeof(tstrBringUpMessage) / sizeof(TCHAR), _T("_debug"));
    #endif
      WM_BRINGUP = RegisterWindowMessage(tstrBringUpMessage);
    }
    
    WndMessageHolder ^WndMessageHolder::GetInstance()
    {
      if (!s_wmhInstance) s_wmhInstance = gcnew WndMessageHolder;
      return s_wmhInstance;
    }
    
    bool WndMessageHolder::PostMessage_(IntPtr hwnd_, int msg_, IntPtr wparam_, IntPtr lparam_)
    {
      return ::PostMessage((HWND)(void *)hwnd_, msg_, (WPARAM)(void *)wparam_, (LPARAM)(void *)lparam_);
    }
    
    bool WndMessageHolder::SetForegroundWindow_(IWin32Window ^wnd_)
    {
      return ::SetForegroundWindow((HWND)(void *)(wnd_->Handle));
    }
    
    Int32 WndMessageHolder::GetLastError_()
    {
      return ::GetLastError();
    }
    This is the .cpp file containing main() where the activation message to the primary instance is broadcast from (in case this is running in a non-primary instance):

    Code:
    // Kalender.cpp: Hauptprojektdatei.
    
    #include "stdafx.h"
    
    #include "Form1.h"
    
    #include "WndMessageHolder.h"
    
    using namespace Kalender;
    
    using namespace System::Threading;
    using namespace System::Diagnostics;
    
    [STAThreadAttribute]
    int main(array<System::String ^> ^args)
    {
      // Aktivieren visueller Effekte von Windows XP, bevor Steuerelemente erstellt werden
      Application::EnableVisualStyles();
      Application::SetCompatibleTextRenderingDefault(false);
    
      String ^strInstanceMutex = "{9EE3300B-1FA0-41bc-8203-9506EB604D12}";
    #ifdef _DEBUG
      strInstanceMutex += "_debug";
    #endif
    
      bool bIMTheOne;
      Mutex ^mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (bIMTheOne) {
        // Hauptfenster erstellen und ausf&#252;hren
        Application::Run(gcnew Form1());
        mtxInstance->Close();
      } else {
    #ifdef _DEBUG
        Debug::WriteLine("Calendar already running, sending WM_BRINGUP message...");
    #endif
        WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
        bool bResult = wmh->PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_BRINGUP, (IntPtr)0, (IntPtr)0);
        Trace::Assert(bResult, String::Format("Broadcasting WM_BRINGUP message failed (code {0})",
          WndMessageHolder::GetLastError_()));
      }
      return 0;
    }
    And these are the involved functions in Form1 (plus the one handling the tray icon click) where the instance activation message is caught:

    Code:
    // Form1.cpp
    
    // ...
    
    System::Void Form1::notifyIcon1_Click(System::Object^  sender, System::EventArgs^  e)
    {
      BringMeUp();  // Factored out because it's also used to handle WM_BRINGUP message
    }
    
    // ...
    
    // Message handler for primary instance activation
    
    void Form1::WndProc(Message &#37;msg)
    {
      if (msg.Msg == WndMessageHolder::GetInstance()->WM_BRINGUP) BringMeUp();
      __super::WndProc(msg);
    }
    
    // Handles both tray icon click and WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      if (WindowState == FormWindowState::Minimized) {
        Visible = true;
        notifyIcon1->Visible = mnuiToggleGhost->Checked;
      } else WindowState = FormWindowState::Minimized;
      WindowState = FormWindowState::Normal;
      // Activate();
    }
    Everything works (under XP Pro SP3) so I should be really satisfied, yet I don't feel completely comfortable with the solution. All possible ways I found in the articles and several forum posts to bring the primary instance window to the front didn't really work. The best I got was actually bringing the window to the front, but not directly activating it, instead flashing its task bar button. I noticed though, that it behaved as desired when it was restored from the minimized state (either on the task bar or the tray) in response to the instance activation message. So I desperately resorted to minizing the app if it isn't already minimized and then (re-)restoring it. Now this does work and it doesn't even cause any annoying optical effects. It doesn't even require the previously used call to Activate() anymore so I could comment it out. But isn't that a bit hackish?

    BTW, I changed the way of handling the mutex to what the codeproject article describes before actually having read it. It simply looked pretty natural to me to handle it that way.

    Also note that I do not use the GC::KeepAlive() trick that is mentioned all around the articles and forum posts. It seems to work perfectly fine without it. I tested letting the app silently lie in the backgound for slightly more than 45 minutes and the instance protection still worked. However, Form1 contains a MonthCalendar that triggers its DateChanged event at least every two minutes, regardless of whether anything has happened (except time passing and probably timer ticks of course) or not. But I don't believe this has any influence on the mutex which is a local variable of main() and thus almost as unrelated as can be.

    Any comments? You're welcome.

    P.S.: Please forgive me all those C-style casts. They simply saved a significant amount of typing effort during lots of back-and-forth changes of the code. Also, the two-stage casts via void * would probably look really ugly without them. I wonder why they're necessary at all but void * was the only type I found to be available as the intermediate step that has machine word size.
    Last edited by Eri523; April 24th, 2011 at 10:37 PM. Reason: Merely fixed a comment in the code
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  2. #2
    Join Date
    Mar 2008
    Location
    Turin / Italy
    Posts
    178

    Re: Single instance app works - still feels a bit like a hack though...

    I haven't any background of CLI but have you read this?

    http://www.flounder.com/nomultiples.htm

    It's about avoiding multiple instances of app in C++. Maybe it could give you some other hints.

  3. #3
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Thanks for the link.

    I haven't yet read the article really thoroughly but the code in there basically appears to use something I used sucessfully all the way while implementing the single instance behaviour (a mutex) and something I tried without success (::SetForegroundWindow()).

    However, at least the discussion of potential problems, in particular race conditions, in the article is something I haven't yet read in that context and the article definitely looks worth thorough reading once I have more time.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  4. #4
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    You could override WndProc (inherited from Form) and make it process the WM_BRINGUP message,
    and than do something like this:
    Code:
    if (WindowState == FormWindowState.Minimized)
        WindowState = FormWindowState.Normal;
    
        Activate();
    In the end, call the base class' implementation to do the rest of the processing.

  5. #5
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Yes, of course what you suggest is the natural variant and I was perfectly sure I had already tested that and it did not work. But maybe I've missed something, so I tried to revert my code to (an equivalent of) what you posted. The following is my BringMeUp() function at the moment that gets called from WndProc() in case it detects a WM_BRINGUP message before calling its base class implementation. (WndProc() is the unchanged original from the OP. - Of course it calls its base class implementation; anything else would be quite fatal... ) Maybe despite careful checking I'm still overlooking something and four (or more) eyes possibly see more than two. So please have a look:

    Code:
    // Handles both tray icon click and WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      if (WindowState == FormWindowState::Minimized) {
        WindowState = FormWindowState::Normal;  // Moved here according to TheGreatCthulhu's suggestion
        Visible = true;
        notifyIcon1->Visible = mnuiToggleGhost->Checked;
      } // else WindowState = FormWindowState::Minimized;  // Commented out according to TheGreatCthulhu's suggestion
      // WindowState = FormWindowState::Normal;
      Activate();  // Reactivated according to TheGreatCthulhu's suggestion
    }
    The first extra line in the if branch is to handle the case when the app is minimized to the tray, in which case I set it to invisible in order to remove it from the task bar. The other extra line controls the visibility of the tray icon, which is to be set to false upon restore from tray unless the "ghost" is activated.

    The behaviour of this variant is exactly what I described it the OP: Anything works except activating the primary instance from a secondary one by sending a WM_BRINGUP message in case the primary instance form is behind any other application window (regardless of whether it's covered entirely, partially or not at all). In this case the primary instance task bar button is flashed instead of bringing its form to the front. It does work, however, if the primary instance form is minimized intead of covered, what originally brought me to the minimize-restore trick.

    Although this renders some other functionality of the app nonfunctional, I even temporarily commented out the two extra lines to make my code exactly resemble your suggestion (except that it's located in an extra function instead of directly in WndProc() and a redundant pair of curly braces). That didn't change anything about the specific behaviour we're discussing here.
    Last edited by Eri523; April 27th, 2011 at 08:49 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  6. #6
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Ok, looks like this one could become tricky... To simplify testing for those who want to assist I have set up a test project and attached it to this post. It includes the original WndMessageHolder class from the original app project, plus the minimum required to reproduce the problem, with two alternative versions of Form1::WndProc(): one based on TheGreatCthulhu's suggestion from post #4 and one based on my original code as posted in the OP between which can be chosen using a macro definition.
    Attached Files Attached Files
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  7. #7
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    That's strange... Your sample project works fine on Windows 7 (with both approaches). Must be a Windows XP thing. Investigation into the unknown is called for...

  8. #8
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    Have you tried SetForegroundWindow?

    Oh, and:
    Quote Originally Posted by Me
    In the end, call the base class' implementation to do the rest of the processing.
    Quote Originally Posted by Eri523
    Of course it calls its base class implementation; anything else would be quite fatal...
    Sorry, it sounded (... it read) as if I was addressing you - I obviously know you don't need to be reminded of such things; but as there may be other people reading, I wanted to mention it for the sake of completeness.

    BTW: When I originally posted, I only gave your code a quick look, and now that I've taken some more time, I see that (aside from the fact that it behaves differently under WinXP - at least in your case) there is nothing remarkably different in "my approach", so scrap that.
    Last edited by TheGreatCthulhu; April 25th, 2011 at 07:28 PM.

  9. #9
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Quote Originally Posted by TheGreatCthulhu View Post
    Have you tried SetForegroundWindow?
    Yes I did. That's where this remnant function in WndMessageHolder is left over from:

    Code:
      // Taken strictly, the following doesn't really belong here...
      // Actually, it isn't even used as of now. It's a remnant from earlier experiments.
    
      bool SetForegroundWindow_(IWin32Window ^);
    I just didn't remove it yet in case it could be useful for testing something I hadn't thought of yet in the future.

    [...] there is nothing remarkably different in "my approach", so scrap that.
    Oh, I wouldn't say there's no remarkable difference. Your approach is the natural, classic one and in fact that was (most likely - the code in BringMeUp() is essentially inherited from versions of the app that didn't have the single instance feature at all, so I'm not really 100% sure) what I tried first. The "alternative approach", OTOH, first "superfluously" minimizes the window if in wasn't already minimized, just to restore it immediatly afterwards. I'd say that's pretty weird and a big difference.

    I already thought of making a new version of the test app which supports logging. I already have a logging class in the original app (designed for reusability in the first place) that I more or less would just need to copy over, but then again, that tiny test app has so few things that are candidate for logging... Would that actually gain anything?

    At the moment I primarily hope someone will test that on another XP to rule out this actually is a peculiarity of my own XP (in which case I'd be really stumped about what to look for...). Unfortunately, none of my test users around here uses that anymore.
    Last edited by Eri523; April 27th, 2011 at 08:57 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  10. #10
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Well, I didn't really get ahead with this since the last post. I'd really love if someone could test that for me on another Windows XP. To simplify testing I have attached a new version of the test project to this post that allows switching between the two instance switching methods at runtime, i.e. in particular without recompilation, so that the compiled version can be passed to people who don't have VC++/VS.

    I'm still not sure wether this is a peculiarity of my own XP, but it looks like it's not a peculiarity of my specific app: Some other apps not written by me appear to have this problem as well, some of which are definitely unmanaged.
    Attached Files Attached Files
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  11. #11
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    Update:
    OMG! Turn's out it's NOT a Windows XP issue - it behaves the same on Windows 7! Except when run from Visual Studio...

    The way I originally tested it is: I use a somewhat customized UI, where right next to the "Start Debugging" button I have a "Start Without Debugging" button. So I just ran the app from there, and it worked in both cases. However, when started in other ways, the "classic" solution behaves as you described.

    Is there something I'm missing about how VS launches the app? I thought that it might have something to do with vshost.exe hosting process, but it seems that it's not used for C++/CLI projects. It behaves the same in both Debug and Release.

    Furthermore, if you hit F5, and then try to run it from somewhere other than VS, it works.

    ...
    In the meantime, I've done some more testing, and now I'm really confused...
    I tried to run it from console, and contrary to my expectations, it worked. Now I can't get it not to work, using the same methods as before. I don't think I did anything special. How weird is that?

  12. #12
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    Update: OK, now I deleted the entire folder and extracted your rar file again, recompiled, and it doesn't work again.
    This is sooooo The X Files.
    EDIT: I don't know what I did, I cannot reproduce the described behavior.
    EDIT 1: This is getting weirder and weirder. F5- start from console... Sometimes it works, sometimes it doesn't...
    EDIT 2: - now it works all the time?! Microsoft, what did you do?!
    Last edited by TheGreatCthulhu; May 17th, 2011 at 08:13 AM.

  13. #13
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    Eri523, I'm going attach the exe file that works (at least on my machine, for now), so that you can test it on yours. Please post the results here. Note that I haven't altered a single line of code, so if it turns out to work, what to heck that means? That windows has somehow altered the PE file?
    Attached Files Attached Files
    Last edited by TheGreatCthulhu; May 17th, 2011 at 08:28 AM.

  14. #14
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Single instance app works - still feels a bit like a hack though...

    UPDATE: OK, I was right to write "for now", as the "working" exe occasionally doesn't work, but most of the time it does. Again, for now... I don't get it. If the conditions are somehow altered, it's not obvious at all.

    Try compiling your app and testing it in various scenarios, like 20 times per run, and see if it behaves the same. This is really, really... unsettling.

  15. #15
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Single instance app works - still feels a bit like a hack though...

    Really, really weird... I already suspected the issue on my pesonal side because I got my copy of VC++ from Warehouse 13, but now that you can reproduce the problem, even on Win 7, my abberation gradually increases...

    The file you posted obviously is a debug build and I had to make a new one. My Debug directory of that project was empty because I apparently didn't do a debug build since I cleaned the project for the attachment to post #10.

    The first thing I checked was the MSIL output of the compiler for Form1::BringMeUp(). They're literally identical in your version of the file and mine, namely:

    Code:
    .method private hidebysig instance void  BringMeUp() cil managed
    {
      // Code size       56 (0x38)
      .maxstack  2
      IL_0000:  ldarg.0
      IL_0001:  ldfld      bool BringUpTest.Form1::m_bAltInstanceSwitch
      IL_0006:  brfalse.s  IL_0021
      IL_0008:  ldarg.0
      IL_0009:  call       instance valuetype [System.Windows.Forms]System.Windows.Forms.FormWindowState [System.Windows.Forms]System.Windows.Forms.Form::get_WindowState()
      IL_000e:  ldc.i4.1
      IL_000f:  beq.s      IL_0018
      IL_0011:  ldarg.0
      IL_0012:  ldc.i4.1
      IL_0013:  call       instance void [System.Windows.Forms]System.Windows.Forms.Form::set_WindowState(valuetype [System.Windows.Forms]System.Windows.Forms.FormWindowState)
      IL_0018:  ldarg.0
      IL_0019:  ldc.i4.0
      IL_001a:  call       instance void [System.Windows.Forms]System.Windows.Forms.Form::set_WindowState(valuetype [System.Windows.Forms]System.Windows.Forms.FormWindowState)
      IL_001f:  br.s       IL_0037
      IL_0021:  ldarg.0
      IL_0022:  call       instance valuetype [System.Windows.Forms]System.Windows.Forms.FormWindowState [System.Windows.Forms]System.Windows.Forms.Form::get_WindowState()
      IL_0027:  ldc.i4.1
      IL_0028:  bne.un.s   IL_0031
      IL_002a:  ldarg.0
      IL_002b:  ldc.i4.0
      IL_002c:  call       instance void [System.Windows.Forms]System.Windows.Forms.Form::set_WindowState(valuetype [System.Windows.Forms]System.Windows.Forms.FormWindowState)
      IL_0031:  ldarg.0
      IL_0032:  call       instance void [System.Windows.Forms]System.Windows.Forms.Form::Activate()
      IL_0037:  ret
    } // end of method Form1::BringMeUp
    As a reference for other readers, this is the source code of that method:

    Code:
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      if (m_bAltInstanceSwitch) {
        // Alternative version - works but unfortunately looks hackish...
    
        if (WindowState != FormWindowState::Minimized)
          WindowState = FormWindowState::Minimized;
        WindowState = FormWindowState::Normal;
        // Activate();
      } else {
        // "Classic" version according to TheGreatCthulhu's suggestion - unfortunately doesn't
        // really work completely... (see CG forum thread)
    
        if (WindowState == FormWindowState::Minimized)
          WindowState = FormWindowState::Normal;
    
        Activate();
      }
    }
    The two .exe files have identical lengths, however, a binary compare reveals 13791 bytes comparing unequal. I'm not familiar enough with the PE format to really interpret them all, but it looks like a considerable bunch of them is just the result of shifting some stuff around inside the file.

    Dumping the structture of the two files to a text file using ILDASM and comparing them shows some differences as well (I estimate significantly less than 10&#37; of the file from looking at Notepad++'s file comaprison display) and they seem all to either come from different hex numbers used to name "UnnamedClass"es and shifting around otherwise unchanged lines (over short distances).

    I have no idea why Windows should modify a PE file on its own at all (maybe to accomodate for some weird permission issues?), and if it actually did, it probably wouln't touch the actual code, rather just maybe the PE header or some manifest stuff, and I don't think that would modify the program's behaviour in the way we're observing. Also, wouldn't it need to modify the file and then somewhen revert it to the original state to cause what we're observing? Even harder to believe I think...

    I didn't check as many variants to start the app as you did (thank's for the effort ). I merely started it either from the IDE or by double-clicking it in Explorer (and I don't think I ever started two instances attempting to run siultaneously both from the IDE). I also did tests involving starting the app via a Start Menu link, but only with the original app the code we discuss here is from. At any rate, the classic method always misbehaved consistently.

    EDIT: Ah, and... In the tests I did with your version of the test app (not many yet, though) it behaved exactly like my version.

    Although I'm somehow disappointed by this thing becomming weirder and weirder instead of getting cleared up, I could pull at least one small immediate benefit from your post #11:

    Quote Originally Posted by TheGreatCthulhu View Post
    I use a somewhat customized UI, where right next to the "Start Debugging" button I have a "Start Without Debugging" button.
    I always wished I had such a button because more often than not I run my apps without debugging in the IDE and the button often would be more convenient than hitting Ctrl+F5. I failed to consider the fact that the IDE has buttons for virtually everything that just need to be placed on the tool bars. Now I have such a button as well. Thanks!

    So, and now I'm gonna watch today's three Twin Peaks episodes. (Actually, the first one already is 2/3 over, but I have the TV in the room with me here.) And the next thing I'll do is change the real-life app so that it always defaults to the alternative method which can only be overridden by a setting in the app config file or an undocumented keyboard shortcut.
    Last edited by Eri523; May 17th, 2011 at 04:03 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Page 1 of 2 12 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Azure Activities Information Page

Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center