Coding Memo

Windows - 같은 프로그램 실행 방지 본문

etc

Windows - 같은 프로그램 실행 방지

minttea25 2024. 10. 1. 13:23

Windows 어플리케이션 개발 시, 하나의 어플리케이션의 실행만 허용해야할 때가 있을 것이다.

 

예를 들어, 

게임에서 클라이언트 다중 실행을 막거나,

서버에서 같은 프로그램이 동시에 실행되는 것을 방지하는 등이 있겠다.

 

Windows에서 동일한 응용 프로그램을 여러 번 실행하는 것을 방지하기 위한 방법에는 어떤 것들이 있는지 알아보자.

 


 

1. Mutex 이용

 

멀티 스레드 환경에서 (하나의 응용 프로그램 내에서) 동기화를 위해 mutex를 활용하듯이, 커널에서도 mutex를 사용할 수 있다. 이 mutex는 커널 오브젝트 (kernel object)로, 하나의 프로세스만 해당 자원에 접근 할 수 있도록 할 수 있다.

자세한 내용은 다음 글에!

https://minttea25.tistory.com/168

 

Windows - CreateMutex - 커널 모드 동기화

프로그램 내의 스레드 간 공유 자원에 대한 접근을 제한하는 것처럼 프로세스에서도 어떤 자원에 대해, 같은 응용 프로그램이 동시에 변경을 하거나, 작업을 하면 안되는 경우가 있다. 어떻게

minttea25.tistory.com

 

#include <iostream>
#include <windows.h>

int main()
{
	HANDLE hMutex = CreateMutex(NULL, TRUE, L"AppMutex");

	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		std::cout << "Application is already running!" << std::endl;
		return 1; // close application
	}

	std::cout << "Application is running now!" << std::endl;

	std::cin.get();

	// release mutex
	ReleaseMutex(hMutex);
	CloseHandle(hMutex);

	return 0;
}

 

위 코드에서 AppMutex의 이름의 커널 뮤텍스를 생성하고 이에 대한 접근 핸들을 얻는다.

만약 다른 프로세스에서 이 뮤텍스에 대한 접근을 하고 있다면, CreateMutex에 대한 에러로 ERROR_ALREADY_EXISTS가 나타날 것이다.

 

 

 

2. 윈도우 핸들 확인

단순히, 윈도우 창 클래스 이름을 사용하여, 같은 클래스 이름을 가진 다른 창이 있는지 확인하여, 프로그램을 종료 시킬 수 있다.

추가적으로 해당 응용 프로그램이 실행 중이라면, 그 창을 찾아 포커스를 할 수도 있다.

int main()
{
    HWND hwnd = FindWindow(NULL, L"WindowsApplication");

    if (hwnd != NULL)
    {
        // 기존 창을 앞으로 가져옴 (highlight)
        SetForegroundWindow(hwnd);
        exit(1);  // exit application
    }
    
    return 0;
}

 

hwnd가 NULL이면 "WindowsApplication"이란 이름의 창이 열려있지 않다는 의미이고, NULL이 아니라면, "WindowsApplication"이란 이름의 창이 실행 중이라는 의미이고, 해당 창에 대한 핸들을 얻는다.

 

이 방법은 콘솔 어플리케이션이 아닌, 윈도우 어플리케이션에서 사용할 수 있는 방법이 되겠다.

 

 

 

3. 파일 기반의 락(lock) 이용

 

lock을 기반으로 동시 접근 제어하는 방식을 이용한다.

락 파일을 생성하고 해당 파일을 쓰기 가능한 형태로 열 수 있는지 확인한다. 만약 이미 락 파일이 존재하거나 잠금 상태에 있을 경우에는 프로그램 실행을 막으면 된다.

Widnows에서 lock과 관련된 API를 제공한다.

Windows 뿐만 아니라 다른 OS에서도 사용 가능한 방법이 되겠다.

bool LockFile(const std::wstring& lockFilePath)
{
    // Create or open the lock file
    HANDLE hFile = CreateFileW(lockFilePath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        std::cerr << "Failed to create or open lock file!" << std::endl;
        return false;
    }

    // Try to lock the file (exclusive lock)
    OVERLAPPED overlapped = {};
    if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped))
    {
        std::cerr << "Failed to lock the file!" << std::endl;
        CloseHandle(hFile);
        return false;
    }

    std::cout << "File is locked." << std::endl;

    // Keep the file open to maintain the lock
    std::cin.get();

    // Unlock and close the file
    UnlockFileEx(hFile, 0, MAXDWORD, MAXDWORD, &overlapped);
    CloseHandle(hFile);
    std::cout << "File is unlocked." << std::endl;

    return true;
}


int main()
{
    auto lockFilePath = L"lockfile.lock";
    if (!LockFile(lockFilePath))
    {
        std::cerr << "Another instance is already running!" << std::endl;
        return 1;
    }

    std::cout << "Application is running now." << std::endl;
    std::cin.get();

    return 0;
}

 

간단하게 사용된 함수들을 살펴보면,

CreateFileW함수는 파일이나 디바이스, 파이프 등의 리소스를 열거나 생성할 때 사용하는 Windows API 함수이다.

CreateFileW
LPCWSTR lpFileName 열거나 생성할 파일 이름
DWORD dwDesiredAccess 접근 권한 (r/w)
DWORD dwShardMode 공유 모드
LPSECURITY_ATTRIBUTES lpSecurityAttributes 보안 속성 구조체
DWORD dwCreationDisposition 파일이 존재하지 않을 경우 생성 여부
DWORD dwFlagAndAttributes 파일 속성 플래그
HANDLE hTemplateFile 템플릿 파일 핸들

 

