Feedback

C# - Windows Forms Anwendungs-Sessions verwalten

Veröffentlicht von am 16.11.2009
(1 Bewertungen)
Diese Klasse hilft bei der Verwaltung von Anwendungssitzungen.

Es ist möglich zu prüfen, welche Sitzungen im System bereits laufen und diese zu benachrichtigen. Bei der Benachrichtigung kann ein Objekt an den Prozess gesendet werden (z.B. Kommandozeilenparameter)


Funktionen
- Starten eines Prozesses mit einer benannten Session
- Herunterfahren der laufenden Session
- Herausfinden, ob eine Session mit dem gewünschten Namen bereits existiert
- Benachrichtigung der "alten" Session
- Übermittlung von Startup-Parametern an die "alte" Session (z.B. CommandLine)
- Auflisten aller laufenden Sessions


Funktionen der Klasse (alle static)

void SessionStart(string sessionName)
void SessionShutdown()
bool SessionIsActive(string sessionName)
bool NotifyActiveSession(string sessionName, object attachment)
List<string> GetAllActiveSessionNames()

event SessionNotifiedEventHandler Notified


Beispielszenario - SingleInstance:
Die Anwendung soll nur einmal gestartet werden können.

Wird sie nochmal gestartet, benachrichtigt der neue Prozess die "alte" Sitzung, übermittelt dabei seine Kommandozeilenparameter und wird dann beendet.

Die neue Sitzung empfängt die Daten und kann darauf reagieren (z.B. ein neues Fenster im eigenen Prozess öffnen).
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

namespace Sessions
{
    public delegate void SessionNotifiedEventHandler(object objectReceivedFromCallingProcess);
    public class SessionManager : IDisposable
    {
        #region Private fields
        /// <summary>
        /// Name of the active session
        /// </summary>
        private string _sessionName;

        /// <summary>
        /// String to identify the current assembly
        /// This string will be attached to the session window name
        /// to make it unique. This id could also be used to identify
        /// groups of sessions.
        /// </summary>
        private readonly string _appendToSessionNametoEnsureItsUnique;

        /// <summary>
        /// The native Window to communicate across processes using 
        /// WM_COPYDATA
        /// </summary>
        private CommunicationWindow _communicator;
        #endregion

        #region Singleton implementation - This object exists only once per process
        /// <summary>
        /// Holds SessionManager instance
        /// </summary>
        private static SessionManager _instance;

        /// <summary>
        /// Private Constructor to allow initialization only from within this class
        /// </summary>
        private SessionManager()
        {
            _sessionName = "";

            // This could be any string, I use the Assembly GUID here.
            // It will be trimmed from the window title later to read the session name
            _appendToSessionNametoEnsureItsUnique = GetAssemblyGUID();
        }

        /// <summary>
        /// Creates the SessionManager Instance if required
        /// and returns it (or the existing one)
        /// </summary>
        private static SessionManager Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new SessionManager();

