고수닷넷 - 데미소다오렌지님
1. Introduction
소프트웨어를 개발하는 사람들이라면 누구나 느끼는 것이 자신이 가장 최근에 빌드한 파일이 가장 최신의 버전 정보를 담기가 힘들다는 사실이다. 물론 이러한 것들을 고치기 위해서 자동 버전 증가 매크로등을 사용하는 일부 똑독한 개발자들도 있다. 하지만 그것을 사용해 본 사람이라면 알겠지만, 버전 증가 매크로의 기본적인 테크닉의 하나인 리소스 파일 편집이 일어남으로써 빌드가 매번 발생한다는 불편함이 있다. 즉, 조금 고치고 빌드하는데도 버전과 관련된 부분은 죄다 새로 컴파일 되니 짜증이 나는 것이다.
과연 그렇다면 개발자들을 version hell로 부터 구해줄 좋은 방법은 정녕 없는 것일까? ㅡ.ㅡ# 물론 당연지사 없다고 생각하는 필자였으나, 최근 VNC 소스를 보면서 굉장히 흥미로운 테크닉을 하나 발견할 수 있었다. 바로 내장 매크로를 사용하는 방법이다.
2. __FILE__, __DATE__, __TIME__
C언어에는 위와 같은 내장 매크로가 존재한다. 물론 수없이 많은 내장 매크로가 있으나, 위에 제시된 것들은 그나마 자주 사용되는 것이다.
__FILE__에는 컴파일 되고 있는 파일의 전체 경로가 저장되어 있다.
__DATE__에는 컴파일 되는 날짜가 저장되어 있다.
__TIME__에는 컴파일 되는 시간이 저장되어 있다.
어느 교수님 말처럼 전문가들은 저런 세세한 것들을 보고 있지 않는다. 단지 아래 코드를 실행해보면 모든게 자연스럽게 이해가 가는 것이다. ㅋㅋㅋ
int main()
{
printf("%s, %s, %s", __FILE__, __DATE__, __TIME__);
return 0;
}
3. Version...
윈도우 95가 소프트웨어 업계에 가지고 온 신선한 충격은 꽤나 많았지만 모든걸 제치고 가장 큰 변화였다고 한다면 버전에 개발 년도를 붙인 것이었다. 그 전에는 1.0, 2.1등의 버전이 일상적이었다. 년도를 버전에 붙이게 되면서 생기는 가장 좋은 점은 해당 소프트웨어가 출시가 된지가 언제였는지 바로 알 수 있다는 점이다. 윈도우 3.1이 출시된지 몇년이 지났는지는 알기 힘들지만 95는 바로 10년이란 세월이 흘렀음을 알 수 있게 해준다. 그 이후 나오는 거의 대부분의 소프트웨어들은 대부분 버전에 년도를 붙이기 시작했다.
물론 이러한 흐름을 끝까지 배척하는 무리들도 있다. 물론 어느쪽이 좋은지는 판단하기 힘들다. 리눅스 커널의 경우 메이저, 마이너, 패치 단위로 버전을 관리하며, 버전 번호로 부터 이 커널이 안정화 커널인지 어느정도 패치가 된 커널인지를 바로 알 수 있다는 장점이 있다. 카누스 교수가 개발하고 있는 텍의 경우는 파이를 따라 버전을 붙이기도 한다. 3.14, 3.141, 3.1415, 3.14159따위의 식이다.
우리가 오늘 사용할 방식은 바로 __DATE__와 __TIME__을 사용한 버전 관리 방법이다. 이 경우 컴파일된 날짜와 시간을 바로 버전으로 삼는다는 것이다. 프로그래머가 버전을 고치기 위해서 개입해야 할 일이 거의 없기 때문에 무척이나 편리한 방법이다. 이제 우리에게 편리함을 안겨줄 코드를 보기로 하자.
void WINAPI
GetBuildDateTime(LPTSTR bdate, UINT datesize, LPTSTR btime, UINT timesize)
{
const char *BUILD_DATE = __DATE__;
const char *BUILD_TIME = __TIME__;
StringCbCopy(bdate, datesize, BUILD_DATE);
StringCbCopy(btime, timesize, BUILD_TIME);
}
우리의 버전을 구해주는 함수는 위와 같다. 그 단순함에 너무나도 놀랐는가? ㅋㅋㅋ~ 난 첨에 사실 조금 당황했다. 이렇게 간단하고 심플한 버전 관리 방법이 있으리라곤... 헐 - -
4. Dll hell
3장의 내용만으로도 모든 아이디어가 전달되었다고 생각되지만 dll의 버전 관리 지옥에서 탈출하기 위한 중요한 함수 하나를 더 소개할까 한다. 일단 아래 코드를 보도록 하자.
typedef struct _BUILD_INFO
{
int year;
int month;
int day;
int hour;
int min;
int sec;
} BUILD_INFO, *PBUILD_INFO;
BOOL WINAPI
GetBuildInfo(PBUILD_INFO pbi, LPCTSTR dllpath)
{
BOOL ret = FALSE;
HINSTANCE hDll;
FGetBuildDateTime func;
TCHAR bdate[80];
TCHAR btime[80];
hDll = LoadLibrary(dllpath);
if(hDll != NULL)
{
func = (FGetBuildDateTime) GetProcAddress(hDll, "GetBuildDateTime");
if(func != NULL)
{
func(bdate, sizeof(bdate), btime, sizeof(btime));
_ParseBuildInfo(pbi, bdate, btime);
ret = TRUE;
}
FreeLibrary(hDll);
}
return ret;
}
위 코드가 하는 일은 dll을 로드해서 거기서 3장에서 제시되었던 GetBuildDateTime으로 해당 dll의 빌드 시간 정보를 넘겨받는다. 그리고는 그놈을 파싱해서 좀 더 의미있는 정보인 BUILD_INFO 구조체로 넘겨주는 함수다. 자 이제, 작성하는 모든 dll에서는 단순히 3장에서 제시된 빌드 정보를 넘겨주는 함수만 익스포트 하고 나면, 자동으로 모든 dll의 버전을 구할 수 있는 것이다. 물론 기존의 dll 버전을 수정하기 위해서 프로그래머가 해야할 일은 없다.
위 코드를 수행하는데 필요한 _ParseBuildInfo 함수는 아래와 같이 작성할 수 있다.
static void
_ParseBuildInfo(PBUILD_INFO pbi, LPTSTR bdate, LPTSTR btime)
{
const char *mn[13] = { NULL,
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec" };
char *ptr;
int i;
ptr = strtok(bdate, " ");
for(i=1; i<13 && ptr; ++i)
{
if(stricmp(mn[i], ptr) == 0)
{
pbi->month = i;
break;
}
}
ptr = strtok(NULL, " ");
if(ptr)
{
pbi->day = atoi(ptr);
}
ptr = strtok(NULL, " ");
if(ptr)
{
pbi->year = atoi(ptr);
}
ptr = strtok(btime, ":");
if(ptr)
pbi->hour = atoi(ptr);
ptr = strtok(NULL, ":");
if(ptr)
pbi->min = atoi(ptr);
ptr = strtok(NULL, ":");
if(ptr)
pbi->sec = atoi(ptr);
}
5. Conclusion
내가 늘 회사에서 하는 실수는 dll을 컴파일하고 나서 버전을 변경하지 않는 실수를 하는 것이었다. 그것은 업데이트 소프트웨어가 dll을 제대로 패치하지 못하게 만들었고, 결국 엉성한 버전들로 구성된 프로그램은 잘못된 연산을 하기가 쉽상이었다.
여기에 적용된 기법에 프로그래머가 버전 관리를 위해서 개입해야 하는 부분은 전혀 없다. 자신이 해당 함수를 익스포트한 이후에는 컴파일할때마다 버전은 자동으로 변경되게 된다. 따라서 버전을 고치지 않는 실수를 미연에 방지할 수 있는 것이다.
"UNIX/Linux C" 카테고리의 다른 글
Leave your greetings.