double angle(CPoint Cen,CPoint First,CPoint Second)
{
CClientDC dc(this);
double lega1,lega2,legb1,legb2;
double norm,norm1,norm2,angle,prod,curl;
int x1,y1,x2,y2,x3,y3;

x2 = Cen.x; y2 = Cen.y;
x1 = First.x; y1 = First.y;
x3 = Second.x; y3 = Second.y;

lega1 = x1-x2;
legb1 = y1-y2;
lega2 = x3-x2;
legb2 = y3-y2;

norm1 = sqrt(lega1*lega1+legb1*legb1);//두 벡터의 크기
norm2 = sqrt(lega2*lega2+legb2*legb2);//두 벡터의 크기
norm = norm1 * norm2;
prod = (lega1*lega2)+(legb1*legb2);//두 벡터의 내적
angle = acos(prod/norm);

curl = (lega1*legb2)-(legb1*lega2);//두 벡터의 외적

if (curl<=0) return angle/3.141592654*180;

else return (360-angle/3.141592654*180);
}
class SC_Rotate_Line
{
public:
dxy_t sc_Begin, sc_End;
double sc_Angle;
dxy_t sc_LeftPoint;
public:
SC_Rotate_Line() {}
SC_Rotate_Line(dxy_t Begin,dxy_t End, float Angle, CDC* pDC)
{
sc_Begin = Begin;
sc_End = End;
sc_Angle = -Angle/180*PI;

sc_LeftPoint.x = cos(sc_Angle)*(sc_End.x - sc_Begin.x) - sin(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.y = sin(sc_Angle)*(sc_End.x - sc_Begin.x) + cos(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.x = sc_LeftPoint.x + sc_Begin.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_Begin.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
}
SC_Rotate_Line(double Beginx,double Beginy,double Endx,double Endy ,float Angle, CDC* pDC)
{
sc_Begin.x = Beginx;
sc_Begin.y = Beginy;
sc_End.x = Endx;
sc_End.y = Endy;
sc_Angle = -Angle/180*PI;

sc_LeftPoint.x = cos(sc_Angle)*(sc_End.x - sc_Begin.x) - sin(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.y = sin(sc_Angle)*(sc_End.x - sc_Begin.x) + cos(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.x = sc_LeftPoint.x + sc_Begin.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_Begin.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
}
SC_Rotate_Line(CPoint Begin, CPoint End, float Angle, CDC* pDC)
{
sc_Begin.x = Begin.x;
sc_Begin.y = Begin.y;
sc_End.x = End.x;
sc_End.y = End.y;
sc_Angle = -Angle/180*PI;

sc_LeftPoint.x = cos(sc_Angle)*(sc_End.x - sc_Begin.x) - sin(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.y = sin(sc_Angle)*(sc_End.x - sc_Begin.x) + cos(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.x = sc_LeftPoint.x + sc_Begin.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_Begin.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
}
CPoint SC_Return(CPoint Begin, CPoint End, double Angle)
{
sc_Begin.x = Begin.x;
sc_Begin.y = Begin.y;
sc_End.x = End.x;
sc_End.y = End.y;
sc_Angle = -Angle/180*PI;

sc_LeftPoint.x = cos(sc_Angle)*(sc_End.x - sc_Begin.x) - sin(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.y = sin(sc_Angle)*(sc_End.x - sc_Begin.x) + cos(sc_Angle)*(sc_End.y - sc_Begin.y);
sc_LeftPoint.x = sc_LeftPoint.x + sc_Begin.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_Begin.y;

return CPoint(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
}
};

예전 학창시절에 만들었던건데.. 자료가 있길래 올려본다.

class SC_Arrow
{
public:
dxy_t sc_Begin, sc_End;
double sc_Angle;
dxy_t sc_LeftPoint, sc_RightPoint;
dxy_t sc_Increasement;
public:
SC_Arrow() {}
SC_Arrow(dxy_t Begin,dxy_t End,CDC* pDC,float Angle = 20.)
{
sc_Begin = Begin;
sc_End = End;
sc_Angle = Angle/180*PI;

sc_Increasement.x = (sc_End.x - sc_Begin.x)*(-1./10.);
sc_Increasement.y = (sc_End.y - sc_Begin.y)*(-1./10.);


sc_LeftPoint.x = cos(sc_Angle)*sc_Increasement.x - sin(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.y = sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.x = sc_LeftPoint.x + sc_End.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_End.y;
sc_RightPoint.x = cos(sc_Angle)*sc_Increasement.x + sin(sc_Angle)*sc_Increasement.y;
sc_RightPoint.y = -sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_RightPoint.x = sc_RightPoint.x + sc_End.x;
sc_RightPoint.y = sc_RightPoint.y + sc_End.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
pDC->MoveTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_RightPoint.x+0.5),long(sc_RightPoint.y+0.5));
}
SC_Arrow(CPoint Begin,CPoint End,CDC* pDC,float Angle = 20.)
{
sc_Begin.x = Begin.x;
sc_Begin.y = Begin.y;
sc_End.x = End.x;
sc_End.y = End.y;
sc_Angle = Angle/180*PI;

sc_Increasement.x = (sc_End.x - sc_Begin.x)*(-1./10.);
sc_Increasement.y = (sc_End.y - sc_Begin.y)*(-1./10.);


sc_LeftPoint.x = cos(sc_Angle)*sc_Increasement.x - sin(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.y = sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.x = sc_LeftPoint.x + sc_End.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_End.y;
sc_RightPoint.x = cos(sc_Angle)*sc_Increasement.x + sin(sc_Angle)*sc_Increasement.y;
sc_RightPoint.y = -sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_RightPoint.x = sc_RightPoint.x + sc_End.x;
sc_RightPoint.y = sc_RightPoint.y + sc_End.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
pDC->MoveTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_RightPoint.x+0.5),long(sc_RightPoint.y+0.5));
}
SC_Arrow(double Beginx,double Beginy,double Endx,double Endy ,CDC* pDC,float Angle = 20.)
{
sc_Begin.x = Beginx;
sc_Begin.y = Beginy;
sc_End.x = Endx;
sc_End.y = Endy;
sc_Angle = Angle/180*PI;

sc_Increasement.x = (sc_End.x - sc_Begin.x)*(-1./10.);
sc_Increasement.y = (sc_End.y - sc_Begin.y)*(-1./10.);


sc_LeftPoint.x = cos(sc_Angle)*sc_Increasement.x - sin(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.y = sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_LeftPoint.x = sc_LeftPoint.x + sc_End.x;
sc_LeftPoint.y = sc_LeftPoint.y + sc_End.y;
sc_RightPoint.x = cos(sc_Angle)*sc_Increasement.x + sin(sc_Angle)*sc_Increasement.y;
sc_RightPoint.y = -sin(sc_Angle)*sc_Increasement.x + cos(sc_Angle)*sc_Increasement.y;
sc_RightPoint.x = sc_RightPoint.x + sc_End.x;
sc_RightPoint.y = sc_RightPoint.y + sc_End.y;

pDC->MoveTo(long(sc_Begin.x+0.5), long(sc_Begin.y+0.5));
pDC->LineTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_LeftPoint.x+0.5), long(sc_LeftPoint.y+0.5));
pDC->MoveTo(long(sc_End.x+0.5), long(sc_End.y+0.5));
pDC->LineTo(long(sc_RightPoint.x+0.5),long(sc_RightPoint.y+0.5));
}
};

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
이 장은 SHLoadInProc 라는 강력한 함수로 인하여
우리가 얼마나 쉽게 윈도우즈 쉘의 메모리안에 접근이 가능한지를 보여준다.

여기에 논의되는 대부분읜 코드는 Dll로 설계된 COM 파일이며,
기본적인 내용을 제외하고는 아주 간단한 코드가 추가될 뿐이다.

여러분들의 최소한의 COM 오브젝트를 만들기 위하여
다음과 같은 함수를 생서하여 주어야 한다.

프로젝트의 생성은 MFC에서 Win32 Dynamic Link Library로
생성하였으며, Blank로 만들면 된다.

여기에 적당한 헤더들을 연결하고..

DllGetClassObject()
DllCanUnloadNow()
DllRegigterServer()
DllUnregisterServer()

각각의 소스 구현은 첨부파일에 있으며.. 여기서는 위의
4개의 함수의 필요성과 구현에 대한 개념을 설명한다.

이글을 쓰고 있는 필자는 COM에 컴자도 모르고, ATL도 모른다.
단지 Shell Programming를 공부해가며 위의 개념이 나오므로 그냥 하나의 프로그램 코드로 이해하며 넘어가고 있다.

1. DllGetClassObject() 함수

STDAPI DllGetClassObject(
REFCLSID rclsid,
REFIID riid,
LPVOID* ppv)

COM객체의 클라이언트는 어떤 것이든지 먼저 COM 객체를 포함하고 있는 라이브러리를 로드시켜야 한다. 그리고 나서, DllGetClassObject() 통해 그것이 필요로 하는 인터페이스 포인터를 얻는다. 자세한 것은 필요없고 중요한 것은 이 함수가 항상 호출된다는 것이다. 그리고 바로 그 클래스 객체가 로드된다. 풀어서 설명하면 이 COM 객체를 로딩하는 놈이 있으면 그 로딩하는 클라이언트에 의해서
DllGetClassObject() 함수가 호출되어진다는 것이다.
그리고 내가 만든 이 COM 객체는 나를 호출하는 클라이언트의 메모리 않에서 동작하게 된다.

대게 모듈들이 객체를 읽어 들일경우 DllGetClassObject를 호출하면서 IClassFactory 인터페이스란걸 요청한다.
하지만 우리는 (혹은 나만) COM이 먼지 모르므로 그러한 요청에 응답할 수 없다고 답변하면 된다.

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return CLASS_E_CLASSNOTAVAILABLE;
}


2. DllCanUnloadNow의 역활

DllGetClassObject를 이용하여 COM 객체를 읽어들인 모듈은DllCanUnloadNow()를 호출하여 확실하게 그 DLL이 안전하게 메모리에서 사라지도록 해야한다.
즉 위의 함수가 S_OK를 리턴하면 메모리상에서 제거할 수 있는 것이다.

STDAPI DllCanUnloadNow()
{
return S_OK;
}

3. 그리고 나머지
DllRegigterServer()
DllUnregisterServer() 는 regsvr32.exe를 이용하여 이
COM 객체를 등록하고 해지하는 역활을 담당한다.

뭐 구지 사용하기 귀찬으면 REG파일을 만들어도 된다.

지금 까지가 설명한 중요한 것은 다 넘아갔다.
우스을지 모르지만 나머지 코딩은 그냥 APP 프로그램과
동일하다.

즉, COM으로 프로젝트를 만든다는 것과 위에서 설명한 두 함수의 대강의 용법만 깨우치면 우리는 쉘에 침략할 수 있다.

참고로 위에서 설계한 COM 객체를 등록는 것은 걍하고..
구동하는 것은

임의의 어플리케이션이나 머 아무거나 만들어서..
const CLSID clsid = {0x????????, 0x????, 0x????,
{0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??}};

SHLoadInProc(clsid);

이렇게 구동하면 된다...

코드가 쫌 길어서 짜증나지만.. 정말 도전해볼만한 프로그램이다.

지금 다루고 있는 SHLoadInProc() 함수를 이용한 쉘에 대한 도전과 생략한 기법중에 하나가 Hook 이다..
Hoo에 대한 설명은 앞으로도 생략할 것이고..

이 장을 넘어가면 Browser Helper Object (BHO)라는 것을
이용하여 좀더 용이하고 좀더 확장된 기능으로 쉘에 대한 침략을 할것이다. 이를 위하여 위에서 설명한 최소한의 COM 객체를 구혈할 수 있어야 하며, 그로 인한 동작을 직접 코딩하여 동작 시켜보아야할 필요가 있다.

^^;

Windows 95의 출현과 함께 문서의 개념이 중요성을 띠게 되었다. 이제는 실행파일이라는 개념이 좀더 복잡해 지고 단순히 구동한다는 의미를 떠나 아주 방대한 개념으로 자리 잡고 있다.
문서라고 하는 것은 시스템의 네임스페이스의 일부인 보다 일반적인 객체를 말하고자 하며, 이문서에 대하여 '열기(open)', '인쇄(print)', '탐색(explore)', '찾기(find)'를 하는 프로그램이 있다. 다시 말해서, 문서라는 것은 그것에 대해서 프로그램이 어떤 동사(Verb)를 실행할 수 있는 모든 아이템을 말한다.

지금의 프로그램 실행자의 모체였던 WinExec()에서 ShellExecuteEx()라는 함수로 그 진행이 옮겨가는 이유도 이해 따른다.

이 장에서는 다음과 같은 것들을 다룰 것이다.
1. WinExec()와 CreateProcess() 사이의 차이점
2. ShellEcecute()와 ShellExecuteEx()가 다른 함수보다 우수한점
3. 동사들(Verb), 문서들 그리고 정책들(Policy)
4. 훅킹을 사용하여 프로세스 실행을 내 마음대로

그리고 다음과 같은 몇가지 예제 코드를 살펴볼 것이다.
1. 디폴트 브라우져 감지법
2. 프로그램을 실행시키고 종료를 기다리는법
3. 어떤 파일에 대한 등록정보 대화상자를 나타내는법
4. 찾기 대화상자를 화면에 출력하는 법
5. 사용자가 특정 폴더를 액세스하지 못하게 하거나, 다른 특정 어플리케이션을 실행하지 못하게 막는법

=====================================================

1. WinExec()에서 CreateProcess()로...

UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow);
이 함수는 Windows 3.x에서는 외부 프로그램을 실행시키는 유일한 방법이었다. 그리고 가장 간단한 사용법을 가지기도 한다.
하지만 단점이라 불리울 수 있는 것이 일단 실행을 시켜 놓으면 실행이 되는지 에러가 났는지, 종료 되었는지 전혀 알수가 없다는 것이다.

다음은 CreateProcess()의 프로토 타입이다.
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 실행파일 모듈의 이름에 대한 포인터
LPTSTR lpCommandLine, // 커맨드 문자열에 대한 포인터
LPSECURITY_ATTRIBUTES lpPA, // 프로세스 보안 속성 포인터
LPSECURITY_ATTRIBUTES lpTA, // 스레드 보안속성 포인터
BOOL bInheritHandles, // 핸들 상속 여부 플래그
DWORD dwCreationFlags, // 생성 플래그
LPVOID lpEnvironment, // 환경 블록에 대한 포인터
LPCTSTR lpCurrentDirectory, // 현재 디렉토리
LPSTARTUPINFO lpStartupInfo, // STARTUPINFO 포인터
LPPROCESS_INFORMATION lpPI // PROCESS_INFORMATION 포인터
);

보시다 시피 여기에는 많은 파라미터들이 있다 하지만.. 대부분의 내용이 MSDN에 잘 문서화가 되어 있으므로 그렇게 어렵다거나 하지는 않다.
일단 가장 간단한 호출을 한번 살펴보자.

STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));

