[C++] 자동 업데이트 프로그램

JeeHyeok Lee·2023년 1월 4일
0

c++를 활용해 자동 업데이트 프로그램을 만들어야 하는 일이 생겨 구현해보고자 한다.

필요로 하는 프로그램은 다음과 같다.

업데이트 해야하는 프로그램이 웹 서버 혹은 로컬 저장소에 업로드 되어있는 상황이라고 가정

기존 프로그램의 버전과 서버 혹은 저장소에 있는 동일한 프로그램의 버전을 비교하여 상위 버전인 경우 기존 프로그램을 상위 버전의 프로그램으로 덮어 씌운 뒤 프로그램을 재실행 한다.

C++는 주로 사용하는 언어가 아니기 때문에 처음부터 구현하지는 않고 구현되어 있는 자료를 참고하여 구현해보려고 한다.

참고자료

https://www.codeproject.com/Articles/1205548/An-efficient-way-for-automatic-updating

https://zadd.tistory.com/123

첫 번째 링크의 Micahel Haepahrati 라는 분이 작성하신 코드와 두 번째 링크의 블로거 분께서 작성해주신 코드 분석 내용을 참고하였다.

중요하다고 생각되는 부분의 코드만 참고하고 다뤄보았고 상세 코드의 경우 첫 번째 링크에서 다운받을 수 있다.

int _tmain(int argc, _TCHAR* argv[])
{
	AutoUpdate au;								
	au.CheckForUpdates();
	system("pause");
	return 0;
}

프로그램의 실행 파일이다.
가장 중요한 역할을 하는 AutoUpdate 클래스를 생성하고 클래스 내부 메서드인 CheckForupdates()를 실행해준다.

AutoUpdate::AutoUpdate()
{
	TCHAR szPath[MAX_PATH];
	// 프로그램 실행 시 경로를 불러온다.
	if (!GetModuleFileName(NULL, szPath, MAX_PATH))
	{ // 경로 탐색 실패 시 오류 메시지 반환
		wprintf(L"Can't find module file name (%s)\n", GetLastError());
		return;
	}
	// 경로와 이름 별도 저장
	SetSelfFullPath(szPath);
	SetSelfFileName(GetFileNameFromPath(szPath));
	// 프로그램 버전 확인
	SG_Version ver; // 구조체 변수
	if (SG_GetVersion(szPath, &ver))
	{
	    CString ModifiedFileName = szPath;
	    AddNextVersionToFileName(ModifiedFileName, ver);
	}
	//

	// 버전 업데이트 메서드
	ReplaceTempVersion();
	
}

AutoUpdate 클래스의 생성자이다. GetModuleFileName이라는 C++ 내부 라이브러리를 통해 현재 실행중인 프로그램의 경로를 불러온다.

불러온 경로를 프로그램의 이름과 분리하여 저장해준 뒤 프로그램의 버전을 확인한다.

버전의 경우 자체적으로 구현한 SG_Version이라는 구조체에 저장하게 되는데
네자리의 정수 형태로 구성되어 있다.

BOOL AutoUpdate::ReplaceTempVersion()
{
	int tries = 5;
	// 임시 파일 여부 확인, 파일 이름 앞 U 여부로 판단 
	if (m_SelfFileName.Left(3) == L"U")
	{
		tempversion = true;
		wprintf(L"We are running a temp version\n");
		retry:;
		// 기존 파일 삭제
		BOOL result = DeleteFile(m_SelfFileName.Mid(3));
		if (result)
		{
			wprintf(L"File '%s' deleted\n", m_SelfFileName.Mid(3));
			// 임시 파일을 복사하여 기존 파일명으로 변경
			BOOL result2 = CopyFile(m_SelfFileName, m_SelfFileName.Mid(3), FALSE);
			if (result2)
			{
				wprintf(L"File '%s' copied to '%s'\n", m_SelfFileName, m_SelfFileName.Mid(3));
				if (SG_Run(m_SelfFileName.Mid(3).GetBuffer()))
				{
					wprintf(L"Terminated %s\n",m_SelfFileName);
					_exit(0);
				}
        	}
    	}
    	else
    	{
        	if (--tries) goto retry;
        	wprintf(L"'original version' ('%s') can't be deleted or doesn't exists\n", m_SelfFileName.Mid(3));
    	}
	}
	else
	{
	    tempversion = false;
	    wprintf(L"We are running the normal version\n");
	    retry2:;
	    BOOL result = DeleteFile(L"_U_"+ m_SelfFileName); // 임시 파일 삭제
	    if (result)
	    {
	        wprintf(L"temp File '%s' deleted\n", L"_U_" + m_SelfFileName);
	    }
	    else
	    {
	        if (--tries) goto retry2;
	        wprintf(L"temp File '%s' can't be deleted or doesn't exist\n", L"_U_" + m_SelfFileName);
	    }
	
	}
	return TRUE;
}

