MSDN, MSJ 에 수록된 내용을 보고 제가 공부한 내용을 정리해보았습니다. 잘 정리된것은 아닌듯 싶으니 편하게 그냥 한번 읽어나 본다 라고 생각하시면

좋으실듯 싶습니다 ㅡ.ㅡ;



SEH(Structured Exception Handling)



1) Exception

예외(Exception)란 의도한대로 되지 않았음을 의미한다. 어떤 목적을 위하여 코드를 작성했을 때 그 코드가 정상적으로 의도한대로 작동하지 않은 상황을 예외라고 한다. 여기에는 결과값이 정상적으로 반환되지 않은경우를 포함하여 코드가 오류를 발생시켜 어플리케이션이 종료되는 경우도 포함된다.

이들 예외중에 코드를 작성하면서 짐작할 수 있는 예외(반환값을 처리한다던가 하는..)를 처리해두면 이것은 Handled Exception 이 되는것이고, 짐작하지 못하여 예외를 처리해두지 못한 상황에서 예외가 발생하면 이것은 Unhandled Exception 이라고 한다.

Unhandled Exception 은 개발을 하다보면 흔히 볼 수 있는 ‘응용프로그램 오류’ 같은것이 가장 보기쉬운 예라 할 수 있다. 메모리 할당을 하도록 코드를 작성하고, 실제로 시스템상의 이유로 메모리를 할당하지 못했는데 그 반환값을 체크하지 않고 메모리가 정상적으로 할당되었을 경우만을 고려하여 할당되지 못한 메모리(잘못된 메모리 주소 혹은 NULL 포인터)를 참조하면 Exception(Access Violation 등)이 발생하게 되는것이다.



예)

소스 1)

char *pBuffer;

char szString[] = {“String copy test”};

pBuffer = (char *)malloc(sizeof(char) * (strlen(szString) + 1));

strcpy(pBuffer, szString);

printf(pBuffer);

free(pBuffer);



소스 2)

char *pBuffer = NULL;

char szString[] = {“String copy test”};

pBuffer = (char *)malloc(sizeof(char) * (strlen(szString) + 1));

if(pBuffer != NULL)

{

strcpy(pBuffer, szString);

printf(pBuffer);

free(pBuffer);

}

else printf(“Not enough memory”);

위의 두 소스를 보자. 첫번째 소스는 메모리가 정상적으로 할당되었을 경우만을 고려하여 코드를 작성한것이다. 두번째 소스는 malloc의 반환값을 비교하여 메모리가 정상적으로 할당이 되었을 경우에만 원하는 일을 처리하도록 코딩하였다.

이런 작은 부분을 처리해주는것이 예외 상황이 발생할 수 있는 확률을 줄일 수 있는 가장 쉬운 방법이 되는것이고, 또 예외가 발생하더라도 디버깅을 쉽게할 수 있게 해준다.



실제로 변수의 초기화 와 변수값의 체크, 사용한 함수의 반환값 체크. 이 두가지만 확실히 해주면 대부분의 예외는 미연에 방지할 수 있다. 이미 구현되어있는 함수를 사용하는 경우가 아니라 함수를 작성해야 하는경우에는 반환값에 대해 일정한 룰(예를들면 HRESULT 같은)을 적용하여 구조를 잡아간다면 좀더 쉽게 디버깅이 가능하고, 어떤 문제가 발생할 수 있는지를 그렇지 않은경우보다 훨씬 수월하게 찾아낼 수 있다.



2) try{} catch(...){}

C++ 컴파일러에서 지원하는 예외처리 방법중에 try, catch 구문이 있다.



try

{

수행할 코드

}

catch(...)

{

예외가 발생했을 경우 수행할 코드

}



간단히 이런 구조로 이루어져있는데, try 블럭에서 예외가 발생하면 catch 블럭으로 수행을 옮겨주는 역할을 한다. 앞의 예제 처럼 한가지 일만해도 된다면 그냥 비교하는 방법으로 처리해도 그다지 복잡해지지 않으니 괜찮을지 모르지만. 처리해야될 일이 많아지고 코드가 길어지게되면 예외를 처리하기 위해 넣어둔 코드가 실제 수행해야할 코드가 더 많아질 수도 있는 일이다.(지나치게 if 구문이 많아지는 경우가 발생할 수 있다.)





예)

BOOL CreateMainWindow(); // 메인 윈도우를 생성하는 함수

BOOL SetInformationToMainWindow(); // 기본정보를 메인윈도우에 세팅

BOOL ShowMainWindow(); // 메인 윈도우를 화면에 보여준다.



이런 함수들로 이루어진 어플리케이션이 있다고 하면...



소스 1)

CreateMainWindow();

SetInformationToMainWindow();

ShowMainWindow();



소스 2)

if(CreateMainWindow())

{

if(SetInformationToMainWindow())

{

if(ShowMainWindow())

{

}

else printf(“Error ? Show main window”);

}

else printf(“Error ? Set information to main window”);

}

else printf(“Error ? Create main window”);



소스 3)

const DWORD ERROR_CREATEMAINWINDOW = 1;

const DWORD ERROR_SETINFOTOMAINWINDOW = 2;

const DWORD ERROR_SHOWMAINWINDOW = 3;



try

{

if(!CreateMainWindow()) throw ERROR_CREATEMAINWINDOW;

if(!SetInformationToMainWindow()) throw ERROR_SETINFOTOMAINWINDOW;

if(!ShowMainWindow()) throw ERROR_SHOWMAINWINDOW;

}