dwSharedMode를 0으로 설정하면 삭제, 읽기 또는 쓰기 액세스를 요청하는 경우, 이후 해당 파일에 대한 열기 작업을 방지한다.

 

LockFileEx는 호출한 프로세스의 베타적 접근(exclusive access)를 위해 지정된 파일을 lock하는 함수이다. 단순 lock 함수라고 비유해도 될 것 같다.

첫 번째 인자로 파일 핸들을 받는데, 이 핸들은 GENERIC_READ나 GENERIC_WRITE 권한으로 만들어진 핸들어이야 된다.

두 번째 인자로 LOCKFILE_EXCLUSIVE_LOCK을 넣어, 파일에 대한 lock을 얻도록 한다.

네 번째와 다섯 번째 인자로 잠글 바이트수를 각각 하위 32비트와 상위 32비트 값으로 받는데, 파일에 대한 독점 권한을 가지고 있을 것이므로, MAXWORD를 사용하여 전체를 잠궈준다.

 

마지막으로 UnlockFileEx를 이용하여 파일 잠금을 해제한다.

 

이 방법의 한 가지 유의할 점은 어플리케이션이 비정상적으로 종료되면, 해당 파일이 여전히 잠겨있을 수 있고, 삭제 작업이 있을 경우, 이 작업이 제대로 실행되지 않을 수 있다. 이 경우에는 수동으로 컨트롤해줘야 할 수도 있다.

 

 

 

4. 프로세스 리스트 확인

이것도 정말 단순하게 실행 중인 프로세스 리스트를 확인하여 동일한 응용 프로그램이 실행되고 있는지 확인할 수 있는 방법이다. 

CreateToolhelp32Snapshot 함수를 통해, 현재 운영체제에서 실행중인 모든 프로세스에 대한 스냅샷을 얻고 Process32First와 ProcessNext 함수로 순차적으로 탐색하여, 프로세스 이름(e.g. Blackboard.exe)을 비교하는 방식이다.

설명만 봐도 비효율적인 코드로 보인다. 스냅샷을 가져와야 되는 것도 그렇고, 모든 프로세스를 순차적으로 탐색 및 비교를 하다니!

더보기
bool IsAlreadyRunning(const wchar_t* processName)
{
    // Create snapshot
    // Note: Access of the snapshots is read-only
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        std::cerr << "Failed to create process snapshot!" << std::endl;
        return false;
    }

    PROCESSENTRY32 pe = {};
    pe.dwSize = sizeof(PROCESSENTRY32);

    // Check first process
    if (!Process32First(hSnapshot, &pe))
    {
        CloseHandle(hSnapshot);
        return false;
    }
    
    do
    {
        // 주어진 이름과 프로세스 이름 비교 (대소문자 구분 없이: _wscicmp 이용)
        if (_wcsicmp(pe.szExeFile, processName) == 0)
        {
            CloseHandle(hSnapshot);
            return true;
        }
    } while (Process32Next(hSnapshot, &pe));

    CloseHandle(hSnapshot);
    return false;
}

단순 프로세스 내에서 스레드간 동기화만 가능한 것이 아니라, 프로세스 단위로도 lock이나 mutex를 이용해 동기화를 할 수 있다.

윈도우 창에서는 열려있는 창의 이름을 통해 실행중인 프로그램 인스턴스를 확인할 수도 있고, 현재 실행중인 프로세스들의 스냅샷을 얻어 하나하나 비교를 할 수도 있다.

 

윈도우 어플리케이션의 경우 두 번째 윈도우 창을 이용하는 방법을 사용하여 하이라이트까지 적용할 수 있을 것 같고, 그외 에는 단순히 첫 번째 방법으로 커널 오브젝트인 mutex를 사용하면 될 것 같다.

마지막으로 네트워크 상에서도 사용가능한 세 번째 방법으로, 리소스 파일에 대한 접근을 컨트롤 할 수 있을 것 같다.


Reference

MSDN

https://learn.microsoft.com/ko-kr/windows/win32/api/winuser/nf-winuser-findwindoww

 

FindWindowW 함수(winuser.h) - Win32 apps

클래스 이름과 창 이름이 지정된 문자열과 일치하는 최상위 창에 대한 핸들을 검색합니다. 이 함수는 자식 창을 검색하지 않습니다. 이 함수는 대/소문자를 구분하는 검색을 수행하지 않습니다.

learn.microsoft.com

 

https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-createfilew

 

CreateFileW 함수(fileapi.h) - Win32 apps

파일 또는 I/O 디바이스를 만들거나 엽니다. 가장 일반적으로 사용되는 I/O 디바이스는 다음과 같습니다. \_file, 파일 스트림, 디렉터리, 실제 디스크, 볼륨, 콘솔 버퍼, 테이프 드라이브, 통신 리소

learn.microsoft.com

 

https://learn.microsoft.com/ko-kr/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot

 

CreateToolhelp32Snapshot 함수(tlhelp32.h) - Win32 apps

지정된 프로세스의 스냅샷 이러한 프로세스에서 사용되는 힙, 모듈 및 스레드를 사용합니다.

learn.microsoft.com

ChatGPT

Some codes are generated by ChatGPT.

 

 

'etc' 카테고리의 다른 글

Windows - CreateMutex - 커널 모드 동기화  (0) 2024.09.27
Shuffle 알고리즘 (Fisher-Yates Shuffle, Knuth Shuffle)  (0) 2024.09.26
[Linux] 리눅스 파일 유형  (0) 2024.02.29
패킷 암호화(비트연산, AES)  (0) 2023.12.14
C++ vs. C# ??  (0) 2023.10.13