CreateProcess(NULL, szPrgName, NULL, NULL, TRUE, NORMAL_PRIORITY_CLSS, NULL, NULL, &si, &pi);

WinExec보다는 복잡하지만 여러가지 부가적인 정보를 줄 수도 있고 받을 수도 있으므로 상대적인 저비용이다.

만약 위의 프로그램을 실행시키고 종료하기를 기다린다고 하면..

BOOL b = CreateProcess(............);
if(!b)
return FALSE;

WaitForSingleObject(pi.hProcess, INFINITE);
return TRUE;

정말 간단하지 않는가? ^^;
위의 결과를 외견상으로 보면 WinExec의 문제점들의 CreateProcess() 함수로써 모두 해결했다는 생각을 할 수도 있겠지만 현재의 문서라는 개념은 좀더 일반화 되었다. 위에서 사용된 실행가능한 프로그램(exe, com ...etc)등은 문서의 한가지 유형일 뿐이다.


2. ShellExecute()와 ShellExecuteEx()가 다른 함수보다 우수한점

HINSTANCE ShellExecute(
HWND hwnd, // 부모 윈도우 핸들
LPCTSTR lpVerb, // 동사 혹은 작업
LPCTSTR lpFile, // 실행 대상 문서
LPCTSTR lpParameters, // 컴맨드 인자
LPCTSTR lpDirectory, // 현재 디렉토리
INT nShowCmd // 보여주기 옵션
);

