|
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
[GGG] 오래전 메일함을 뒤적이다가 누군가한테 보낸 메일 중에 재미난 내용이 있어서 소개해 봅니다. 저도 다른 분께 메일로 받은 내용 중 일부를 발췌해서 보낸 건데, 제목은 제가 임의로 붙인 겁니다. 그때도 덧으로 많은 생각이 들게 하는 글이라고 적었는데 지금 봐도 많은 생각을 하게 하는 글이네요. 제자들이 아인슈타인에게 선생님은 어떻게 학문에 성공하셨는지 그 비결을 물었답니다.세 가지 항목을 따로 때놓고 보면 성공이랑 그닥 관련 없는 팩터들 같아 보이지 않습니까? 그런데 또 붙여놓고 생각해보면 성공의 요소인 것도 같아보이네요. 오묘합니다. ㅎㅎ 성공 방정식의 세 가지 항목 중에서 한 가지도 못지키는 요즘인 것 같습니다. 좀 여유를 가지도록 노력해야 겠습니다. 그나저나 메일함을 보니 참 유치한 메일들이 많네요. 철없던 시절 철없이 휘갈겨 쓴 말도 안되는 메일들. 보면서 웃기도 하고, 그때가 그립기도 하고 그렇네요. 언젠가 먼 훗날엔 지금 쓴 글들을 보면서 그런 생각을 하고 있겠죠? ㅋㅋ 그때의 순수를 잃은 것 같아 조금은 아쉽습니다. 나이가 든다는 건 점점 순수를 잃어가는거라고 말했던 어느 작가분이 생각납니다. 진짜 가을이 오려나???? ㅋㅋㅋ |
[GGG] 정확한 OS 종류를 판단하는 것은 무척 중요한 일 입니다. 이 문서에 포함된 함수들은 VerifyVersion과 GetVersionEx함수를 사용하여 정확한 OS 종류를 판단합니다. 제공되는 함수는 아래와 같습니다. 각각의 함수가 판별하는 OS는 주석을 참고 하십시요. [CPP]BOOL WINAPI IsWin95() // 윈도우 95[CPP]#ifndef GETPROC #define GETPROC(dll, type, name) (type)GetProcAddress(dll, name) #endif typedef BOOL (WINAPI *fnVerifyVersionInfo)(LPOSVERSIONINFOEX, DWORD, DWORDLONG); typedef ULONGLONG (WINAPI *fnVerSetConditionMask)(ULONGLONG, DWORD, BYTE); BOOL _VerifyVersionInfo(LPOSVERSIONINFOEX lpVersionInfo , DWORD dwTypeMask , DWORDLONG dwConditionMask , BOOL *bVerified) { BOOL bRet = FALSE; HINSTANCE hDll = NULL; fnVerifyVersionInfo FVerifyVersionInfo; fnVerSetConditionMask FVerSetConditionMask; hDll = LoadLibrary("kernel32.dll"); if(hDll != NULL) { FVerifyVersionInfo = GETPROC(hDll , fnVerifyVersionInfo , "VerifyVersionInfoA"); FVerSetConditionMask = GETPROC(hDll , fnVerSetConditionMask , "VerSetConditionMask"); if(FVerifyVersionInfo != NULL && FVerSetConditionMask != NULL) { dwConditionMask = 0; if(dwTypeMask & VER_MAJORVERSION) dwConditionMask = FVerSetConditionMask(dwConditionMask , VER_MAJORVERSION , VER_EQUAL); if(dwTypeMask & VER_MINORVERSION) dwConditionMask = FVerSetConditionMask(dwConditionMask , VER_MINORVERSION , VER_EQUAL); if(dwTypeMask & VER_PLATFORMID) dwConditionMask = FVerSetConditionMask(dwConditionMask , VER_PLATFORMID , VER_EQUAL); if(dwTypeMask & VER_PRODUCT_TYPE) dwConditionMask = FVerSetConditionMask(dwConditionMask , VER_PRODUCT_TYPE , VER_EQUAL); *bVerified = FVerifyVersionInfo(lpVersionInfo , dwTypeMask , dwConditionMask); bRet = TRUE; } FreeLibrary(hDll); } return bRet; } BOOL WINAPI IsWin95() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 0; osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 0) && (osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && (osi.szCSDVersion[1] != 'C'); } BOOL WINAPI IsWin95OSR2() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 0; osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 0) && (osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && (osi.szCSDVersion[1] == 'C'); } BOOL WINAPI IsWin98() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 10; osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 10) && (osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && (osi.szCSDVersion[1] != 'A'); } BOOL WINAPI IsWin98SE() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 10; osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 10) && (osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && (osi.szCSDVersion[1] == 'A'); } BOOL WINAPI IsWinME() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 90; osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 90) && (osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); } BOOL WINAPI IsWin9X() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; bRet = _VerifyVersionInfo(&osi, VER_PLATFORMID, dwlConditionMask, &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return osi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS; } BOOL WINAPI IsWinNT4() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 4; osi.dwMinorVersion = 0; osi.dwPlatformId = VER_PLATFORM_WIN32_NT; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 4) && (osi.dwMinorVersion == 0) && (osi.dwPlatformId == VER_PLATFORM_WIN32_NT); } BOOL WINAPI IsWin2K() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 5; osi.dwMinorVersion = 0; osi.dwPlatformId = VER_PLATFORM_WIN32_NT; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 5) && (osi.dwMinorVersion == 0) && (osi.dwPlatformId == VER_PLATFORM_WIN32_NT); } BOOL WINAPI IsWinXP() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 5; osi.dwMinorVersion = 1; osi.dwPlatformId = VER_PLATFORM_WIN32_NT; bRet = _VerifyVersionInfo(&osi , VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 5) && (osi.dwMinorVersion == 1) && (osi.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osi.wProductType == VER_NT_WORKSTATION); } BOOL WINAPI IsWinNET() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwMajorVersion = 5; osi.dwMinorVersion = 1; osi.wProductType = VER_NT_WORKSTATION; osi.dwPlatformId = VER_PLATFORM_WIN32_NT; DWORD type = VER_MAJORVERSION | VER_MINORVERSION | VER_PRODUCT_TYPE | VER_PLATFORMID; bRet = _VerifyVersionInfo(&osi, type, dwlConditionMask, &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return (osi.dwMajorVersion == 5) && (osi.dwMinorVersion == 1) && (osi.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osi.wProductType != VER_NT_WORKSTATION); } BOOL WINAPI IsWinNT() { OSVERSIONINFOEX osi; DWORDLONG dwlConditionMask = 0; BOOL bRet = FALSE; BOOL bVerified = FALSE; ZeroMemory(&osi, sizeof(OSVERSIONINFOEX)); osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osi.dwPlatformId = VER_PLATFORM_WIN32_NT; bRet = _VerifyVersionInfo(&osi , VER_PLATFORMID , dwlConditionMask , &bVerified); if(bRet == TRUE) return bVerified; osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) { osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(GetVersionEx((OSVERSIONINFO *) &osi) == 0) return 0; } return osi.dwPlatformId == VER_PLATFORM_WIN32_NT; }[/CPP] |
[GGG] 어젠가 풀리비님의 글을 보다가 관련 내용을 보게 되었다. 그 내용을 처음 보았을 때만 해도 "그냥 뉴스에 나왔나 보다"라고 생각을 했다. 왜냐면 그만큼 해묵은 방법이었기 때문이다. 그런데 예상외로 여기저기서 이슈가 되고 있는 모습이 눈에 띈다. "그 방법이 통하기 때문에 그런게 아닐까?"란 생각을 조심스럽게 해본다. 메모리 해킹이란 방법은 십 수년 아니 컴퓨터가 생기던 시절부터 있었던 메카니즘이다. 고전적인 수법인만큼 그 방법도 여러 수천 가지에 이른다(실제적으로 여러 수천 가지는 아니다). 따라서 기존에 나와있는 방법에 대해서 대응력을 가지는 것은 가능하겠지만 전혀 새로운 형태로 다른 프로세스의 메모리에 접근하는 것을 원칙적으로 차단하는 것은 매우 힘든 일이다. 그렇다면 메모리 해킹을 막을 방법은 없을까? 물론 방법이 없는 것은 아니다. 과거 컴퓨터의 역사와 같이한 방법인 만큼 그것을 막는 방법도 다양하게 연구되었다. 그 중 최종 결론은 데이터가 메모리에 존재하는 구간을 최소화 시켜야 한다는 것이었다. 궁극적으로 데이터가 메모리에 존재하는 시간이 0이 되면 메모리 해킹은 무용지물이 된다. 메모리에 데이터가 없다면 조작할 것이 없는 것이고, 결국은 메모리 해킹은 무위로 돌아갈 수 밖에 없기 때문이다. 이를 인터넷 뱅킹에 적용해서 생각해 보면 다음과 같이 된다. 인터넷 뱅킹을 하기 위해서 사용자가 입력해야 하는 아주 중요한 자료는 두 가지다. 이체 대상 계좌번호와 이체 금액이다. 이 둘은 사용자가 직접 입력하고 화면에 표시된다. 화면에 표시된다는 말은 이미 메모리 상에 존재한다는 말이다. 그래서 지금 메모리 해킹이 문제가 되고 있다. 이 두 데이터가 메모리 상에 존재하지 않는다면 안전하다는 것은 앞서 설명했다. 이 둘을 표시하지 않고 인터넷 뱅킹을 할 수 있을까? 물론 없다. 사용자가 실수로 잘못된 정보를 입력할 수 있기 때문에 보여주는 과정은 절대적으로 필요하다. 그렇다면 인터넷 뱅킹의 메모리 해킹을 막는것은 불가능 한가? 그것도 아니다. 과거 MMORPG 게임에 처음 메모리 해킹이 적용됐을때 그 파급효과는 정말 컸다. 거의 모든 데이터가 클라이언트에 저장되었기 때문에 고치기만 하면 적용되는 그런 세상이었다. 하지만 지금은 많이 달라졌다. 메모리 해킹 툴을 사용해서 클라이언트를 조작해도 데이터가 변경되진 않는다. 어떻게 그렇게 됐을까? 원리는 간단하다. 클라이언트에서 표시되는 데이터는 단지 보여지는 역할만 하기 때문이다. 모든 데이터 생성은 서버에서 이루어지는 것이다. 주사위를 굴린 값도 서버에서 생성되어 클라이언트로 전송된다. 그래서 요즘의 MMORPG 게임은 메모리 해킹 보다는 봇을 막는것이 좀 더 중심이 되고 있다. 다시 인터넷 뱅킹으로 돌아가보자. 우리는 클라이언트에 데이터가 저장될 수 밖에 없다면 단지 보여주는 기능만을 하도록 그것을 제한하면 메모리 해킹에 효율적으로 대응할 수 있다는 것을 게임에서 배웠다. 어떠한 방법을 통해서 이것을 인터넷 뱅킹에 적용할 수 있을까? 가장 기본적인 원리는 모든 것을 서버가 결정한다는데 있다. 다음과 같은 예를 한번 생각해보자. 사용자가 로그인을 한다. 이제 계좌번호를 입력할 순간이 되면 서버에서 그림을 열 조각 보내 준다. 이 그림은 0부터 9까지에 대응하는 그림이다. 하지만 각 그림에는 고유 키번호가 맵핑되어 있다. 즉 소프트웨어적으로 0번 그림이 0이라는 것을 알아낼 방법은 없다. 사람이 0을보고 누르면 그에 대응하는 키 값이 들어가는 것이다. 사용자가 1,2,3,4를 눌렀고 서버로 c,d,e,x가 전송되었다고 해보자. 서버는 받은 데이터를 자신이 보내준 그림 데이터에 근거해서 원복한다. 그럼 1,2,3,4가 나온다. 그럼 서버에서 그걸 다시 클라이언트로 전송한다. 화면에는 사용자가 입력한 계좌 번호가 나올 것이다. 메모리 해킹을 하는 프로그램이 그것을 조작해본들 그건 단지 뷰일 뿐이다. 서버로 전송되는 값이 아니란 의미다. 물론 이 경우에도 서버에서 발생시킨 그림 조각을 해킹 프로그램이 알아낸다면 해킹을 할 수가 있다. 하지만 이는 자동화하기가 매우 난해하다. 해커라면 리버싱을 통해서 그림과 의미를 매칭시킬 수 있지만 프로그램은 그것을 하기가 무척 어렵다. 결론적으로 0번 그림을 보고 그것이 0이라는 것을 인식하는 문제로 귀결되는데 이는 아직까지는 불가능하기 때문이다. 또한 그 이미지 자체도 동적으로 바뀐다면 더욱 프로그램으로 인식하기 어려워진다. 이때까지 말한 이 솔루션은 사실 새로운 방식도 아니고 몇 해 전에나 제안되던 구식 방법이다. 이 방식이 적용되지 않았던 이유는 단 한가지였다. 사용성이 떨어진다는 점이었다. 유저 입장에서도 골치아프고, 적용하는 업체 입장에서도 고칠 곳이 많아진다. 결과론적으로 비용이 올라간다는 것이었다. 맞는 말이긴 하다. ㅎㅎ 바이러스가 나오면 백신 업체가 돈을 벌고, 해킹 소식이 나오면 보안 업체가 돈을 버는 것은 초등학생도 알 만한 일이다. 이미 솔루션을 구축하고 이 뉴스가 일파만파 퍼지기를 기다리고 있는 보안업체가 있을지도 모른다. 물론 그들이 잘못한건 없다. 이슈가 되지 않으면 적용하지 않는 세태가 더 잘못된게 아닐까? 기사 말미에 나오는 휴대폰 인증도 좋은 방법이다. 그런데 휴대폰 방법을 적용하려면 이런 말을 하는 사람들이 꼭 있다. "그럼 휴대폰없는 사람은 인터넷 뱅킹도 하지 말라는 말이냐?" 그래서 집전화까지 지원하게 된다. ㅋㅋ |
[GGG]
그럼 이제 자주 사용되는 타입을 살펴보자.
보다 많은 타입에 대한 더욱 상세한 설명은 아래 페이지에 나와있다. |
[GGG] 얼마전 소리통을 만들고 거기에 음악방송 기능을 추가하면 좋겠다는 생각이 들었습니다. 그래서 ShoutCAST 소스 필터를 찾아보았는데 찾기가 쉽지 않더군요. 물론 코덱팩에 들어있는 필터는 많은데 저는 별도의 코덱을 설치하지 않아도 들을 수 있게 하고 싶었습니다. 그냥 exe에 집어넣을 수 있게 소스가 있으면 좋겠다 싶었죠. 네트를 방황하던 끝에 ShoutCAST 소스 필터를 찾을 수 있었습니다. MPEG-1 Stream Splitter 필터에 연결해서 사용할 수 있는 소스 필터였습니다. 필터 소스를 분리해서 연결하니 음악 방송이 솔솔 흘러오더군요. 음. 다했네 라고 잠시 생각했습니다. 그런데 헉.. 아주 사소한 문제가 있었습니다. 바로 탐색이죠. 로컬 파일 같은 경우는 앞뒤로 이동이 되는데 음악 방송같은 경우는 원칙적으로 탐색이 불가능합니다. 그런데 이 놈은 MPEG-1 Stream Splitter에 연결해서 쓰는 놈이라 그런지 탐색이 되는 것이었죠. 탐색이 되는건 아니고 단지 되는 척만 하는 것이었죠. 물론 UI에서 체크해서 비활성화 시키면 되지만 비겁한 방법이란 생각이 들더군요. 그래서 MP3를 분석해서 직접 ShoutCAST 푸시 소스 필터를 만들어야 겠다고 생각을 했습니다. MP3 프레임 헤더는 정말 간단한 4바이트 구조로 이루어져 있습니다. 프레임 헤더 구조가 궁금하신 분은 다음 페이지를 참고하세요. http://www.dv.co.yu/mpgscript/mpeghdr.htm 4바이트 심플한데 비트가 홀수로 쪼개져서 조금 귀찮아 보이죠. 전 저걸 보고 겁없이 비트필드를 만들었답니다. 보는 순간 이건 나에게 비트필드를 만드라고 지시하는 것 같았죠. 그 날 전 하루 죈장 삽질했습니다. 제가 삽질했던 이유는 비트필드의 메모리 레이아웃을 제대로 몰랐고, 엔디안을 정확하게 판단하지 않았던 것 입니다. 파일에 저장된 4바이트는 빅엔디안 순서로 저장되어 있고, VC++의 비트필드는 리틀 엔디안과 같이 뒤에서 부터 비트를 채우는 형태더군요(http://msdn2.microsoft.com/en-us/library/ms858673.aspx 참고). 결국 작업을 하긴 했지만 굉장히 힘든 과정이었습니다. 단지 4바이트 분석하는것이. ㅠㅠ 아래는 비트필드에 관한 자료를 찾던 중 발견한 재미난 내용입니다. 첫 번째 링크는 뉴스그룹에서 비트필드에 관한 논쟁입니다. 베커 아저씨와 mlimber 아저씨의 논쟁인데 둘의 입장이 첨예하게 대립됩니다. mlimber 아저씨는 비트필드 따위를 버려라. 베커 아저씨는 좀 더 현실적인데요.두 번째 링크는 mlimber 아저씨가 참고하라고 올려준 비트 스트림 클래스입니다. 자세히 읽지는 않았지만 재미난 내용인 것 같아요. 뉴스 그룹 스레드 비트필드를 예술로 승화시킨 사나이 재미난 논쟁인데 여러분은 어떤 입장인가요? 이번달 마소 플러스에도 이와 관련된 내용을 하나 연재했는데, 저는 베커 아저씨한테 손을 들어주고 싶습니다. 왜냐하면 일반적으로 회사에서 개발하는 제품은 특정 플랫폼에서 동작하는 경우가 대부분이기 때문이죠. 멀티플랫폼을 생각하더라도 베커 아저씨 말처럼 비트필드를 스왑하는 작업은 그다지 큰 힘도 어려운 일도 아닙니다. 과거 수많은 소프트웨어들이 그런 식으로 포팅되어 왔구요. 물론 잘 만들어진 비트스트림 클래스가 있다면 사용하는 것이 바람직하겠지만 없는걸 굳이 만들어가면서 까지 시간을 소비할 필요는 없을것 같다는게 제 생각입니다. 또한 템플릿 메타 프로그래밍을 해본 사람이라면 알겠지만 쉬이 접근할 곳이 아니죠. --- 한참 전에 썼던 글인데 아직도 ShoutCAST 푸시 소스 필터는 만들지 못했습니다. 요즘은 회사일이 조금 바빠서 그 코드 보는 일만으로도 힘이 드는군요. 저런건 또 재미 붙었을때 만들어야 하는데 타이밍 놓치면 다시 만들라면 좀 그렇더라고요. 하지만 언젠가는 ShoutCAST 기능을 붙일 겁니다. *^^* 언젠가는 다시 재밌어 지겠죠. ~~ |
[GGG] 저는 매달 마이크로소프트웨어를 읽습니다. 예전엔 정기 구독을했고, 지금은 필자라서 한 권씩 보내주는 걸 받아서 보고 있죠. 특히 서광렬님, 최재훈님, 안윤호 아저씨의 칼럼을 좋아합니다. 세 분의 칼럼을 좋아하는 이유는 사뭇 다릅니다. 서광렬님의 칼럼은 늘 제가 궁금했던 부분이나 신기한 것에 관한 것을 담고 있어서 좋아합니다. 최재훈님의 칼럼은 재미있고 유익하기 때문에 좋아하죠. 아마 제 생각에 마소를 보는 독자중에 가장 많은 사람들이 보는 내용이 최재훈님의 칼럼이 아닐까 싶습니다. 그만큼 재밌고 유익합니다. 안윤호 아저씨의 칼럼은 그 분의 박식함에 매료되는 경우가 많습니다. 그 분의 글에서 뿜어져 나오는 포스는 정말 감당이 완되죠. 글을 읽을 때마다 전 늘 이렇게 생각합니다. "어떻게 이렇게 많은 것을 알 수 있을까? 사람이 아닐꺼야." 이번 달에 연재된 안윤호 아저씨의 칼럼 중에서 공감가는 내용이 있어서 소개해 봅니다. 사람들은 자기가 파악할 수 있을 정도의 복잡성을 좋아하고 그 이상은 사실 효율적이기 보다는 머리를 아프게 할 때가 많다. 하지만 아무리 복잡한 일도 자기가 관리할 수 있는 복잡성 이상으로 운영할 수는 없다.토할만큼 공감가는 말이죠. 돌려보면 아는 만큼 보인다라는 말과도 유사합니다. 이번 달 안윤호 아저씨의 칼럼이 저에겐 저 말의 증명이었습니다. 그간에 연재된 칼럼의 경우는 너무 어려워서 이해하기 힘든 경우가 많았죠. 자연스럽게 제가 처리할 수 있는 복잡도를 넘어가니 흥미가 없었습니다. 그런데 조금 쉬운 내용을 담고 있는 이번 달 칼럼은 무척 재밌었습니다. 앞으로는 디지털 토이 제작과 관련된 내용을 쓰신다고 합니다. 한 때 관심 분야 였기에 더욱 기대가 됩니다. 재미난 토이들이 많이 나왔으면 좋겠습니다. 인두기랑 납을 준비해야 되는 건 아닌지 모르겠습니다. 인두기랑 납하니까 옛날 생각이 나는군요. CodeWiz가 임베디드를 떠난 까닭은? 저의 첫 병역특례 회사는 임베디드 장비를 만드는 회사였습니다. 그 때 제가 처음 맡았던 프로젝트는 IP540이라는 장비를 운용하는 프로그램을 제작하는 것이었죠. IP540이란 기계는 196kc 마이크로컨트롤러가 있었고, lcd, 랜카드, 몇 개의 버튼, 바코드 리더기 정도가 달려있는 생산 자동화 장비입니다. 그 때 전 윈도우 프로그래밍이란 일에 한참 염증을 느꼈던 때라 임베디드 프로그래밍이 굉장히 신선했습니다. 회로도 보고, 레지스터를 조작하고, 모든 것을 직접 만들기 때문이었죠. 코머 아저씨의 책을 벗삼아 열심히 프로토콜도 공부하고 했습니다. 그렇게 저의 첫 작품은 모 업체에 납품되어서 성공적으로 사용되었습니다. 랜카드 칩셋과 씨름한 무수한 날들이 눈녹듯 사라지는 그런 날이었죠. ![]() 제가 다음으로 맡게된 프로젝트는 굉장히 위험한 것이었습니다. 전력선 통신 모듈을 개발하는 것이었죠. 이게 왜 위험한고 하니 전력선 통신이라는 것이 220볼트를 그대로 통신 선로로 사용하는 것 입니다. 따라서 테스트 장비또한 220볼트가 그대로 들어오죠. 회로에 결함이 있다면? 내지는 프로그램의 결함이 있다면 골로 가는 겁니다. 5볼트로 장난치는 디지털 세상과는 조금 다르죠. 이 때 사용한 칩은 PIC이었습니다. 8비트 칩이었죠. 단지 전등과 스위치를 연동시켜서 통신으로 켜고 끄는 간단한 일이었습니다. 이 간단한 일이 얼마나 힘든지를 뼈져리게 느꼈죠. 그 때 당시 결국 스위치를 연동해서 사용하는 것 까지 구현은 했으나 실용성은 제로 였습니다. 왜냐하면 노이즈가 조금만타도 전혀 동작되지 않았기 때문이었죠. 패리티나 CRC 같은 것들은 완전 쓸모없는 환경이었습니다. 하여튼 그 때 저는 실험 도중 그놈의 스위치가 폭발하는 것을 두번이나 보았고, 그 다음 부터는 사실 칩을 새로 구워서 테스트 하는 것도 두려웠습니다. 그렇게 저의 두번째 프로젝트는 대실패로 돌아갔고, 저는 결국 임베디드를 떠나 다시 윈도우 프로그래밍으로 돌아왔습니다. 그 때 저에게 김실장님이 하신 말이 아직도 생각나는 군요. 김: "왼손 잽이는 이쪽에서 일하면 안돼." 신: "왜 그런가요?· 김: "왼손있는데 바로 심장이 있잖아. 까딱하면 골로 가는거야...ㅠㅠ" |
[GGG] 어제 아는 형이 이런걸 물어보더군요. "lua 스크립트를 쓰는데 그걸 VC++ 6.0에서 C++로 인식시키고 싶어. 방법이 없을까?· 그 형이 요구한건 lua 스크립트를 컴파일은 하지 않고, 단지 신택스 하이라이팅만 하고 싶다는 것이었습니다. 제가 알려준 페이지는 아래 페이지입니다. http://support.microsoft.com/kb/181506 테스트를 해보지 않아서 정확하진 않지만 아마도 형이 원한 답이 아니었을 것 같습니다. C++로 인식 시키면 컴파일 해버리겠죠. ㅠㅠ 혹시나 그런 경우가 아니라 커스텀 확장자를 사용하고 싶은 분들에게는 도움이 될 것 같습니다. cpp가 꼭 C++이어야 할 필요는 없자나요. c++을 확장자로 쓰고 싶은 사람들도 있을 테니까요. 그런데 과거부터 지금까지 쭉 느낀거지만 VC++의 신택스 하이라이팅 기능은 발전할 필요가 있습니다. 새로운 문법에 대해서도 그렇고, 구분 단위에 대해서도 그렇고 말이죠. |
[GGG] 2006년도였던가 부터 전 구글 통계 서비스를 사용했습니다. 사실 말이 사용이지 자주 들어가보진 않습니다. 머 통계가 저에게 큰 의미가 없기 때문이죠. 단지 심심할 때 한번씩 들어가 보지도 않죠. 하여튼 거의 사용을 하지 않았던것 같습니다. 그러다 몇 일 전에 애드센스 홈페이지를 들렀다가 통계 서비스를 간만에 아주 간만에 들어가게되었죠. 예전에도 구글 통계는 꽤나 괜찮은 서비스였습니다. 왠만한 유료 통계 서비스보다 더욱 미려한 화면과 상세한 자료를 제공해 주었죠. 그런데 그놈이 개편된 것이었습니다. 언제 개편됐는지는 모르겠으나 감동이었습니다. 후후~ 아래는 방문자의 기록 개요를 보여주는 화면입니다. 크게 표시된 그래프는 플래시입니다. 마우스를 움직이면 포인트가 트래킹 되어서 그 날 얼마나 많은 방문자가 다녀갔는지를 표시해주고 있습니다. 이 UI를 보면서 제가 느낀 첫 감정은 "이거 웹이야?"라는 것이었습니다. 미려하고 잘 움직입니다. 2007년 중에 가장 많은 사람이 방문한 날은 5월 16일이군요. 713명이나 방문했습니다. 와우~ ㅋ. 찾아보니 뱅킹 ActiveX 글을 올려서 아주 잠깐 올블에 올랐던 날이었군요. 평균적으로는 대체적으로 200명 내외에 해당하는 방문자가 다녀갑니다. 사실 저건 시작에 불과합니다. 그 밑으로 더 재미난 정보들이 나열되어 있습니다. 올해 총 방문 횟수는 52786회 이고, 그 중 실제 방문자는 38709명 이네요. 이 데이터를 보면 이 사이트의 방문자 성향을 알 수 있습니다. 거의 한번 왔다 가는 사람이 대부분이라는 거죠. ㅠㅠ 새로운 방문 횟수가 72.81%라는 것은 얼마나 충성도가 낮은 사이트인지를 말해 줍니다. 방문당 평균 채류 시간, 평균 페이지뷰 같은 재미난 정보도 나와 있습니다. 각 정보 옆에 나온 작은 그래프는 기간 내의 데이터를 작게 줄여서 표시한 것 입니다. 각 데이터의 특징을 간략하게 볼 수 있죠. 정보 부분을 클릭하면 보다 자세한 내용이 나옵니다. ![]() 아래는 방문자의 충성도(로열티)를 보여주는 표 입니다. 보시면 앞서도 언급했듯이 지니야닷넷을 방문해주시는 분들은 거의 대부분이 새로운 방문자라는 것을 알 수 있습니다. 우연찮게 한 번 들리고 마는 것이죠. 굉장히 슬픈 현실입니다. 몇 안되는 방문자 중에서도 그나마도 우연찮게 한번 들리는 사람이 대부분이라는 사실 ㅠㅠ ![]() 아래는 제가 완전 감동먹은 기간 선택 UI입니다. 통계 정보를 볼 기간을 선택하는 부분입니다. 달력에서 클릭하면 날짜가 자동적으로 입력됩니다. 세달 이전이나 이후로는 옆에 있는 버튼을 누르면 이동됩니다. DateRange 밑에 파란 박스가 보이시나요? 그건 지금 달력에서 클릭하면 그 자리에 입력된다는 것을 나타내는 포커스 표시입니다. 대단합니다. 군더더기 없는 황홀한 UI에 놀랐습니다. ![]() 예전엔 유료 통계 서비스가 있었습니다. 지금도 있나요? 하여튼 그런데 참 이런 구글의 통계 서비스를 보고 있자면, "이런게 공짜라면 당췌 유료 통계 서비스를 기획하는 사람들은 어떻게 만들어야 돈받고 팔 수 있을까?"란 생각이 머릿속을 파고 듭니다. 통계 서비스는 더 이상 경쟁력이 없는 건가요? 사용자로서 행복하지만 다른 한편으로는 참 씁쓸했던 구글의 통계 서비스 체험기였습니다. 홈페이지 혹은 블로그를 운영하신다면 꼭 가입해서 사용해 보세요. 수준 높은 서비스를 체험할 수 있습니다. 정말 생각지도 못했던 통계 자료도 많습니다. 물론 그런 것들이 개인이 운영하는 페이지에는 큰 영향을 미치진 않겠지만 말이죠. |
[GGG] 제가 비스타를 시스템에 설치해서 사용한지는 꽤 되었습니다. 64비트 컴퓨터를 산지가 꽤 됐으니까 말이죠. ㅋ 그런데 비스타를 처음 깔아서 쓸때부터 지금까지 느꼈던 엄청난 단점이 있었습니다. 바로 엄청난 양의 디스크 I/O 였습니다. 제 컴퓨터는 빠방한 AMD 64비트 듀얼 코어에, 2기가 램을 가지고 있습니다. 그런데도 비스타는 저의 하드를 엄청나게 긁어 대는 것이었습니다. 상식적으론 도저히 납득이 되질 않죠. 무엇이 부족해서 그리도 디스크를 읽는단 말이냐? 하고 늘 생각했습니다. 그래서 처음 설치하고 쉽게 떠오르는 인덱싱 서비스나 디펜더 서비스를 중지 시켰습니다. 당연히 클린 시스템이니 자동 실행 프로그램이 많을리 없습니다. 이제는 조용하겠지? 하고는 재부팅을 했는데 소용없는 노릇이었습니다. 그렇게 몇 달간을 포기하고 지냈습니다. 회사 컴퓨터였으면 어떻게든 고쳤을텐데 집 컴퓨터라 할 시간이 많지 않아 그냥 두고 있었죠. 오늘 집에 와서 부팅을 시켰는데 이 놈 또 부팅하자마자 20분을 혼자 디스크를 긁고 있습니다. 조용해졌다 싶어서 작업을 좀 해볼까 하면 또 연나 긁어댑니다. 주기도 없고, 규칙도 없고 마음대로 긁어대는 거죠. 그래서 오늘 안되겠다 싶어서 구글님과 집중 면담을 했습니다. 그 결과 원인이 무엇인지 알아냈습니다. 휴우 ㅋㅋ 원인은 바로 비스타에서 새로 생긴 수퍼페치(Superfetch)라는 기능 때문이었습니다. 수퍼라... 먼가 좋아보이지 않나요? ㅎㅎ 수퍼페치가 하는 일을 아주 간단히 설명하면 메모리에 자주 사용하는 것들을 미리 올려두어서 사용하자 필요하는 시점에 그것들을 바로바로 제공해 주는 것입니다. 메모리를 캐시로 사용하겠다는 생각이죠. I/O 병목 지점에서 미리 읽어둔다면 실행 속도가 엄청 빨라질겁니다. 와우. 훌륭한 기능이죠. 물론 아이디어는 좋습니다. 하지만 수퍼페치의 기능을 몇 달간 체험해 본 바로 제가 느낀 점은 "너무 공격적이다"라는 것 입니다. 메모리를 아낌없이 사용하며, 디스크 I/O는 시도때도 해버리는 그런 존재인 것이죠. 제가 봤을때 수퍼페치를 설계한 사람들이 결정적으로 간과한 한 가지 사실은 하드디스크 소음입니다. 그 엄청난 I/O에서 발생하는 소음 공해들. 작업에 집중할 수 없는 환경을 만들어 줍니다. 이런 연유로 저는 오늘 당장 수퍼페치를 중지 시켰고 매우 만족스럽게 사용하고 있습니다. 이제야 조용한 컴퓨터가 되었네요. 그렇다면 수퍼페치는 어떻게 중지시키나요? ![]() 우선 제일 중요한 작업은 수퍼페치 서비스를 중지하는 것 입니다. 서비스 관리에 들어가서 Superfetch를 찾은 다음 속성을 누릅니다. 그리고는 과감하게 사용안함으로 바꾼다음 확인을 누르면 됩니다. 위와같이 표시된다면 정상적으로 중지된 것 입니다. ![]() 서비스만 중지해도 되는데 전 조금 찜찜해서 레지까지 수정했습니다. HKLM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters에 들어갑니다. 거기서 오른쪽에 표시되는 항목 중에 EnableSuperfetch 값을 0으로 변경해 줍니다. 참고로 값이 의미하는 바는 0은 중지, 1은 부팅 과정 수행, 2는 응용프로그램 수행, 3은 둘 다 수행입니다. 조용한 비스타를 원하시는 분들은 한번쯤 중지시켜 보세요. 분명 만족하실 겁니다. |