catch (DWORD dwError)

{

printf(“Error code : %d”, dwError);

}



두 소스를 보자. 정상적인 경우 소스 1, 2, 3 모두 동일한 동작을 한다. 하지만 만약 CreateMainWindow() 를 실패했다고 하면 소스 1의 경우 Main Window가 없는데 그 윈도우에 SetInformationToMainWindow()를 수행하려고 한다. 이런경우에 응용프로그램 오류가 발생할 수 있다. 소스 2와 3의 경우는 실패했을경우 오류 메시지를 보이도록 해두었으니 문제는 없다.

이제 소스 2, 3을 보자. 소스 2 의 경우도 소스 3 처럼 오류코드를 받아서 오류 코드가 존재하면 메시지를 보이도록 처리할 수도 있다. 하지만 코드가 소스 3 에 비해 복잡해질 수 있다. 중첩된 if 구문의 사용이 너무 많아질 수 있다.(반드시 그렇지는 않다-_-)

또 다른 생각을 해보자. ShowMainWindow()를 수행한 뒤 또다른 작업을 수행하도록 하라는 지시가 내려왔다(-_-). 저 중첩 if 문 안에 또다른 if 구문을 넣을것인가? 다른 방법을 찾을것인가를 정해야한다. 선택하기 나름이지만 계속 복잡하게 되어가는 소스를 보고있으면 일하기도 싫어진다.

같은 상황을 소스 3 에 적용해보자. 새로 수행할 작업의 함수를 추가하고, 그 함수에 대한 오류코드를 추가하자. 그리고 try 블럭 내부에 그 작업을 수행하도록 하자. 비교는 스스로 해보시길 바란다.



2 - 1) try{} catch(...){}

위에서 얘기하지 않은 것이 있는데, catch(...) 의 정확한 사용법이다. 2) 의 예제 소스에서 보면 catch(DWORD dwError) 로 되어있는데 이것은 throw 로 발생시킨 예외 값이 DWORD 라는 의미이다. 그리고 throw 로 발생한 예외값이 DWORD 값이 아니라면 catch 블럭은 수행되지 않는다. 자기 자신을 필요로하는 예외가 아니라고 생각(-_-)하는 것이다.

그렇다면 예외로 반환될 수 있는 값이 여러가지라면 어떻게 해야하는지 알아보자. 위의 소스 3 을 예로 들어보겠다.



소스 3)

#define ERROR_STCRITICAL 1024

이 #define 은 리소스의 String Table에 있는 “크리티컬 오류발생” 이라는 문자열의 리소스 ID 라고 가정한다.

const DWORD ERROR_CREATEMAINWINDOW = 1;

const DWORD ERROR_SETINFOTOMAINWINDOW = 2;

const DWORD ERROR_SHOWMAINWINDOW = 3;



try

{

if(!CreateMainWindow()) throw ERROR_STCRITICAL;

if(!SetInformationToMainWindow()) throw ERROR_SETINFOTOMAINWINDOW;

if(!ShowMainWindow()) throw ERROR_SHOWMAINWINDOW;

}

catch (DWORD dwError)

{

printf(“Error code : %d”, dwError);

}



위와 같이 소스를 고쳐보았다. 메인윈도우를 생성할 수 없다는 것은 정말 큰일(-_-;Wink이기 때문에 메시지를 특별하게! 보여주기로 결정하였다. 하지만 리소스 ID 는 int 값으로 인식을 한다. 이제 ERROR_STCRITICAL 을 처리할 수 있는 catch 블럭을 만들어보자.

위 소스의 catch (DWORD dwError) 블록의 끝부분에 아래코드를 추가해보자.



catch (int nStringID)

{

char szErrorString[1024];

LoadString(AfxGetResourceHandle(), nStringID, szErrorString, 1024);

printf(szErrorString);

}



이렇게 해주시면 되겠다. 그럼 DWORD 값으로 발생한 예외는 catch(DWORD dwError)가 int 값으로 발생한 예외는 catch(int nStringID) 로 처리가 이루어진다.(실제로 DWORD 와 int 가 다르게 작동하는지는 확인해보지 않았다. 이것은 예제이다. 서로 다른 타입의 예외를 처리할 때 어떻게 하는지에 대한 간단한 설명을 위한 예제이다. 실제로는 DWORD와 int를 같이 인식할 수도 있다는 얘기다. 예제 상황임을 확실히 해두자-_-;;Wink



3) Unhandled Exception

위의 예제는 모두 Handled Exception에 관한 얘기이다. 예상할 수 있는 일들만 막아냈을 뿐이다. 이제는 우리가 예상하지 못했던 예외의 처리에 대해서 알아보자.



3 ? 1) SetErrorMode