일단 위의 함수를 살펴보면 CreateProcess() 함수보다 기능이 많이 떨어지는 것 처럼 보이지만 이 함수의 진정한 의미는 동사 연결이라는 점이다.
보통 우리가 말하는 [연결 프로그램]을 지칭한다.

위에서 [동사 혹은 작업] 부분의 LPCTSTR lpVerb 에 사용될 수 잇는 것은데 대한 나열이다.
===============================================
열기(open) - 프로그램/문서/폴더
탐색(explore) - 폴더
인쇄(print) - 문서
~~로 인쇄(printto) - 문서
찾기(find) - 폴더

------> 먼저 열기(open)에 대하여 살펴보자.

ShellExcute(hwnd, "open", "c:\\prog.exe", "/nologo", "d:\\", SW_SHOW);

ShellExcute(hwnd, "open", "c:\\file.txt", NULL, NULL, SW_SHOW);

만약 txt확장자를 가진 파일에 대한 연결이 존재하지 않으면 우리가 많이 보던 연결프로그램 대화상자가 나타날 것이다.

------> 탐색 작업

탐색 작업은 폴더에 대해서만 가능하고 open 인경우와 약간의 차이를 나타낸다.
open로 동사를 주면 pane이 하나로 된 창이 뜨고, explore로 주면 pane가 2개인 탐색기다 뜬다.