생성자 종료지점에서 실행하게 되는 메서드인 ReplaceTempVersion()이다.

우선 현재 파일의 임시 파일 여부를 확인한다. 업데이트 파일을 다운받아오는 상황에서 해당 파일이 현재 실행 파일이 아닌 임시 파일임을 알아볼 수 있도록 파일 이름 앞에 U를 붙여 저장하게 된다.

실행된 파일이 임시 파일인 경우 기존의 파일을 삭제한 뒤 업데이트 파일인 임시 파일을 기존 파일명으로 변경한 뒤 저장해준다.

실행된 파일이 임시 파일이 아닌 경우 임시 파일을 삭제하게 된다.

BOOL AutoUpdate::CheckForUpdates(void)
{
	if (tempversion) return TRUE; // We don't check for updates if we are running a temp version
	MyCallback pCallback;
	// 업데이트 파일명은 U + 기존 파일명으로 설정
	CString ExeName = L"U" + m_SelfFileName;
	// 업데이트 파일 다운로드 경로
	CString URL = m_DownloadLink + m_NextVersion;
	wprintf(L"Next version will be %s\n", m_NextVersion);
	if (m_NextVersion == L"") return FALSE;
	wprintf(L"Looking for updates at %s\n", URL);
	// 캐시 초기화
	DeleteUrlCacheEntry(URL);
	HRESULT hr = 0;
	hr = URLDownloadToFile(
	NULL, // A pointer to the controlling IUnknown interface (not needed here)
	URL,
	ExeName,0, // Reserved. Must be set to 0.
	&pCallback); // status callback interface (not needed for basic use)
	if (SUCCEEDED(hr))
	{
	    // Check if the version string matches the file name on the server
	    SG_Version ver;
	    if (SG_GetVersion(ExeName.GetBuffer(), &ver))
	    {
	        if (SG_VersionMatch(m_NextVersion.GetBuffer(), &ver) == FALSE)
	        {
	            wprintf(L"Version string doesn't match actual version\n");
	            return FALSE;
	        }
	    }
	    wprintf(L"Downloaded file '%s' which is a newer version. Result = %u\n", m_NextVersion, hr);
	
	    if (SG_Run(ExeName.GetBuffer()))
	    {
	        wprintf(L"Successfully started the temp version (%s)\n", ExeName);
	        _exit(0);
	    }
	    else
	    {
	        wprintf(L"Couldn't start the temp version (%s)\n", ExeName);
	    }
	
	}
	else
    	wprintf(L"No new version (%s) on the server\n", m_NextVersion);
	return (hr)?TRUE:FALSE;
}

실행 파일에서 실행하게 되는 AutoUpdate 클래스의 메서드인 CheckForUpdates 메서드이다.

업데이트 할 파일의 경로를 저장한 뒤 URLDownLoadToFile() 라는 내부 라이브러리 함수를 통해 파일을 다운받게 된다.

업데이트 할 파일의 다운로드가 완료되면 해당 파일의 버전을 확인한 뒤 해당 파일을 실행하게 된다.

정리

업데이트 할 파일을 웹 서버를 통해 다운받은 뒤 기존 실행중인 프로그램을 대체하는 방식의 프로그램이다.

해당 프로그램을 조금 수정하여 웹 서버가 아닌 로컬 저장소를 통해 업데이트 파일을 저장받도록 수정하려고 한다. 또한 프로그램에서 이해가 되지 않는 부분들이 존재하여 새롭게 이해하게 되는 부분이 있으면 코드를 좀 더 수정해보려고 한다.

2개의 댓글

comment-user-thumbnail
2023년 3월 27일

안녕하세요! 분석글 잘 읽었습니다:) 다름이아니라 분석하신 동일한 소스를 사용해서 자동업데이트를 구현하려하는데 사용법을 잘 모르겠어서요..도움을 받을 수 있을지 여쭈어봅니다ㅜㅜ

1개의 답글