SetErrorMode 라는 함수가 있다. 이 함수가 무슨일을 하느냐 하면.. 다시 얘기하지만 가장 쉽게볼 수 있는 Unhandled Exception 상황, 즉 “응용프로그램 오류가 발생하였습니다.”라는 삭막한 회색창이 있지 않던가? 그 창을 제어해주는 함수이다. 정확히는 제어가 아니라 보여줄까 말까를 설정할 수 있게 해준다.(-_-Wink 인자를 살펴보자. 인자를 0 으로 지정하면 삭막한 회색창을 보여준다(디폴트).



SEM_FAILCRITICALERRORS

“응용프로그램 오류”라고 하는 회색창(-_-)을 보여주지 않는다.

SEM_NOGPFAULTERRORBOX

NOGPFAULT 란다. NO General Protection FAULT 를 줄여놓은것이다. 일반 보호오류가 발생해도 회색창을 보여주지 않는다.

SEM_NOOPENFILEERRORBOX

혹시 본적이 있는지 모르겠지만, 파일을 오픈할 때 없는 파일을 오픈하면 오류가 발생한다. 회색이지만(-_-) “응용프로그램 오류” 같은 창이 아닌 메시지 박스가 뜬다(‘빈 파일에 엑세스할 수 없습니다.’라던가? 그 비슷한 메시지를 보여줬던 것 같다). 그걸 안보이게 해준다.



이 함수를 얘기하는 이유는 다음절에 나온다. 3 ? 2 로 넘어가보자.



3 ? 2) SetUnhandledExceptionFilter

자, 드디어 Unhandled Exception 의 본론이다. 이 함수가 무엇을 하는지 우선 알아보면, 위에서 우리가 열심히 막았던 예외를 제외한(다시한번 말하지만 Unhandled Exception 은 예상치 못한 예외(예외가 예상치 못한것이긴 하지만-_-;;Wink를 말한다.) 나머지 예외에 대한 처리를 할 수 있도록 도와(!)만 준다. 우선 인자를 살펴보자.



LPTOP_LEVEL_EXCEPTION_FILTER 를 인자로 받는데, 이게 뭘까?



LONG UnhandledExceptionFilter(STRUCT _EXECUTION_POINTERS *ExceptionInfo);



이렇게 정의가 되어있다.



그냥봐도 예외가 발생하면 저 함수를 호출해주는구나 할 수 있을것이다(-_-Wink. 자 이제 예외를 잡아보자.



아래 예제 소스는 MSJ의 Under the hood에 수록된 예제 소스를 기초로 수정하였습니다. MSJ에 있는 소스는 클래스로 잘 만들어져 있습니다. 간단하게 함수 한 개로 축소했을 뿐입니다.

소스)

우선 핸들러를 정의합니다.

LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)

{

PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;

printf(“Exception code : %08X”, pExceptionRecord->ExceptionCode)



PVOID addr = pExceptionRecord->ExceptionAddress;

TCHAR szModule[MAX_PATH];

DWORD len = sizeof(szModule);

DWORD section = 0;

DWORD offset = 0;

MEMORY_BASIC_INFORMATION mbi;



If(!VirtualQuery(addr, &mbi, sizeof(mbi))) return FALSE;

DWORD hMod = (DWORD)mbi.AllocationBase;



If(!GetModuleFileName((HMODULE)hMod, szModule, len)) return FALSE;



PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;

PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);

PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr);

DWORD rva = (DWORD)addr ? hMod;



for(unsigned int i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++)

{

DWORD sectionStart = pSection->VirtualAddress;

DWORD sectionEnd = sectionStart + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);



If((rva >= sectionStart) && (rva <= sectionEnd))

{

section = i + 1;

offset = rva ? sectionStart;



break;

}

}



printf(“Fault Address : %08X %02X:%08X %s”, pExceptionRecord->ExceptionAddress, section, offset, szModule);

}



핸들러를 설치해야겠죠.

LPTOP_LEVEL_EXCEPTION_FILTER lpPreviousFilter = NULL;

lpPreviousFilter = SetUnhandledExceptionFilter(UnhandledExceptionFilter);



종료될때는 이전 핸들러를 복구해주면 끝입니다.

SetUnhandledExceptionFilter(lpPreviousFilter);



이정도가 기본코드라고 할 수 있겠군요. 이제 예외가 발생하면 UnhandledExceptionFilter 를 호출해줄 것입니다. 예외가 발생하면 아래와같이 출력될 것입니다.



Exception code: C0000005

Fault address: 0183B877 01:0000A877 C:\XXXXXXXX.dll



C0000005 는 예외 코드입니다. 예외 코드 값은 아래와 같이 정의되어 있습니다.



#define EXCEPTION_ACCESS_VIOLATION STATUS_ACCESS_VIOLATION

#define EXCEPTION_DATATYPE_MISALIGNMENT STATUS_DATATYPE_MISALIGNMENT

#define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT

#define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP

#define EXCEPTION_ARRAY_BOUNDS_EXCEEDED STATUS_ARRAY_BOUNDS_EXCEEDED

#define EXCEPTION_FLT_DENORMAL_OPERAND STATUS_FLOAT_DENORMAL_OPERAND

#define EXCEPTION_FLT_DIVIDE_BY_ZERO STATUS_FLOAT_DIVIDE_BY_ZERO

#define EXCEPTION_FLT_INEXACT_RESULT STATUS_FLOAT_INEXACT_RESULT

#define EXCEPTION_FLT_INVALID_OPERATION STATUS_FLOAT_INVALID_OPERATION

#define EXCEPTION_FLT_OVERFLOW STATUS_FLOAT_OVERFLOW

#define EXCEPTION_FLT_STACK_CHECK STATUS_FLOAT_STACK_CHECK

#define EXCEPTION_FLT_UNDERFLOW STATUS_FLOAT_UNDERFLOW

#define EXCEPTION_INT_DIVIDE_BY_ZERO STATUS_INTEGER_DIVIDE_BY_ZERO

#define EXCEPTION_INT_OVERFLOW STATUS_INTEGER_OVERFLOW

