티스토리 뷰

Visual Studio 2012 사용

프로그램 실행 시 자동으로 최신 프로그램으로 업데이트 하는 기능을 활용해보자.

정보의 시대에 처음부터 짤 필요는 없다.
우리의 선생님이신 코프 선생님의 지도를 받기로 한다.

참고 자료 코프 Michael Haephrati 선생님

글 작성 하면서 알게 됐는데, 2017에 코프에 베스트 C++ Article 로 상도 타셨네..

해당 아티클에서 자료를 다운 받으면 한 프로젝트가 나온다.
여기서 우리가 분석할 중요한 소스는 AutoUpdate.cpp와 AutoUpdate.h이다.

분석을 시작해보자.

프로젝트를 열었을 때 SG_AutoUpdate.cpp를 열어서 아래 함수를 검토한다.
왜냐하면, 프로젝트 실행 시 진행되는 함수기 때문에..

int _tmain(int argc, _TCHAR* argv[])
{
	...
	AutoUpdate au; // For auto updating
	au.CheckForUpdates();
	...
}

다른 부분은 볼 필요가 없다.
중요한건 위의 내용 처럼 AutoUpdate 클래스 au를 불러온 후에
해당클래스의 CheckForUpdate()라는 함수를 불러 온다는 내용만 보면 된다.

우선 AutoUpdate 클래스를 확인해보자.
au라는 변수를 만들었으니 생성자에 무엇인가 있는지 먼저 검토해보자.

비쥬얼 스튜디오를 활용하고 있다면
F12키는 해당 함수의 정의로 넘겨 주는 중요한 기능을 담당한다.
해당 함수, 클래스 클릭 후 F12키로 정의되어 있는 코드로 슝 넘어가보자.

AutoUpdate::AutoUpdate()
{
	TCHAR szPath[MAX_PATH];
	//a.
	if (!GetModuleFileName(NULL, szPath, MAX_PATH))
	{
	wprintf(L"Can't find module file name (%s)\n", GetLastError());
	return;
	}
	SetSelfFullPath(szPath);
	SetSelfFileName(GetFileNameFromPath(szPath));
	//a end
	//b.
	SG_Version ver;
	if (SG_GetVersion(szPath, &ver))
	{
	    CString ModifiedFileName = szPath;
	    AddNextVersionToFileName(ModifiedFileName, ver);
	}
	//b end

	//c.
	ReplaceTempVersion();
	//c end
}

 

TCHAR는 해당 프로젝트가 유니코드로 이루어져 있어 char 대신 활용한 것이라 생각한다.

소스에 명시한 파트 별로 분석해보자 //a. -> a파트 등..

a.
a파트는 프로그램 실행 시 프로그램의 경로를 가져오는 부분이다.
GetModuleFileName() 함수를 통해 szPath에 프로그램 전체 경로를 넣고,
SetSelfFullPath(), SetSelfFileName()함수를 통해
전체 경로와 파일 이름만 따로 분리하여 저장하는 것을 알 수 있다.

b.
b파트는 현재 프로그램 버전을 확인하는 부분이다.
MFC 프로그램 버전은 0.0.0.0 으로 4가지의 숫자가 사용되어 지는데
이를 구분할 수 있게 SG_Version이라는 구조체를 사용했다.

typedef struct
{
	int Major;
	int Minor;
	int Revision;
	int SubRevision;
} SG_Version;

SG_GetVersion() 함수를 통해 현재 프로그램의 버전을 ver라는 구조체에 저장한 후
AddNextVersionToFileName() 함수를 통해 다음 프로그램 버전의 이름을 생성해 둔다.
Ex) 현재 프로그램 이름(A.exe/버전 1.0.0.0)
AddNextVersionToFileName() 후 다음 프로그램 이름 -> A.1.0.0.1.exe

c.
c파트는 함수가 한개지만 중요해서 따로 빼놨다.
ReplaceTempVersion() 함수를 확인해보자. 이 함수는 현재 실행된 프로그램의 이름을 확인하여 임시 파일인지 아닌지 검토한 후 그에 맞게 처리하는 함수다.


BOOL AutoUpdate::ReplaceTempVersion()
{
	int tries = 5;
	//1.
	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));
    	}
	}
	//1 end
	//2.
	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);
	    }
	
	}
	//2 end
	return TRUE;
}

소스가 좀 길어서 이것 또한 2파트로 나눠보자.

  1. 1번 파트는 임시 파일이 실행된 경우의 대한 절차과정이다. (파일 이름 앞에 "U" 자가 붙었으면 임시로 판단)
    임시 파일이라고 판단되면 기존 프로그램을 DeleteFile() 함수로 제거하고,
    CopyFile() 함수로 임시파일을 기존 프로그램 파일명으로 변경하여 복사한 후,
    SG_Run() 함수로 기존 프로그램 파일 명을 실행시키면서 프로그램을 종료한다.

