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

Hybrid View

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

    [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,675

    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,133

    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,675

    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,675

    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,133

    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,133

    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,675

    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,675

    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,133

    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
    Jun 2010
    Location
    Germany
    Posts
    2,675

    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.

  13. #13
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

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

    Late update...

    I just happened to notice that flashing the task bar button respectively window caption actually is the intended and documented behavior of Form::Activate() in case the calling application isn't the active one:

    Quote Originally Posted by MSDN
    Activating a form brings it to the front if this is the active application, or it flashes the window caption if this is not the active application. [...]


    So now the question is: Is there any (preferably "official") way for an application to activate itself? At least I didn't find one in either the System::Windows::Forms::Application or System::Environment (rather less likely) class.
    Last edited by Eri523; July 28th, 2011 at 05: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.

  14. #14
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

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

    I just revisited this topic because I had a new idea how to implement it: There definitely seems to be no way for an app to activate itself. Perhaps the Windows developers considered the possibility of self-activation a risk - at least an annoyance risk? However, of course, apps can be activated by other apps, like Process Explorer for instance.

    Based on this consideration, I implemented a new way of activation of the primary app instance when another one is started. The goal was to call BringWindowToTop() on the primary instance main window from the non-primary instance. The problem just was that the non-primary instance doesn't know its handle. So I introduced two new registered window messages, one with which the non-primary instance announces its own main window handle and another one that the primary instance uses to send back its own main window handle to the non-primary window handle it just got to know.

    Following is the implementation. (I also attached the new version of the demo project to the post.) I'm not posting the modified WndMessageHolder since the changes I applied to it are mostly natural and can be deduced from the code posted. Of course it is part of the attached project. Also, I have reverted the BringUpTest.cpp file which contains the main() function back to the state in which it was created by the IDE, since all the instance switching work now is done from the form class because it needs a running message loop in order to catch the response from the primary instance.

    Code:
    // Form1.cpp
    
    #include "stdafx.h"
    
    #include "Form1.h"
    #include "WndMessageHolder.h"
    
    using namespace BringUpTest;
    
    // Message handler for primary instance activation
    
    void Form1::WndProc(Message %msg)
    {
      WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
      if (msg.Msg == wmh->WM_BRINGUP) BringMeUp();
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISE) MessageEchoAdvertiseHandler(msg.WParam);
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE) MessageEchoAdvertisementResponseHandler(msg.WParam);
      __super::WndProc(msg);
    }
    
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      switch (m_ism) {
        case InstanceSwitchMethod::Classic:
          // "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();
    
          break;
    
        case InstanceSwitchMethod::Alternative:
          // Alternative version - works but unfortunately looks hackish...
    
          if (WindowState != FormWindowState::Minimized)
            WindowState = FormWindowState::Minimized;
          WindowState = FormWindowState::Normal;
          // Activate();
          break;
    
        case InstanceSwitchMethod::MessageEcho:
          Trace::Fail("Message Echo method does not use the WM_BRINGUP message");
          break;
      }
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISE message - gets passed the non-primary instance main window handle
    
    void Form1::MessageEchoAdvertiseHandler(IntPtr ipHwndNonPrimary)
    {
      bool bResult = WndMessageHolder::PostMessage_(ipHwndNonPrimary,
        WndMessageHolder::GetInstance()->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE, Handle, (IntPtr)0);
      Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message failed (code {0})",
        WndMessageHolder::GetLastError_()));
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message - gets passed the primary instance main window handle
    
    void Form1::MessageEchoAdvertisementResponseHandler(IntPtr ipHwndPrimary)
    {
      bool bResult = WndMessageHolder::BringWindowToTop_(ipHwndPrimary);
      Trace::Assert(bResult, String::Format("Calling BringWindowToTop() on primary instance main window handle failed (code {0})",
        WndMessageHolder::GetLastError_()));
      tmrMessageEchoTimeout->Stop();
      Close();
    }
    
    // Event handler to switch between the two instance switching modes
    
    System::Void Form1::InstanceSwitchModeChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbMessageEcho->Checked) {
        if (!m_evtMessageEchoSignal) {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(true, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
        } else m_evtMessageEchoSignal->Set();
        m_ism = InstanceSwitchMethod::MessageEcho;
      } else {
        if (m_evtMessageEchoSignal) m_evtMessageEchoSignal->Reset();
        if (rbClassic->Checked) m_ism = InstanceSwitchMethod::Classic;
        else if (rbAlt->Checked) m_ism = InstanceSwitchMethod::Alternative;
        else Trace::Fail("What the heck... Unexpected outcome of instance switching mode change");
      }
    }
    
    System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e)
    {
      String ^strInstanceMutex = "{BE3A098C-D815-43e7-B8E2-5EC55CBC5306}";
    #ifdef _DEBUG
      strInstanceMutex += "_debug";
    #endif
    
      bool bIMTheOne;
      m_mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (!bIMTheOne) {
        WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
        m_evtMessageEchoSignal = gcnew EventWaitHandle(false, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
    
        if (m_evtMessageEchoSignal->WaitOne(0)) {
    
          // The event isn't actually part of the Message Echo mechanism. It's just used in this demo
          // to signal the non-primary instance to use the Message Echo method.
    
          // Advertise our own main window handle:
    
          Trace::WriteLine("BringUpTest already running, sending WM_MESSAGEECHO_ADVERTISE message...");
          bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_MESSAGEECHO_ADVERTISE, Handle, (IntPtr)0);
          Trace::Assert(bResult, String::Format("Broadcasting WM_MESSAGEECHO_ADVERTISE message failed (code {0})",
            WndMessageHolder::GetLastError_()));
    
          // The primary instance is now supposed to respond with a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message
          // carrying its own main window handle
    
          // Make my own main window invisible and start timeout:
    
          Visible = false;
          tmrMessageEchoTimeout->Start();
    
        } else {
    
          Trace::WriteLine("BringUpTest already running, sending WM_BRINGUP message...");
          bool bResult = WndMessageHolder::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_()));
          Close();
    
        }
      }
    }
    
    System::Void Form1::tmrMessageEchoTimeout_Tick(System::Object^  sender, System::EventArgs^  e)
    {
      // This handler actually should never get called
    
      Trace::Fail("BringUpTest: Message Echo mechanism timed out");
    }
    It's plain to see that this is considerably more complicated than the Alternative approach used in the earlier posts. I probably won't implement it that way in the real-life app at all, not only because of the increased complexity, but also because the older, somewhat hackish approach leads to a considerably smoother repaint of the primary instance that just has been brought to the top. (At least that's the case on my own system. Anyone who wants to test that on something else than an XP SP3 is welcome.) At least it's cleaner style, IMO...

    Comments welcome!
    Last edited by Eri523; December 17th, 2011 at 04:47 PM. Reason: Removed attachment - has been superseded by the one attached to the next post
    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.

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

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

    Oops! There has been a serious bug in the demo project attached to the last post: Due to failure to properly adjust a copy-pasted piece of code, the program effectively used a WM_NULL for the advertisement response used in the Message Echo method instead of the registered message. This seemingly worked until some other program attempted to check the responsiveness of the test app, which is usually done by sending a WM_NULL that is supposed to be ignored by the app that receives it. Due to the bug, the responsiveness check resulted in an assertion failure since, of course, the message sent in this process doesn't carry a valid window handle in its wParam.

    To fix the bug, those who don't want to download the new version of the demo project just need to change line 31 of WndMessageHolder.cpp to this:

    Code:
      WM_MESSAGEECHO_ADVERTISEMENTRESPONSE = RegisterWindowMessage(tstrMessageEchoAdvertisementResponse);
    However, the new version of the demo project attached to this post also implements yet another instance switching method called Deposited Handle that I created because I thought maybe the message traffic between the two instances was responsible for the awkward repainting occuring when using the Message Echo method. The Deposited Handle method works by the primary instance placing its own window handle in a registry entry to be retrieved by the secondary instance.

    Simply calling BringWindowToTop() on the primary instance window had no observable effect, though. So I resorted to having the non-primary instance post a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message to itself as a workaround which basically works, but leads to the same repainting behavior as the Message Echo method.

    At any rate, following is the current version of the demo project's Form1.cpp. In addition to the changes already mentioned, I mainly cleaned up the handling of radio button state changes. I hope the comments I placed in the code answer at least most of the questions remaining after the explanations above.

    Code:
    // Form1.cpp
    
    #include "stdafx.h"
    
    #include "Form1.h"
    #include "WndMessageHolder.h"
    
    using namespace BringUpTest;
    
    // Message handler for primary instance activation
    
    void Form1::WndProc(Message %msg)
    {
      WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
      if (msg.Msg == wmh->WM_BRINGUP) BringMeUp();
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISE) MessageEchoAdvertiseHandler(msg.WParam);
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE) MessageEchoAdvertisementResponseHandler(msg.WParam);
      __super::WndProc(msg);
    }
    
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      switch (m_ism) {
        case InstanceSwitchMethod::Classic:
          // "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();
    
          break;
    
        case InstanceSwitchMethod::Alternative:
          // Alternative version - works but unfortunately looks hackish...
    
          if (WindowState != FormWindowState::Minimized)
            WindowState = FormWindowState::Minimized;
          WindowState = FormWindowState::Normal;
          // Activate();
          break;
    
        default:
          Trace::Fail("Message Echo method and Deposited Handle method don't use the WM_BRINGUP message");
          break;
      }
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISE message - gets passed the non-primary instance main window handle
    
    void Form1::MessageEchoAdvertiseHandler(IntPtr ipHwndNonPrimary)
    {
      bool bResult = WndMessageHolder::PostMessage_(ipHwndNonPrimary,
        WndMessageHolder::GetInstance()->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE, Handle, (IntPtr)0);
      Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message failed (code {0})",
        WndMessageHolder::GetLastError_()));
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message - gets passed the primary instance main window handle
    
    void Form1::MessageEchoAdvertisementResponseHandler(IntPtr ipHwndPrimary)
    {
      bool bResult = WndMessageHolder::BringWindowToTop_(ipHwndPrimary);
      Trace::Assert(bResult, String::Format("Calling BringWindowToTop() on primary instance main window handle failed (code {0})",
        WndMessageHolder::GetLastError_()));
      tmrMessageEchoTimeout->Stop();
      Close();
    }
    
    // Event handlers to switch between the two instance switching modes
    
    System::Void Form1::rbClassic_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbClassic->Checked) m_ism = InstanceSwitchMethod::Classic;
    }
    
    System::Void Form1::rbAlt_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbAlt->Checked) m_ism = InstanceSwitchMethod::Alternative;
    }
    
    System::Void Form1::rbMessageEcho_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbMessageEcho->Checked) {
        if (!m_evtMessageEchoSignal) {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(true, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
        } else m_evtMessageEchoSignal->Set();
        m_ism = InstanceSwitchMethod::MessageEcho;
      } else m_evtMessageEchoSignal->Reset();
    }
    
    System::Void Form1::rbDepositedHandle_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbDepositedHandle->Checked) {
        Application::UserAppDataRegistry->SetValue("PrimaryWindowHandle", int(Handle));
        m_bRegistryEntryCreated = true;
        m_ism = InstanceSwitchMethod::DepositedHandle;
      } else RemoveRegistryEntry();
    }
    
    // Tear down our entire registry branch in order to leave zero registry footprint
    
    void Form1::RemoveRegistryEntry()
    {
      if (m_bRegistryEntryCreated) {
        RegistryKey ^rkCompany = Registry::CurrentUser->OpenSubKey("Software\\" + Application::CompanyName, true);
        rkCompany->DeleteSubKeyTree(Application::ProductName);
        if (rkCompany->SubKeyCount + rkCompany->ValueCount == 0) {  // Delete company key if it's empty now
          rkCompany->Close();
          Registry::CurrentUser->OpenSubKey("Software", true)->DeleteSubKey(Application::CompanyName);
        }
        m_bRegistryEntryCreated = false;
      }
    }
    
    System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e)
    {
      String ^strInstanceMutex = "{BE3A098C-D815-43e7-B8E2-5EC55CBC5306}";
    #ifdef _DEBUG
      strInstanceMutex += "_debug";
    #endif
    
      bool bIMTheOne;
      m_mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (!bIMTheOne) {
        WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
    
        // Don't use Application::UserAppDataRegistry here to avoid creating the key if it doesn't exist already:
    
        Object ^objRegValue = Registry::GetValue(String::Format("HKEY_CURRENT_USER\\Software\\{0}\\{1}\\{2}",
          Application::CompanyName, Application::ProductName, Application::ProductVersion), "PrimaryWindowHandle", nullptr);
        if (objRegValue) {
    
          // The registry value exists, so use the Deposited Handle method. Calling BringWindowToTop()
          // on the stored window handle from here doesn't report failure but has no effect either -
          // for unknown reasons. Posting a message to ourselves works but has no visible advantage
          // over the Message Echo method.
    
          bool bResult = WndMessageHolder::PostMessage_(this, wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE,
            IntPtr(safe_cast<int>(objRegValue)), (IntPtr)0);
          Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message to self failed (code {0})",
            WndMessageHolder::GetLastError_()));
    
        } else {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(false, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
    
          if (m_evtMessageEchoSignal->WaitOne(0)) {
    
            // The event isn't actually part of the Message Echo mechanism. It's just used in this demo
            // to signal the non-primary instance to use the Message Echo method.
    
            // Advertise our own main window handle:
    
            Trace::WriteLine("BringUpTest already running, sending WM_MESSAGEECHO_ADVERTISE message...");
            bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_MESSAGEECHO_ADVERTISE, Handle, (IntPtr)0);
            Trace::Assert(bResult, String::Format("Broadcasting WM_MESSAGEECHO_ADVERTISE message failed (code {0})",
              WndMessageHolder::GetLastError_()));
    
            // The primary instance is now supposed to respond with a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message
            // carrying its own main window handle
    
            // Make my own main window invisible and start timeout:
    
            Visible = false;
            tmrMessageEchoTimeout->Start();
    
          } else {
    
            Trace::WriteLine("BringUpTest already running, sending WM_BRINGUP message...");
            bool bResult = WndMessageHolder::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_()));
            Close();
    
          }
        }
      }
    }
    
    System::Void Form1::tmrMessageEchoTimeout_Tick(System::Object^  sender, System::EventArgs^  e)
    {
      // This handler actually should never get called
    
      Trace::Fail("BringUpTest: Message Echo mechanism timed out");
    }
    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.

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
  •  





Click Here to Expand Forum to Full Width

Featured