ShellExcute(hwnd, "expolre", "c:\\", NULL, NULL, SW_SHOW);

------> 인쇄 작업

인쇄작업은 문서를 인쇄하기 위한 것이지만, 지정된 문서를 인쇄할 수 있는 명령어 라인과 프로그램을 정확히 알나내기 위해 레지스트리에 저장된 정보에 의존한다.

ShellExcute(hwnd, "print", "c:\\file.txt", NULL, NULL, SW_SHOW);

이 함수는 텍스트 파일을 처리하기 위해 등록된 프로그램을 찾고, 그 프로그램에 인쇄를 위한 명령이 있는지 검사한다. 정상적이라면 이 프로그램은 notepad.exe가 될 것이고, 그 명령어 라인은 다음과 같다.

notepad.exe /p

디폴트 프린터로의 인쇄는 위에서 처럼 간단하게 사용이 가능하지만 만약 여러개의 프린터가 있거나 출력 포트를 설정하고 싶다면 printto를 사용해야 한다.

만약 printto가 문서에서 지원이 된다면 등록된 명령이 실행될것이고 그렇지 못하다면..
경고창이 뜨면서 디폴트 프린터로 출력할 것이지를 묻는다.

------> 찾기 작업

지정된 경로를 루트로 찾기 창이 생성된다.
예제는 생략한다. ^^;