Ex) 파일이름 UA.exe 가 현재 프로그램인 경우, 기존 A.exe 파일을 지우고, UA.exe 파일을 A.exe 파일명으로 복사하여 삽입 후, A.exe 파일을 실행 하고 현재 프로그램을 종료한다.

  1. 2번 파트는 1번 파트와 반대로, 정상 파일이 실행된 경우이다.
    DeleteFile() 함수로 임시파일을 제거하는 루틴이다.

Ex) 파일이름 A.exe 가 현재 프로그램인 경우, UA.exe 라는 임시 파일을 제거한다.

위의 1,2 파트를 확인해보면
새로운 버전인 프로그램을 임시 파일로 생성한 후, 해당 임시파일을 기존 프로그램 이름으로 바꿔치기 하는 루틴임을 알 수 있다.

--------- 여기까지가 생성자 함수 분석 ----------

지금까지가 AutoUpdate au; 를 선언 했을 때 진행되는 과정이였다.
이후에 au.CheckForUpdates(); 에 해당되는 부분도 살펴보자


BOOL AutoUpdate::CheckForUpdates(void)
{
	if (tempversion) return TRUE; // We don't check for updates if we are running a temp version
	MyCallback pCallback;
	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);
	//1.
	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)
	//1 end
	//2.
	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;
	//2 end
}

이 부분도 2파트로 나눠서 확인해보자.

  1. 1번 파트는 업데이트할 파일을 다운받는 부분이다.
    CString URL = m_DownloadLink + m_NextVersion;

위의 URL 변수에 들어가는 m_DownloadLink는 인터넷 링크 or 업데이트 파일 위치 폴더 경로 가 될것이고, m_NextVersion는 생성자에서 사용한 AddNextVersionToFileName() 함수의 결과값이 들어갈 것이다.

그 값으로 나온 URL 주소를 DeleteUrlCacheEntry()를 사용하여 캐시를 초기화하는데, 이유가 있다.
웹상에서 다운을 받는 경우 캐싱을 통해 해당 파일을 저장해 두는데
동일한 이름의 파일을 연속적으로 다운을 받게되면 웹을 통해 받는게 아닌, 기존에 저장되있던 캐시 파일을 활용하여 다운받는 경우가 있게 되어 업데이트가 된 파일임에도 이전 파일이 다운이 되는 경우가 있을 수 있다.
그렇기에 캐시를 지우는 함수인 DeleteUrlCacheEntry()을 사용하여야 한다.

해당 URL의 캐시 파일을 지운 이후
URLDownloadToFile() 함수를 통해 URL에 있는 파일을 현재 프로그램 경로에 저장하는데,
위 상황에선 ExeFile 변수의 이름으로 저장을 하게된다.
ExeFile의 선언을 살펴보면
CString ExeName = L"U" + m_SelfFileName;

현재 파일명의 앞에 "U"를 붙이는데, 이는 생성자에서 봤던 임시 파일의 기준을 맞추는 내용이다. 즉, 해당 URL에서 파일을 받아 "U"를 앞에 붙인 임시파일을 현재 프로그램 경로에 저장하는 파트이다.

2.
2번 파트는 SUCCEEDED() 함수를 통해 1번 파트가 작동 여부에 따른 분기를 나타낸다.
1번 파트 실패시엔 에러를 띄워주고 끝이다.
1번 파트 성공시엔 SG_GetVersion()을 통해 임시파일의 버전을 확인하고, SG_VersionMatch()를 통해 AddNextVersionToFileName()에서 확인한 버전과 임시파일의 버전이 일치하는지 확인한다.

해당 버전이 일치한다면 SG_Run()을 통해 임시파일을 실행하는 루틴과 함께 프로그램이 종료된다.

--------- 함수 분석 끝 ---------

결론으로 이 자동업데이트 프로젝트의 실행루틴은

  1. 업데이트 파일 확인하여 있으면 업데이트 파일명을 임시파일명으로 변경하여 다운받아 해당 파일 실행 후 프로그램 종료
  2. 실행된 프로그램 파일명이 임시파일이면(업데이트파일) 기존파일 제거하고 임시파일을 기존파일명으로 복사 후 기존파일명 실행 후 프로그램 종료
  3. 실행된 프로그램 파일명이 기존파일명이면 임시파일 확인하여 삭제

이런 순으로 진행되게 된다.

다음엔 내가 원하는 입맛에 알맞게 바꿔서 사용해보겠다.

댓글