우리는 보통 프로그램 중복 실행을 방지한다고 표현하는데 외국에서는 중복 인스턴스 실행을 방지한다고 표현하나보다. 여기저기 뒤진 끝에 외국 소스를 찾았는데 싱글 인스턴스를 유지하는 방법이라고 써 있었다.
아무튼 프로그램 중복 실행을 방지한다는 것은 뭐 다 알다시피 메모장을 실행시킨 후 다시 메모장을 실행시켜도 이미 열려있는 메모장이 있으면 또 열리지 않는다는 것을 의미한다.
처음에 데브피아나 여러 곳에서 중복실행 방지에 대해서 찾았었는데, 뮤텍스 방법 위주로만 나오길래 무슨 방법인가 했다. 예전에는 프로그램명으로 검색해서 같은게 떠있으면 두 번째 실행을 방지하는 방법을 썼었는데 말이다.
아무튼 뮤텍스가 뭔가 해서 대충 찾아보니(일과시간에 잠깐 짬내서 찾느라고 자세하게는 알아보지 못했지만) 프로그램의 실행상태를 시스템에 알려주고 같은 프로그램을 실행시키면 시스템에 물어봐서 그 프로그램이 실행중인지 따져보고 실행할 지 안할지 판단하는 방법이라고 한다.
그러나 이 방법은 프로그램이 종료될 때 반드시 시스템에 프로그램이 실행중이었다는 사실을 해제해야 하는 것 같았다. 그렇다면 비정상 종료시에는 시스템에 프로그램이 종료되었다는 사실을 알려주지 못할텐데 그 때는 문제가 생길거 같아서 이 방법은 일단 배제하기로 했다.
(다시 알아보니 시스템 커널이 관리하는 뮤텍스라는 놈이 있는데 그놈은 공유자원을 관리하는 놈이란다. 그래서 공유자원을 다 썼으면 반드시 뮤텍스에게 다 썼다고 알려줘야 하는것이다.)
역시 예전에 사용하던 방법이 좋은거같다.
그렇다면 작업관리자에 들어가 있는 실행파일명을 가지고 판단할 것인가, 아니면 작업표시줄에서 보여지는 프로그램 이름(캡션명)을 사용 할 것인가를 정해야 한다.
그러다가 찾아낸 소스가 작업표시줄에 보여지는 이름을 가지고 판단하는 것이었으며 이 소스는 중복 프로그램 실행 시 이미 실행중이 프로그램이 최소화 상태로 있던가 뒤에 숨어있던가 하면 앞으로 꺼내주는 역할까지 해준다. 마침 내가 원하는 소스였다.
소스를 처음 만든 사람의 뜻을 존중해서 그 사람이 사용한 변수명이나 클래스명은 고치지 않았으며 출처도 안에 그대로 두었다.(ㅋㅋ 사실은 귀찮아서 그냥 그대로 사용한건데..)
이렇게 보니 사설이 너무 길었다.
바로 소스 설명에 들어가자면..
ProcessChecker.cs라는 클래스 파일을 하나 만들어서 그 안에서 이미 실행중인 프로그램의 캡션을 조사하여 이미 실행중인지 아닌지 여부를 알려준다.
C# 윈폼 프로그램의 시작점인 Program.cs의 Main에서 ProcessChecker.cs에게 xxx프로그램이 실행중인지 물어보고 실행할지 말지 판단하도록 한다.
먼저 ProcessChecker.cs파일의 소스를 보자면,
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace SISMonitor { /// <SUMMARY> /// Check running processes for an already-running instance. Implements a simple and /// always effective algorithm to find currently running processes with a main window /// matching a given substring and focus it. /// Combines code written by Lion Shi (MS) and Sam Allen. /// /// 사용법:Program.cs파일의 다음 부분에 아래와 같이 소스를 수정한다. /// "Program Window Text"은 폼의 타이틀 이름이다. /// (띄어쓰기가 있는 /// [STAThread] /// static void Main() /// { /// if (ProcessChecker.IsOnlyProcess("Program Window Text")) /// { /// Application.EnableVisualStyles(); /// Application.SetCompatibleTextRenderingDefault(false); /// Application.Run(new TextWindow()); /// } /// } /// /// 출처:http://dotnetperls.com/single-instance-windows-form /// </SUMMARY> static class ProcessChecker { /// <SUMMARY> /// 찾아야 할 캡션 /// </SUMMARY> static string _requiredString; /// <SUMMARY> /// Contains signatures for C++ DLLs using interop. /// </SUMMARY> internal static class NativeMethods { /// <SUMMARY> /// 현재 실행중인 윈도우의 상태를 보여준다. /// </SUMMARY> /// <PARAM name="hWnd"></PARAM> /// <PARAM name="nCmdShow"></PARAM> /// <RETURNS></RETURNS> [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); /// <SUMMARY> /// 선택한 윈도우를 뒤에 숨어있었으면 앞으로, 최소화상태였으면 원래상태로 되돌려놓으며 활성화시킨다. /// </SUMMARY> /// <PARAM name="hWnd"></PARAM> /// <RETURNS></RETURNS> [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); /// <SUMMARY> /// EnumWindows 함수는 모든 최상위 윈도우를 검색해서 그 핸들을 콜백함수로 전달하되 /// 모든 윈도우를 다 찾거나 콜백함수가 FALSE를 리턴할 때까지 검색을 계속한다. /// 콜백함수는 검색된 윈도우의 핸들을 전달받으므로 모든 윈도우에 대해 모든 작업을 다 할 수 있다. /// EnumWindows 함수는 차일드 윈도우는 검색에서 제외된다. /// 단 시스템이 생성한 일부 최상위 윈도우는 WS_CHILD 스타일을 가지고 있더라도 예외적으로 검색에 포함된다. /// </SUMMARY> /// <PARAM name="lpEnumFunc">EnumWindows의 실행 결과를 받아줄 콜백함수이다. /// EnumWindows는 이 함수 결과가 false가 될 때까지 계속 윈도우를 검색하게 된다.</PARAM> /// <PARAM name="lParam"></PARAM> /// <RETURNS></RETURNS> [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProcDel lpEnumFunc, Int32 lParam); /// <SUMMARY> /// HWND 값을 이용하여 프로세스 ID를 알려주는 함수이다. /// </SUMMARY> /// <PARAM name="hWnd"></PARAM> /// <PARAM name="lpdwProcessId"></PARAM> /// <RETURNS></RETURNS> [DllImport("user32.dll")] public static extern int GetWindowThreadProcessId(IntPtr hWnd, ref Int32 lpdwProcessId); /// <SUMMARY> /// 윈도우의 캡션을 가져온다. /// </SUMMARY> /// <PARAM name="hWnd"></PARAM> /// <PARAM name="lpString"></PARAM> /// <PARAM name="nMaxCount"></PARAM> /// <RETURNS></RETURNS> [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, Int32 nMaxCount); //윈도우의 상태를 normal로 하게 하는 상수 public const int SW_SHOWNORMAL = 1; } /// <SUMMARY> /// EnumWindows의 실행 결과를 받아줄 콜백함수이다. /// EnumWindows는 이 함수 결과가 false가 될 때까지 계속 윈도우를 검색하게 된다. /// </SUMMARY> /// <PARAM name="hWnd"></PARAM> /// <PARAM name="lParam"></PARAM> /// <RETURNS></RETURNS> public delegate bool EnumWindowsProcDel(IntPtr hWnd, Int32 lParam); /// <SUMMARY> /// Perform finding and showing of running window. /// 모든 실행중인 윈도우를 검색하며 찾고자 하는 캡션의 윈도우를 발견하면 활성화시킨다. /// </SUMMARY> /// <RETURNS>Bool, which is important and must be kept to match up /// with system call.</RETURNS> static private bool EnumWindowsProc(IntPtr hWnd, Int32 lParam) { int processId = 0; NativeMethods.GetWindowThreadProcessId(hWnd, ref processId); StringBuilder caption = new StringBuilder(1024); NativeMethods.GetWindowText(hWnd, caption, 1024); //방금 검색한 윈도우의 캡션을 가져온다. //찾을 윈도우명과 가져온 캡션이 일치한다면, if (processId == lParam && (caption.ToString().IndexOf(_requiredString, StringComparison.OrdinalIgnoreCase) != -1)) { //윈도우를 normal 상태로 바꾸고 제일 앞으로 가져온다. NativeMethods.ShowWindowAsync(hWnd, NativeMethods.SW_SHOWNORMAL); NativeMethods.SetForegroundWindow(hWnd); } return true; //왜 계속 true만 반환해야 할까??? } /// <SUMMARY> /// 지금 실행하려는 프로그램이 이미 실행중인지 아닌지 찾아보고 결과를 알려준다. /// </SUMMARY> /// <PARAM name="forceTitle">찾으려는 윈도우의 캡션, 즉 프로그램 타이틀</PARAM> /// <RETURNS>해당 캡션의 윈도우가 이미 실행중이라면 False, /// 처음 실행하는 것이라면 True를 반환한다.</RETURNS> static public bool IsOnlyProcess(string forceTitle) { _requiredString = forceTitle; //먼저 실행파일의 이름으로 이름이 같은 프로세스를 검색해본다. foreach (Process proc in Process.GetProcessesByName(Application.ProductName)) { if (proc.Id != Process.GetCurrentProcess().Id) { NativeMethods.EnumWindows(new EnumWindowsProcDel(EnumWindowsProc), proc.Id); return false; } } return true; } } }아래의 첨부파일의 확장자를 cs로 고친 후 프로그램에 적용시키면 된다.
그리고 소스의 앞부분 주석에 써 놓은 것처럼 C#프로그램의 진입점인 Program.cs 파일에
[STAThread] static void Main() { if (ProcessChecker.IsOnlyProcess("Program Window Text")) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new TextWindow()); } }
이렇게 소스를 넣으면 잘 작동된다.