그렇다면 파일을 열기 위해서 혹은 그 파일과 연결된 실행 프로그램은 어떻게 구할 수 있는가?
의외로 쉽게 구할 수 있다. SDK에서 API를 제공하니까 ^^;

HINSTANCE FindExecuteable(
LPCTSTR lpFile, // 알아볼 파일
LPCTSTR lpDir, // 경로
LPTSTR lpResult) // 찾은 결과값

하지만 위 함수에는 치명적인 결함이 있다.
1. 보통 연결 프로그램은 확장자를 기준으로 검색되지만 이 함수는 항상 파일이 존재하여야만 결과를 리턴한다.
2. 경로에 공백이 있어도 않된다.
3. 리턴하는 결과에도 공백이 있으면 잘린다.

한마디로 엉터리에 가까운 API라 말할 수 있다.
이렇듯 이 함수는 규칙없이 긴 이름을 가진 파일을 염두에 두지 않고 설계되었기 때문에 생기는 문제인데..
MS에서 알고 있지만 절대 고치지 않는다..
Windows 이후로 긴 이름의 파일을 지원하지만..
MS Windows에서 시스템 명령어 중에 8.3포멧을 지키지 않는 명령어는 아직까지도 존재하지 않는다.

이 문제를 바로잡기 위한 FindExecutableEx를 만들어보자.
HINSTANCE FindExecutableEx(... 인자는 동일하다 ...)
{
TCHAR drive[_MAX_DRIVE];
TCHAR dir[_MAX_DIR];
TCHAR dir1[_MAX_DIR];
TCHAR file[_MAX_FILE];
TCHAR ext[_MAX_EXT];

HINSTANCE hi = FindExecutable(file, dir, result);
result[lstrlen(result)] = 32;

_splitpath(result, deive, dir, file, ext);

LPTSTR p = strchr(dir, ':');
if(p != NULL)
{
--p;
dir[p-dir] = 0;
_splitpath(dir, NULL, dir1, file, ext);
p = strchr(ext, 32);
ext[p-ext] = 0;
_makepath(result, drive, dir1, file, ext);
}
return hi;
}
이전에 설명했듯이 SHLoadInProc를 사용해서 침략을 할경우는 먼저 이것을 로딩해주는 프로그램이 존재해야했다.