#define EXCEPTION_PRIV_INSTRUCTION STATUS_PRIVILEGED_INSTRUCTION

#define EXCEPTION_IN_PAGE_ERROR STATUS_IN_PAGE_ERROR

#define EXCEPTION_ILLEGAL_INSTRUCTION STATUS_ILLEGAL_INSTRUCTION

#define EXCEPTION_NONCONTINUABLE_EXCEPTION STATUS_NONCONTINUABLE_EXCEPTION

#define EXCEPTION_STACK_OVERFLOW STATUS_STACK_OVERFLOW

#define EXCEPTION_INVALID_DISPOSITION STATUS_INVALID_DISPOSITION

#define EXCEPTION_GUARD_PAGE STATUS_GUARD_PAGE_VIOLATION

#define EXCEPTION_INVALID_HANDLE STATUS_INVALID_HANDLE



이제 실제로 오류가 발생한곳을 찾아봐야겠군요.



Fault address: 0183B877 01:0000A877 C:\XXXXXXXX.dll



0183B877 은 예외가 발생한 주소입니다.

0001:0000A877 은 예외가 발생한곳의 논리주소(Logical Address)입니다. 이 주소를 찾아가면 어디서 오류가 발생했는지를 알 수있습니다.

이 주소를 추적하기 위해서는 MAP이 필요합니다. 여러가지 정보를 많이 만들어주는데 그중에서 논리주소만 참조하면 되었던 것 같습니다. MAP 파일은 Project Settings -> Link -> Generate mapfile 을 체크하시면 Debug 혹은 Release 디렉토리에 *.map 파일이 만들어집니다. 그럼 *.map 파일을 열어보면...



XXXXXXXX



Timestamp is 3e1e58ab (Fri Jan 10 14:22:51 2003)



Preferred load address is 10000000



Start Length Name Class

0001:00000000 00017553H .text CODE

0001:00017553 00000bf0H .text$AFX_AUX CODE

0001:00018143 00000093H .text$AFX_COL1 CODE

0001:000181d6 000002abH .text$AFX_COL2 CODE

0001:00018481 00005db2H .text$AFX_CORE1 CODE

0001:0001e233 000008e7H .text$AFX_CORE2 CODE

0001:0001eb1a 0000051cH .text$AFX_CORE3 CODE

0001:0001f036 00000082H .text$AFX_CORE4 CODE

0001:0001f0b8 00001989H .text$AFX_INIT CODE

0001:00020a41 00000000H .text$AFX_OLE1 CODE

-------------------- 생략 ----------------------------------



Address Publics by Value Rva+Base Lib:Object



0001:00000000 ??0CFileEx@@QAE@XZ 10001000 f FileEx.obj

0001:00000016 ??_GCFileEx@@UAEPAXI@Z 10001016 f i FileEx.obj

0001:00000016 ??_ECFileEx@@UAEPAXI@Z 10001016 f i FileEx.obj

0001:00000032 ??1CFileEx@@UAE@XZ 10001032 f FileEx.obj

0001:00000076 ?ReadLine@CFileEx@@QAEKPAPAXK@Z 10001076 f FileEx.obj

-------------------- 생략 ----------------------------------



이런식의 알것도 같고 모를것도 같은 이상한것들이 마구 저장되어 있는 것을 볼 수 있습니다.



Fault address: 0183B877 01:0000A877 C:\XXXXXXXX.dll



이렇게 되어있으니까...0001:0000A877 에 가장 가까운곳을 Address에서 찾아봅니다. 쭉 찾아보니.. 이렇게 나오는군요..



0001:0000a719 ?OnClicked1@CXXXXXXXXDlg@@IAEJGGPAUHWND__@@AAH@Z 1000b719 f XXXXXXXX.obj

0001:0000a7e6 ?ControlEvent@CXXXXXXXXDlg@@IAEXXZ 1000b7e6 f XXXXXXXX.obj

0001:0000a947 ?GetTest@CXXXXXXXXDlg@@QAEKXZ 1000b947 f XXXXXXXX.obj



굵은글씨로 되어있는곳과 그 다음라인의 사이에 오류가 발생한 주소가 있습니다. 즉 굵은글씨로 되어있는 함수 내부에서 오류가 발생하였다는 것입니다. 이제 그 함수를 살펴보면 됩니다. 정확하게 집어내지는 못하더라도 어느 함수가 오류를 발생시켰는지는 알아낼 수 있습니다. 물론 디버그모드 릴리즈모드 공히 적용할 수 있습니다.



3 ? 3) Callstack

디버그를 하다보면 Callstack을 자주 이용하게 됩니다. 디버깅 하는데 아주 유용하죠. Unhandled Exception을 처리하는데도 오류가 발생한 곳의 Callstack을 볼 수 있다면 정말 좋을 것 같지 않으십니까?



소스)

BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, DWORD §ion, DWORD &offset)

{

MEMORY_BASIC_INFORMATION mbi;



if(!VirtualQuery(addr, &mbi, sizeof(mbi))) return FALSE;



DWORD hMod = (DWORD)mbi.AllocationBase;



if(!GetModuleFileName((HMODULE)hMod, szModule, len)) return FALSE;



PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;



PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);



PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr);



DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address



for(unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++)

{

DWORD sectionStart = pSection->VirtualAddress;

DWORD sectionEnd = sectionStart + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);



if((rva >= sectionStart) && (rva <= sectionEnd))

