CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 15 of 20

Threaded 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.

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