하지만 BHO를 이용해서 하면 우리가 만든 객체를 레지스트리에 등록해주고 나면 끝이다.
탐색기나 인터넷 익스프롤러의 인스턴스가 생성될때마다 레지스트리의 BHO 부분을 읽어서 객체를 로딩하고 인스턴스가 소멸될때 로딩한 객체도 소멸된다.

먼저 ATL Wirzard로 DLL 골격을 만든다.

그리고, 메뉴에서 Insert -> New ATL Object를 선택한다.

그리고 Simple Object를 선택한후 NewFolder로 심플 오브젝트를 만들고 샘플의 소스를 삽입한다.

일단 이러한 인련의 과정을 거쳐서 객체를 생성한다.

나머지는 소스를 참고하고..
실제 구동에서 Windows NT 환경과 Windows 9X 환경에서도
동작은 동일하지만..
문자열의 처리가 틀리므로..
9x 계열에서는 ANSI로
NT 계열에서는 Unicode 형태로 선택해야 한다.

그리고, 끝으로 RGS 파일을 수정하여 다음과 같은 스크립트를 추가해야 해서 마무리한다.
아래의 스크립트는 BHO를 등록하는 과정이다.

HKLM
{
SOFTWARE
{
Microsoft
{
Windows
{
CurrentVersion
{
Explorer
{
'Browser Helper Objects'
{
{FAFC718E-632B-11D6-B5D3-0050BF79AAF5}
}
}
}
}
}
}
}

VOID SHChangeNotify(
LONG wEventId,
UINT uFlags,
LPCVOID dwItem1,
LPCVOID dwItem2
);

wEventId ===========================================>
SHCNE_ALLEVENTS All events have occurred.
SHCNE_ASSOCCHANGED A file type association has changed. SHCNF_IDLIST must be specified in the uFlags parameter. dwItem1 and dwItem2 are not used and must be NULL.
SHCNE_ATTRIBUTES The attributes of an item or folder have changed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the item or folder that has changed. dwItem2 is not used and should be NULL.
SHCNE_CREATE A nonfolder item has been created. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the item that was created. dwItem2 is not used and should be NULL.
SHCNE_DELETE A nonfolder item has been deleted. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the item that was deleted. dwItem2 is not used and should be NULL.
SHCNE_DRIVEADD A drive has been added. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive that was added. dwItem2 is not used and should be NULL.
SHCNE_DRIVEADDGUI A drive has been added and the shell should create a new window for the drive. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive that was added. dwItem2 is not used and should be NULL.
SHCNE_DRIVEREMOVED A drive has been removed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive that was removed. dwItem2 is not used and should be NULL.
SHCNE_EXTENDED_EVENT Not currently used.
SHCNE_FREESPACE The amount of free space on a drive has changed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive on which the free space changed. dwItem2 is not used and should be NULL.
SHCNE_MEDIAINSERTED Storage media has been inserted into a drive. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive that contains the new media. dwItem2 is not used and should be NULL.
SHCNE_MEDIAREMOVED Storage media has been removed from a drive. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the root of the drive from which the media was removed. dwItem2 is not used and should be NULL.
SHCNE_MKDIR A folder has been created. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the folder that was created. dwItem2 is not used and should be NULL.
SHCNE_NETSHARE A folder on the local computer is being shared via the network. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the folder that is being shared. dwItem2 is not used and should be NULL.
SHCNE_NETUNSHARE A folder on the local computer is no longer being shared via the network. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the folder that is no longer being shared. dwItem2 is not used and should be NULL.
SHCNE_RENAMEFOLDER The name of a folder has changed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the previous PIDL or name of the folder. dwItem2 contains the new PIDL or name of the folder.
SHCNE_RENAMEITEM The name of a nonfolder item has changed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the previous PIDL or name of the item. dwItem2 contains the new PIDL or name of the item.
SHCNE_RMDIR A folder has been removed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the folder that was removed. dwItem2 is not used and should be NULL.
SHCNE_SERVERDISCONNECT The computer has disconnected from a server. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the server from which the computer was disconnected. dwItem2 is not used and should be NULL.
SHCNE_UPDATEDIR The contents of an existing folder have changed, but the folder still exists and has not been renamed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the folder that has changed. dwItem2 is not used and should be NULL. If a folder has been created, deleted, or renamed, use SHCNE_MKDIR, SHCNE_RMDIR, or SHCNE_RENAMEFOLDER, respectively, instead.
SHCNE_UPDATEIMAGE An image in the system image list has changed. SHCNF_DWORD must be specified in uFlags. dwItem1 contains the index in the system image list that has changed. dwItem2 is not used and should be NULL.
SHCNE_UPDATEITEM An existing nonfolder item has changed, but the item still exists and has not been renamed. SHCNF_IDLIST or SHCNF_PATH must be specified in uFlags. dwItem1 contains the item that has changed. dwItem2 is not used and should be NULL. If a nonfolder item has been created, deleted, or renamed, use SHCNE_CREATE, SHCNE_DELETE, or SHCNE_RENAMEITEM, respectively, instead.