                return _instance;
            }
        }
        #endregion

        #region Methods - These methods will be used by the static Interface methods
        /// <summary>
        /// Starts a new Session with the given name 
        /// by creating a (hidden) session window
        /// </summary>
        /// <param name="sessionName">Name of the session</param>
        private void StartNew(string sessionName)
        {
            _sessionName = sessionName;

            // Initialize the Native Window
            // The window will be invisibly running in background, only to be found by the findwindow method
            _communicator = new CommunicationWindow(_sessionName + _appendToSessionNametoEnsureItsUnique);
        }

        /// <summary>
        /// Shuts the session down by closing and destroying the native window
        /// </summary>
        private void Shutdown()
        {
            if (_communicator != null)
                _communicator.DestroyHandle();
        }

        /// <summary>
        /// Searches for the Session Window
        /// </summary>
        /// <param name="sessionName"></param>
        /// <returns></returns>
        private bool IsActive(string sessionName)
        {
            IntPtr handle = FindWindow(IntPtr.Zero, sessionName + _appendToSessionNametoEnsureItsUnique);
            if (handle != IntPtr.Zero)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Notifies another session about the launch of a new process.
        /// The target session should now act as it was called. 
        /// The calling Session will exit immediately after calling.
        /// </summary>
        /// <param name="sessionName">Name of the session to send the notification to</param>
        /// <param name="attachment">Attachment to be contained within the message. Usually command line string</param>
        /// <returns>true if the message was sent, false if the target session was not found</returns>
        private bool NotifySession(string sessionName, object attachment)
        {
            // Search for the window of the session to notify
            IntPtr hWnd = FindWindow(IntPtr.Zero, sessionName + _appendToSessionNametoEnsureItsUnique);
            if (hWnd != IntPtr.Zero)
            {
                // This is the Trick! Use a pinned handle to have a global pointer to the byte array.
                // The pinned pointer allows us to access the memory location from another process
                GCHandle bufferHandle = new GCHandle();
                try
                {
                    byte[] buffer;
                    COPYDATASTRUCT dataStructForMessage = new COPYDATASTRUCT();
                    if (attachment != null)
                    {
                        buffer = MakeBytes(attachment);
                        bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); // here we pin the buffer

                        dataStructForMessage.dwData = 0;
                        dataStructForMessage.cbData = buffer.Length;
                        //get the address of the pinned buffer
                        dataStructForMessage.lpData = bufferHandle.AddrOfPinnedObject();
                    }

                    GCHandle dataStructHandle = GCHandle.Alloc(dataStructForMessage, GCHandleType.Pinned); // the COPYDATASTRUCT must also be pinned!!
                    try
                    {
                        // Send the message to the target process and attach the structure
                        SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, dataStructHandle.AddrOfPinnedObject());
                        return true;
                    }
                    finally
                    {
                        dataStructHandle.Free();  // Free the handle to let the memory cleanup do its magic!
                    }
                }
                finally
                {
                    if (bufferHandle.IsAllocated)
                        bufferHandle.Free(); // Free second handle too!
                }
            }
            return false;
        }

        /// <summary>
        /// Enumerates all Windows of the system and returns a list of those
        /// ending with the string defined in _appendToSessionNametoEnsureItsUnique
        /// (without _appendToSessionNametoEnsureItsUnique)
        /// </summary>
        /// <returns>List of Session Names</returns>
        private List<string> GetListOfSessionWidows()
        {
            List<string> windowNames = new List<string>();

            // Enumeration with nested delegate function for API callback
            EnumWindows(delegate(IntPtr hWnd, IntPtr lParam)
            {
                string windowText = GetWindowText(hWnd);
                if (windowText.EndsWith(_appendToSessionNametoEnsureItsUnique))
                    windowNames.Add(windowText.Replace(_appendToSessionNametoEnsureItsUnique, ""));
                return 1;
            }, new IntPtr(0));

            return windowNames;
        }
        #endregion

        #region Static Interface Methods - Use these to call from the outside

        /// <summary>
        /// Occurs when another process was launched targetting the same session name.
        /// The other process will exit after this event was handled.
        /// </summary>
        public static event SessionNotifiedEventHandler Notified;


        /// <summary>
        /// Starts the Session.
        /// </summary>
        /// <param name="sessionName">Name of the session.</param>
        public static void SessionStart(string sessionName)
        {
            Instance.StartNew(sessionName);
        }

        /// <summary>
        /// Shuts the Session down.
        /// </summary>
        public static void SessionShutdown()
        {
            Instance.Shutdown();
        }

        /// <summary>
        /// Determinse if a session with the given name is already active.
        /// </summary>
        /// <param name="sessionName">Name of the session.</param>
        /// <returns>true if a session name is already used by another session</returns>
        public static bool SessionIsActice(string sessionName)
        {
            return Instance.IsActive(sessionName);
        }


        /// <summary>
        /// Notifies the active session and attaches an object to be sent.
        /// </summary>
        /// <param name="sessionName">Name of the session.</param>
        /// <param name="attachment">The attachment.</param>
        /// <returns>true if the message was sent, false if the target session was not found</returns>
        public static bool NotifyActiveSession(string sessionName, object attachment)
        {
            return Instance.NotifySession(sessionName, attachment);
        }

        /// <summary>
        /// Gets all active session names.
        /// </summary>
        /// <returns>List of names of running sessions</returns>
        public static List<string> GetAllActiveSessionNames()
        {
            return Instance.GetListOfSessionWidows();
        }
        #endregion

        #region Windows API Methods
        [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
        static extern IntPtr FindWindow(IntPtr zeroOnly, string lpWindowName);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

        private delegate int EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern IntPtr SendMessage(IntPtr hWnd, int nMsg, IntPtr wParam, IntPtr lParam);

        const short WM_COPYDATA = 74;

        struct COPYDATASTRUCT
        {
            public int dwData;
            public int cbData;
            public IntPtr lpData;
        }
        #endregion

        #region NativeWindow used to send and receive Inter Process Messages
        /// <summary>
        /// The CommunicationWindow class is used to identify the current session
        /// from any process by using FindWindow. The Window Name will be the 
        /// Session id followed by a GUID to make sure the window name is unique.
        /// </summary>
        private sealed class CommunicationWindow : NativeWindow
        {
            public CommunicationWindow(string sessionname)
            {
                CreateParams cp = new CreateParams();
                cp.Caption = sessionname;
                CreateHandle(cp);
            }

            /// <summary>
            /// Override WndProc to receive Messages from other Processes
            /// 
            /// Messages to catch are WM_COPYDATA messages. the lParam Value 
            /// points to a structure containing the commandline used to open the session 
            /// </summary>
            /// <param name="m">The Message received</param>
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_COPYDATA)
                {
                    // Get the Copydata structure from the pointer in lParam
                    COPYDATASTRUCT dataStructFromMessage = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));

                    // This will be the object encapsulated in the message
                    object receivedObject = null;

                    if (dataStructFromMessage.cbData > 0 && dataStructFromMessage.lpData != IntPtr.Zero)
                    {
                        // Copy the bytes in the struct to our local buffer
                        byte[] buffer = new byte[dataStructFromMessage.cbData];
                        Marshal.Copy(dataStructFromMessage.lpData, buffer, 0, buffer.Length);

                        //deserialize the buffer to a new object
                        receivedObject = MakeObject(buffer);
                    }

                    // Raise event
                    if (Notified != null) // if evenHandlers are connected
                        Notified(receivedObject);
                }
                else
                    base.WndProc(ref m);
            }
        }
        #endregion

        #region Tool functions
        /// <summary>
        /// Determines the GUID of the current assembly
        /// </summary>
        /// <returns>GUID as string</returns>
        private string GetAssemblyGUID()
        {
            string id = "";
            foreach (object attr in Assembly.GetExecutingAssembly().GetCustomAttributes(true))
            {
                if (attr is GuidAttribute)
                    id = ((GuidAttribute)attr).Value;
            }
            return id;
        }

        /// <summary>
        /// Gets the window text.
        /// </summary>
        /// <param name="hWnd">The handle to the window.</param>
        /// <returns>Caption of the window</returns>
        private string GetWindowText(IntPtr hWnd)
        {
            StringBuilder sb = new StringBuilder(GetWindowTextLength(hWnd) + 1);
            GetWindowText(hWnd, sb, sb.Capacity);
            return sb.ToString();
        }



        /// <summary>
        /// Deserializes an object in memory.
        /// </summary>
        /// <param name="buffer">Byte array of the object received from an external process</param>
        /// <returns>Deserialized object. Requires casting into its correct type.</returns>
        private static object MakeObject(byte[] buffer)
        {
            using (MemoryStream stream = new MemoryStream(buffer))
            {
                return new BinaryFormatter().Deserialize(stream);
            }
        }

        /// <summary>
        /// Serializes an object in memory into a byte array. 
        /// This can be used to attach any object to the COPYDATASTRUCT
        /// structure and send it to another process.the receivubg process
        /// must know which type to expect to make this work.
        /// </summary>
        /// <param name="obj">An object to serialize</param>
        /// <returns>A byte array containing the object.</returns>
        private static byte[] MakeBytes(Object obj)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                new BinaryFormatter().Serialize(stream, obj);
                return stream.ToArray();
            }
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Shutdown();
        }

        #endregion
    }
}
Abgelegt unter C#, Process, Assembly, Session.

Kommentare zum Snippet

 

Logge dich ein, um hier zu kommentieren!