{

section = i + 1;

offset = rva - sectionStart;



return TRUE;

}

}



return FALSE;

}



UnhandledExceptionFilter 함수의 맨 아래쪽에 코드를 추가합니다.



PCONTEXT pCtx = pExceptionInfo->ContextRecord;

DWORD pc = pCtx->Eip;

PDWORD pFrame, pPrevFrame;



pFrame = (PDWORD)pCtx->Ebp;



do

{

TCHAR szModule[MAX_PATH] = _T(““);

DWORD section = 0, offset = 0;



GetLogicalAddress((PVOID)pc, szModule, sizeof(szModule), section, offset);

printf(“%08X %08X %04X:%08X %s”, pc, pFrame, section, offset, szModule);

pc = pFrame[1];

pPrevFrame = pFrame;

pFrame = (PDWORD)pFrame[0];



if((DWORD)pFrame & 3) break;

if(pFrame <= pPrevFrame) break;



if(IsBadWritePtr(pFrame, sizeof(PVOID) * 2)) break;

} while(TRUE);



휴.. 힘들군요 이거.. 이 소스도 MSJ에 수록된 IntelStackWalk 라는 함수의 소스입니다. 함수 이름에서 아실 수 있으시겠지만 x86 계열의 CPU에서만 동작합니다. 이 부분의 출력 결과는 대강 아래와 같습니다.



0183B877 0012F9D4 0001:0000A877 E:\XXXXXXX.dll

XXXXXXXX XXXXXXXX 0001:XXXXXXXX E:\XXXXXXX.dll



맨 윗줄이 예외를 발생시킨 위치이고, 아래로는 그 부분을 호출한 콜스택이 쭈욱 나열됩니다.



4) ...