The following values specify combinations of other events: SHCNE_DISKEVENTS Specifies a combination of all of the disk event identifiers.
SHCNE_GLOBALEVENT Specifies a combination of all of the global event identifiers.


The following value modifies other event values and cannot be used alone: SHCNE_INTERRUPT The specified event occurred as a result of a system interrupt.


uFlags ===========================================>
Flags that indicate the meaning of the dwItem1 and dwItem2 parameters. The uFlags parameter must be one of the following values: SHCNF_DWORD The dwItem1 and dwItem2 parameters are DWORD values.
SHCNF_IDLIST dwItem1 and dwItem2 are the addresses of ITEMIDLIST structures that represent the item(s) affected by the change. Each ITEMIDLIST must be relative to the desktop folder.
SHCNF_PATH dwItem1 and dwItem2 are the addresses of NULL-terminated strings that contain the full path names of the items affected by the change.
SHCNF_PRINTER dwItem1 and dwItem2 are the addresses of NULL-terminated strings that represent the friendly names of the printer(s) affected by the change.

The following flags modify other data-type flags and cannot be used by themselves: SHCNF_FLUSH The function should not return until the notification has been delivered to all affected components.
SHCNF_FLUSHNOWAIT The function should begin delivering notifications to all affected components but should return as soon as the notification process has begun.


dwItem1 ===========================================>
First event-dependent value.


dwItem2 ===========================================>
Second event-dependent value.

1. 위의 내용은 함수에 대한 프로토와 인자에 대한 설명이다.

시스템에 관한 것이 변경되면 탐색기는 스스로 그들 중 일부를 감지할 수 있다. 하지만 프로그램에서 변경한 것들은 다른 곳으로부터 통지를 받아야 한다.

조금 전에 다룬 내용과는 약간 반대되는 개념이기도 하다.
FindXXXChangeNotification() 함수들은 통지를 받아오는 역활이지만 SHChangeNotify()는 시스템에 사용자가 변경한 내용을 알려주는 역활이기 때문이다.

2. SHChangeNotify() 함수의 역활은 무엇인가?

위의 설명에서 처럼 어떤 객체(파일 시스템 혹은 그 이상의 다른 시스템)에 대하여 프로그램이 변경을 가하였을 경우 탐색기가 인지하도록 할 필요가 있다.
이러한 일련의 동작을 제대로 구성하지 않는 다면 사용자를 무시한 나만의 프로그램이 되지는 않을까?

어쨋든 좋다. 내가 시스템에서 파일시스템의 변화를 얻어 오듯 우리도 그들(시스템)에게 이러한 정보를 알려주는 것이 안정된 시스템 구성이라 할 수 있겠다.

예제는 아주 간단하므로 그냥 한 줄의 예제로 넘어간다.

SHChangeNotify(SHCNE_CREATE, SHCNF_IDLIST, pidl, NULL);
이전의 상황을 종합해보자.

실제적인 소스에 대한 주석은 더이상 달지 않는다.
이전의 내용에 다 설명이 되어있고, 소스 자체가 겨우 50줄 정도의 간단한 내용이기 때문이기도 하다.

실제로 동작을 시켜보면 여러가지 이상한 점을 발경할 수 있는데, 새로 폴더를 만든다든가 파일을 삭제한다든가 할경우 생각보다 많은 이벤트가 발생한다는 점이다.

이러한 것은 첨에 폴더를 만들고, 이름을 바꾸고등등의 이벤트를 가정하더라도 너무 많다.

파일을 삭제할 경우는 2번의 이벤트가 발생하는데 Shift+Del로 지울경우는 한번만 발생한다.
이유는 파일을 휴지통으로 옮길때 한번, 원본을 삭제할 때 한번 등등..
이러한 세부적인 내용은 윈98에서는 알길이 없다.
단지 이벤트가 발생한다는 정도만 알수 있다.

이후에 ICopyHook 라는 쉘 익스텐션을 이용하여 좀더 색다를 접근을 해볼것이지만 이것또한 Win98에서는 근본적인 해결책은 되지 못한다.

하지만 우리가 바라는 내용은 Windows NT 이상버전에서는 현실적으로 가능한 이야기다.

FindFirstChangeNotification 함수와 비슷한 함수인
ReadDirectoryChangeW()라는 함수가 있다.
이 함수는 발생한 사건에 대하여 야기시킨 발생자와 그에 관한 구체적인 정보로 버퍼를 채운다는 차이가 있다.

이에 관한 부분은 이후에 다루기로 한다.
. 통지객체

통지객체는 커널객체로, 이를 통하여 여러분의 스레드를 동기화할 수 있다. 개념을 설졍하자면 객체 하나를 생성하고 어던 벼화가 생길 경우에 스레드를 깨울 것인가 나타내는 속성들을 그 객체에 부여한다. 그리고 나서 여러분의 스레드를 멈추게하고, 객체의 통지를 기다리는 것이다. 원한다면 통지 객체를 파일시스템에 생기는 변화를 감지할 때 자동으로 신호 상태(signal)가 되는 아주특별하게 고안된 이벤트로 생각할 수 있다.

2. 통지객체의 내용

FindFirstChangeNotification()
FindNextChangeNotification()
FindCloseChangeNotification()

이렇게 3가지로 나누어지고 1번째는 생성 2번째는 변화가 감지되면 그 이벤트를 받는 역활을 하고 3번째는 생성된 객체를 해지한다.

물론 윈98에서는 이벤트로 받을 수 있는 정보가 한정적이기는 하지만 생각외로 요긴하게 쓰일 경우가 많다.


3. FindFirstChangeNotification() 함수

HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, // 감시할 경로
BOOL bWatchSubtree, // 서브 경로 감시 여부
DWORD dwNotifyFilter // 감시할 옵션
);

감시할 옵션으로는
FILE_NOTIFY_CHANGE_FILE_NAME 파일 이름 변경
FILE_NOTIFY_CHANGE_DIR_NAME 경로 이름 변경
FILE_NOTIFY_CHANGE_ATTRIBUTES 속성 변경 폴더/파일
FILE_NOTIFY_CHANGE_SIZE 파일 사이즈 변경
FILE_NOTIFY_CHANGE_LAST_WRITE 마지막 수정시간 변경 디렉토리/파일
FILE_NOTIFY_CHANGE_SECURITY 보안설정 변경 디렉토리/파일

위의 함수로 생성된 핸들을 루프안에서 WaitForSingleObject()를 이용하여 대기하다가
시그널이 들어오면..
FindNextChangeNotification() 를 이용하여 정보를 찾아낸다.

4. FindNextChangeNotification() 함수

BOOL FindNextChangeNotification(
HANDLE hChangeHandle // handle to change notification
);

간단한 예제를 들어보자...

while(g_bContinue)
{
WaitForSingleObject(hNotify, INFINITE);
PostMessage(ci.hwnd, WM_EX_CHANGENOTIFICATION, 0, 0);

FindNextChangeNotification(hNotify);
}
위에서 보다시피 루프안에는 루프를 끝낼 만한 이벤트가 없다. while문 안에 있는 부울린값 g_bContinue는 전역 변수로서 위 코드를 실행하는 스레드의 외부 에서 정해진다.
다시 말해 이 코드조작은 두개의 스레드가 있음을 암시하고 있다.

그리고, 마지막으로 감시를 중단할 경우는
BOOL FindCloseChangeNotification(
HANDLE hChangeHandle // handle to change notification
);
를 이용하여 감시를 해제할 수 있다.
이전에 g_bContinue를 FALSE상태로 바꾸고 위의 함수를 호출하는 형태로 해제가 가능하다.

그렇다면 위의 간단한 예제는 아래와 같이 변경되면 조금더 쓸모있는 코드가 될 수 있다.

while(g_bContinue)
{
WaitForSingleObject(hNotify, INFINITE);
if(!g_bContinue)
break;

PostMessage(ci.hwnd, WM_EX_CHANGENOTIFICATION, 0, 0);
FindNextChangeNotification(hNotify);
}

+ Recent posts