몇가지 예외 처리에 대한 방법을 알아보았습니다. SetErrorMode 라는 함수를 설명한 것은 3 에서 설명한 Unhandled Exception Handler에서 “응용프로그램 오류”같은 회색상자가 아니라 IE 혹은 MSN 이 오류를 발생시켰을 때 보여주는 것 처럼 좀더 나은 인터페이스의 오류 화면을 보여줄 수 있기 때문입니다. 옵션을 끄지 않으면 그 회색상자(-_-;;Wink가 보이면서 같이 보이기 때문에 그다지 좋지는 않아보이더군요. 좀더 자세한 내용은 아래 링크를 참조 하시면됩니다.



http://www.microsoft.com/msj/defaultframe.asp?page=/msj/0497/hood/hood0497.htm
에러 코드가 가끔씩 나오면.. 이걸 해석하기 위하여.. 이것 저것 살펴보아야한다.
본인은 일반적으로 MFC UI쪽 과 MSSQL ODBC쪽 프로그램을 개발하므로.. 필요한 유틸리티를
만들었다.

시스템 에러아 ODBC에러의 코드를 입력하면 한글로 에러코드를 설명해주는 유틸리티이다.
별로 유용하진 않지만.. 그래도 가끔 쓰다보면 유용할 때가 있다.

비주얼 스튜디오에서 다이알로스 리소스를 편집하고 나서..
프로그램을 실행시키면 화면이 나오게 된다.

이 때 화면의 컨트롤끼리 이동을 할 때 TAB 및 shift + TAB를 이용하여 건너 다닐 수 있는데
여러번 수정하다 보면 순서가 바뀔때가 있다 이 때 어떻게 쉽게 원하는 순서대로 맞추어 줄 수 있는가?

리소스 편집상태에서 Ctrl + D를 눌러보자 그러면 다음과 같은 그림이 나올것이다.


이 상태에서 원하는 순서대로 마우스로 클릭하면 번호가 바뀌면서 1번부터 매겨지게 된다.
그럼 만사 오케~
가끔 다이알로그를 사용하다가, 참조된 소스를 보면,,,.
다이알로그에서는 볼수 없던 이벤트를 사용하는 것들이 보인다.

예를 들면
WM_ERAGEBKGND
WM_WINDOWSPOSCHANGED
WM_FONTCHANGE

이와 같은 메시지들을 다이알로그에서 자연스럽게 사용하려면
엄청난 고수가 되어 저걸 모두 외워서 사용해야하는가?

결론은 전혀 그렇지 않다이다.
저런 메시지가 존재한다는 것을 알고 있다면 일반 윈도우에서 사용하는 것과
별반다르지 않게 클래스 위저드를 이용하여 충분히 쉽게 사용이 가능하다.

1. 클래스 위저드를 띄운다.
2. Class name: 콤보에서 해당 원하는 다이알로그 클래스를 선택한다.
3. Class Info 탭을 선택한다.
그러면 하단부의 Advanced Options의 Message Filter란 항목이 보일 것이다.
이걸  Window로 변경하면 CWnd에 사용가능한 모든 메시지 항목이 메시지 맵에 보여지게 된다.



이렇게 메시지를 필터링해서 보여주는 이유는 너무 많은 메시지가 한번에 보여지게 되면..
시각적인 부담이 있고, 필요한 항목을 적절하게 검색하는데 시간이 걸리기도 하지만..
중요한 것은 각각의 옵션타입마다 그 클래스에서 고유하게 사용되는 이벤트들을 적절하게
필터링하여 보여주기 때문에 이런 기능이 존재하는거라 생각한다.

키워드 [Programming][VC++][Multimedia]
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7518&ref=7518

응용 프로그램에서 간단하게 소리 효과를 낼 경우 가장 간단하게 사용할 수 있는 수준의 API중에
PlaySound라는 것이 제공된다.

장점 : 사용하기 쉽다.
단점 : 제어 할 수 있는 부분이 거의??? 없다.


PlaySound()는 아주 간단하게 리소스나 파일의 wav폼 파일을 읽어서 재생할 수 있는 함수입니다.
단점이 싱크모드와 어싱크 모드로 재생이 가능한데.. 싱크모드로 돌리면 제어가 불가능하고..
어싱크모드로 돌리면 종료시 점을 알수 없다는데 있습니다.
 
아래의 기법을 이용하면 어싱크모드로 재생하면서 종료 시점을 알수 있고요..
Sleep에 의한 오차 100ms가 있긴 하지만.. 어쨋든...
 
원하는 소리를 원하는 시점에서 재생 및 중지를 시킬 수 있다는게 장점입니다.
 
// 소리를 재생하는 스레드입니다.
DWORD CALLBACK AAA(void* param)
{
   // 임시로 aaa.wav란 넘을 어싱크모드로 플레이 합니다.
    PlaySound("aaa.wav", NULL, SND_FILENAME | SND_ASYNC);
 
    while(1)
    {
        Sleep(100);
       
        // 재생이 끝날 때 까지 대기 합니다.
        // SND_NOSTOP 옵션이 있어서 현재 재생중일 때는 아래의 함수가 FALSE를 리턴함다.
        if(PlaySound(NULL, NULL, SND_PURGE | SND_NOSTOP | SND_NOWAIT | SND_SYNC))
            break;
    }
 
    return 0;
}

 
// 아무 곳에서나 현재 재생중인 음악을 중지 하시고 싶으시면.
// 음악을 중지시키면 당연히 위의 스레드도 바로 종료하게 됩니다.
PlaySound(NULL, NULL, SND_PURGE | SND_NOWAIT | SND_ASYNC);

키워드 [Programming][VC++][Security][open ssl]
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7598&ref=7598


윈도에서 OpenSSL을 사용할경우 인증서를 익스포트 시켜서
PEM 파일로 변환한 후 로드하는 과정이 필요하죠.

보통 제어판에서 인증서를 열어가지고 마우스로 콕콕 찍어서..
프라이빗 키 설정한 후 익스포트 하여 사용하곤 하는데..

귀찬죠.. 가끔 까먹기도 하죠.. 설명할라믄 메뉴얼 작성해야죠.. -_-
윈도 크립토 API를 사용하여 간단하게 샘플을 만들어 보았습니다.

//////////////////////////////////////////////////////////
#include <windows.h>
#include <wincrypt.h>
#include <cryptuiapi.h>
 
class CCertToPFX 
{
public:
    CCertToPFX();
    virtual ~CCertToPFX();
 
    void SetStoreName(CHAR* name);
    void SetCertName(CHAR* name);
    void SetSaveName(CHAR* name);
    void SetPrivatePassword(CHAR* name);
 
    BOOL Export();
 
    DWORD GetLastError();
 
protected:
    HCERTSTORE  m_hCertStore;       
    HCERTSTORE  m_hCertTemp;       
    CHAR        m_pszStoreName[256];
    CHAR        m_pszCertName[256];
    CHAR        m_pszSaveName[MAX_PATH];
    CHAR        m_pszPrivatePass[256];
    DWORD       m_dwLastError;
};


샘플 코드는 아래와 같습니다.
////////////////////////////////////////////////////
    CCertToPFX con;
    con.SetStoreName("MY");
    con.SetCertName("my_ssl");
    con.SetSaveName("C:\\test.pfx");
    con.SetPrivatePassword("my_password");
 
    if(!con.Export())
        con.GetLastError();

키워드 [Programming][VC++][Icon][Merge]
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=265&ref=192

이번에 쓰는 팁은 아주 간단한 겁니다... 파일이나 폴더 브라우져를 만드시는 분에게
꼭 필요한 팁이 아닐까 생각합니다.
강좌를 중단한건 엄청 잘못했지만 그래도 관련된 자료나 팁은 계속 올리겠습니다.

이번에 사용되는 팁은 아이콘을 표시할 때 공유되는 폴더나 바로가기 아이콘 처럼
원래의 아이콘에 오버레이된 이미지를 사용하여 원하는 아이콘을 만들어 사용하는
팁입니다.
디바이스 컨텍스나 이미지 처리를 하지않고 사용할 수 있는 간단한 방법이지요... ^^;;;

여기서는 바로가기 아이콘을 흉내낸 처리루틴입니다.

// hiFolder는 원본 아이콘
HICON GetLinkedIcon(HICON hiFolder)
{
    // 리턴할 아이콘
    HICON hiShared;
    // 바로가기의 쪼그만 사각형에 화살표가 들은 아이콘을 저장할 핸들
    HICON hiHand;

    // 바로가기의 쪼그만 사각형 아이콘 핸들을 얻어온다.
    ExtractIconEx("shell32.dll", 29, &hiHand, NULL, 1);

    // 주어진 플래그대로 HIMAGELIST 를 만든다.
    HIMAGELIST himl = ImageList_Create(32, 32, ILC_MASK, 1, 0);

    // 아이콘의 Merge 처리를 위하여 이미지 리스트에 추가한다.
    ImageList_AddIcon(himl, hiFolder);
    ImageList_AddIcon(himl, hiHand);

    // 두개의 아이콘을 Merge 시킨다.
    HIMAGELIST himlNew = ImageList_Merge(himl, 0, himl, 1, 0, 0);

    // 이미지 리스트에서 Merge된 아이콘을 찾아낸다.
    hiShared = ImageList_ExtractIcon(0, himlNew, 0);

    // 다쓴 아이콘 지우기
    DestroyIcon(hiHand);

    // 다쓴 이미지 리스트 지우기
    ImageList_Destroy(himl);
    ImageList_Destroy(himlNew);

    // 완성된 아이콘 돌려주기
    return hiShared;
}

키워드 [Programming][VC++][Security]
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=5736&ref=5736

사용 환경..
Windows NT/2000: Requires Windows 2000 (or Windows NT 4.0 SP6a or later with DSClient).
Windows 95/98: Requires Windows 95/98 (with IE 4.01 or later and DSClient). Not supported on Windows Me.
Header: Declared in Adshlp.h, Iads.h
Library: Use ActiveDS.Lib, ADSIid.Lib, Adptif.Lib


코드

#include <Iads.h>
#include <Adshlp.h>

HRESULT CheckUserGroups(IADsUser *pUser)
{
    IADsMembers *pGroups;
    HRESULT hr = S_OK;
    hr = pUser->Groups(&pGroups);
    pUser->Release();
    if (FAILED(hr)) return hr;

    IUnknown *pUnk;
    hr = pGroups->get__NewEnum(&pUnk);
    if (FAILED(hr)) return hr;
    pGroups->Release();

    IEnumVARIANT *pEnum;
    hr = pUnk->QueryInterface(IID_IEnumVARIANT,(void**)&pEnum);
    if (FAILED(hr)) return hr;

    pUnk->Release();

    // Now Enumerate
    BSTR bstr;
    VARIANT var;
    IADs *pADs;
    ULONG lFetch;
    IDispatch *pDisp;

    VariantInit(&var);
    hr = pEnum->Next(1, &var, &lFetch);
    while(hr == S_OK)
    {
        if (lFetch == 1)
        {
             pDisp = V_DISPATCH(&var);
             pDisp->QueryInterface(IID_IADs, (void**)&pADs);
             pADs->get_Name(&bstr);
             printf("Group belonged: %S\n",bstr);
             SysFreeString(bstr);
             pADs->Release();
        }
        VariantClear(&var);
        pDisp=NULL;
        hr = pEnum->Next(1, &var, &lFetch);
    };
    hr = pEnum->Release();
    return S_OK;
}

IADsUser *GetUserObject(LPWSTR uPath)
{
    IADsUser *pUser;
    HRESULT hr = ADsGetObject(uPath,IID_IADsUser,(void**)&pUser);
    if (FAILED(hr)) {return NULL;}
    BSTR bstr;
    hr = pUser->get_FullName(&bstr);
    printf("User: %S\n", bstr);
    SysFreeString(bstr);
    return pUser;
}

void main()
{
    HRESULT hr = CoInitialize(NULL);
    IADsUser *pUser = (IADsUser*)GetUserObject(
                      L"WinNT://KWONJH/Rkakr,user");
    pUser->AddRef();
    hr = CheckUserGroups(pUser);
    if(pUser) pUser->Release();
    CoUninitialize();
}

예제 설명..
위이 상단 두 함수는 MSDN을 참조하여 구성한 것이고..
아래의 main 문에서 실제 정보를 추출하는데 필요한 GetUserObject()에 전달되는 인자 타입에 대하여 설명한다.

IADsUser *pUser = (IADsUser*)GetUserObject( L"WinNT://KWONJH/Rkakr,user");

WinNT://KWONJH/Rkakr,user

여기서 WinNT는 NT커널임을 이야기 한다.
JWONJH는 컴터의 이름이다... 혹은 다른 컴터의 이름이나 도메인 이름이 될 수 있다.
Rkakr 는 사용자의 유저 이름이다.

혹시나 이것을 지금 로그온 한 사람의 유저 이름으로 할려면 간단하게..
char *logname =getenv("USERNAME");
이렇게 읽어 오면 된다.
user 요건은 사용자를 말하며.. 컴터를 찾을 경우는 computer라고 입력하면 된다.

실제로 위의 정보가 맞다면..

CheckUserGroups 함수 중간에 있는 프린트 문에서..
현 정보가 주어진 사용자가 속한 그룹의 리스트가 쭈~~욱 프린트 될것이다.

테스트 환경
Windows XP SP1/ Windows 2000 SP2,  VC++6.0 SP5

위의 코드를 돌려 보니 아래와 같은 결과가 나왔습니다.

User: Rkakr
Group belonged: Administrators
Group belonged: Remote Desktop Users
Group belonged: Users


그럼 즐거운 주말 되세욤 ^^;

키워드 [Programming][VC++][Base64]
걍 라이브러리로 만들어 놓은 것 중에서.. 일부를 뽑아 소스를 올린다.



/////////////////////////////////////////////////////////////
// base64 인코딩/디코딩 및 제거
// base64.h
K4LIB_BASIC_API unsigned char* base64_encode(const unsigned char *string, int length, int *ret_length);
K4LIB_BASIC_API unsigned char* base64_decode(const unsigned char *string, int length, int *ret_length);
K4LIB_BASIC_API void base64_free(unsigned char* data);




/////////////////////////////////////////////////////////////
// base64 인코딩/디코딩 및 제거
// base64.cpp
static char base64_table[] =
{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0'
};
static char base64_pad = '=';

unsigned char *base64_encode(const unsigned char *string, int length, int *ret_length)
{
    const unsigned char *current = string;
    int i    = 0;
    int    wi    =    0;
    int        nAllocSize    =    ((length + 3 - length
  % 3) * 4 / 3 + 1) * sizeof(char);
    nAllocSize    +=    (nAllocSize/80+1)*2;
    unsigned char *result = new unsigned char[nAllocSize];
 
    while (length > 2)
    { /* keep going until we have less than 24 bits */
        result[i++] = base64_table[current[0] >> 2];
        result[i++] = base64_table[((current[0] & 0x03) <<  4)
   + (current[1] >> 4)];
        result[i++] = base64_table[((current[1] & 0x0f) <<  2)
   + (current[2] >> 6)];
        result[i++] = base64_table[current[2] & 0x3f];
 
        current += 3;
        length -= 3; /* we just handle 3 octets of data */
        wi+=4;
 
/*        if(wi%80 == 0)
        {
            result[i++] = '\r';
            result[i++] = '\n';
        }
*/    }
 
    /* now deal with the tail end of things */
    if (length != 0)
    {
        result[i++] = base64_table[current[0] >> 2];
        if (length > 1)
        {
            result[i++] = base64_table[((current[0] &
    0x03) <<  4) + (current[1] >> 4)];
            result[i++] = base64_table[(current[1] &
    0x0f) <<  2];
            result[i++] = base64_pad;
        }else
        {
            result[i++] = base64_table[(current[0] &
    0x03) <<  4];
            result[i++] = base64_pad;
            result[i++] = base64_pad;
        }
    }
    if(ret_length)
    {
        *ret_length = i;
    }
    result[i] = '\0';
    return result;
}

/* as above, but backwards. :) */
unsigned char *base64_decode(const unsigned char *string, int length, int *ret_length)
{
    const unsigned char *current = string;
    int ch, i = 0, j = 0, k;
    char *chp;
 
    unsigned char *result = new unsigned char[(length / 4 *3 + 1) * sizeof(char)];
    if (result == NULL) {
        return NULL;
    }
 
    /* run through the whole string, converting as we go */
    while ((ch = *current++) != '\0') {
        if (ch == base64_pad) break;
        chp = strchr(base64_table, ch);
        if (chp == NULL) continue;
        ch = chp - base64_table;
 
        switch(i % 4) {
        case 0:
            result[j] = (unsigned char)(ch <<  2);
            break;
        case 1:
            result[j++] |= ch >> 4;
            result[j] = (unsigned char)((ch & 0x0f) <<  4);
            break;
        case 2:
            result[j++] |= ch >>2;
            result[j] = (unsigned char)((ch & 0x03) <<  6);
            break;
        case 3:
            result[j++] |= ch;
            break;
        }
        i++;
    }
 
    k = j;
    /* mop things up if we ended on a boundary */
    if (ch == base64_pad) {
        switch(i % 4) {
        case 0:
        case 1:
            delete [] result;
            return NULL;
        case 2:
            k++;
        case 3:
            result[k++] = 0;
        }
    }
    if(ret_length) {
        *ret_length = j;
    }
    result[k] = '\0';
    return result;
}

void base64_free(unsigned char* data)
{
 delete[] data;
}


키워드 [Programming][VC++][MD5]
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=6446&ref=6446

많이 사용되는 MD5 기법을 MFC에서 사용하기 간단한 클래스로 구현한 자료를 올립니다..
편하게 사용하세요.. 라이센스?? 던주면 감사히 받구요..
 
MD5가 머냐고 의문이 생기시는 분은 관련분야에 종사하시면 레퍼런스를 참고하시구요, 관련 분야가 아니시면, 걍 암호화의 한 부분이라고 생각하시면 됩니다.... 복잡하게 생각하면 머리 터져요 ^^;
 
간단히 헤더만 정리해 보면...
 
// MD5.h: interface for the CMD5 class.
//
//////////////////////////////////////////////////////////////////////
 
#if !defined(AFX_MD5_H__1E84B7F9_E0C0_4075_9CB1_3366A8363F48__INCLUDED_)
#define AFX_MD5_H__1E84B7F9_E0C0_4075_9CB1_3366A8363F48__INCLUDED_
 
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
 
class CMD5 
{
    typedef struct
    {
        UINT state[4];                                   /* state (ABCD) */
        UINT count[2];        /* number of bits, modulo 2^64 (lsb first) */
        BYTE buffer[64];                         /* input buffer */
    } MD5_CTX;
 
public:
    CMD5();
    virtual ~CMD5();
    CString GetString(CString str);
 
protected:
    void MD5Init(MD5_CTX* ctx);
    void MD5Update(MD5_CTX* ctx, BYTE* input, UINT inputlen);
    void MD5Final(BYTE* digest, MD5_CTX* ctx);
};
 
#endif // !defined(AFX_MD5_H__1E84B7F9_E0C0_4075_9CB1_3366A8363F48__INCLUDED_)

코드를 보시면 알겠지만.. 걍 선언해놓구.. GetString() 함수만 호출하는 간한단 구조로 되어 있습니다..
샘플 코드

CMD5 md5;
TRACE("%s\n", md5.GetString("dfsafafadffsdfsfsf"));

Output : 9632F1DC3D8E35BFF71D1D86E3E1EB9D

그럼 즐프 하세요..

+ Recent posts