API - DeleteObject / MyGDI & OldGDI 교체이유
- 프로그래밍/WindowsAPI
- 2011. 5. 13. 10:52
1.삭제 ( DeleteObject ) / MyPen & OldPen 교체이유
GDI 오브젝트는 사용한 후 반드시 삭제해야 한다.
왜냐하면?
=>GDI 오브젝트도 메모리를 사용하기 때문이다.
메모리를 할당한 후 반드시 해제해야 하는 것과 마찬가지로 GDI 오브젝트도 사용이 끝나면 해제하는 것이 원칙이다.
( 해제하지 않으면 메모리와 리소스를 갉아먹게 될 것이다 )
BOOL DeleteObject ( HGDIOBJ hObject );
삭제하고자 하는 GDI 오브젝트의 핸들만 인수로 넘겨주면 된다.
단 , 이때 주의할 것은 DC 에 현재 선택되어 있는 GDI 오브젝트는 삭제할 수 없다는 점인데
이는 현재 사용되고 있는 객체를 함부로 삭제하도록 내버려 둘 수는 없기 때문에 생긴 일종의 안전규정 이다.
그래서 삭제하기 전에 DC 에 선택된 객체를 선택 해제해야 하는데 ,
가장 간단한 방법은 같은 종류의 다른 GDI 오브젝트를 선택하는 것이다.
이런 이유로 OldPen 이라는 핸들을 선언하고 이 핸들에 MyPen 이 선택되기 전의 펜 핸들을 저장하여
MyPen 을 삭제하기 전에 OldPen 을 다시 선택하는 것이다.
2.그렇다면 Old는 왜 쓰는것일까?
GDI 오브젝트를 사용할 때마다 항상 같은 타입의 Old 핸들을 선언하고,
SelectObject 로 선택할 때 이전 선택 객체를 Old 에 대입받아 두었다가
파괴하기 전에 Old 를 다시 DC 에 선택 하였다.
GDI 오브젝트 하나를 쓰기 위한 과정이 무척 번거로운데 ,왜 이런 식의 코드가 필요한 것일까?
case WM_LBUTTONDOWN:
hdc = GetDC( hWnd );
for( i=0; i<1000; i++ ){
hPen = CreatePen ( PS_SOLID , 2, RGB(255,0,0) ); // 펜을 생성 ( 계속해서 생성 )
SelectObject( hdc , hPen );
// 그리기 코드
}
ReleaseDC( hWnd , hdc );
return 0;
코드에서 보다시피
펜을 만들기만 할 뿐 , 파괴하지 않기 때문에 이 프로그램을 한참 실행하도 보면 , 리소스 영역이 바닥나서
펜은 물론이고 브러시 , 폰트 등의 GDI 오브젝트를 더이상 생성하지 못하게 된다.
GDI 오브젝트를 저장하는 리소스 영역은 메인 메모리와는 다른 특수한 영역인데 이 영역의 크기가 그리 크지 못해 오브젝트를 많이 만들면 금방 가득차 버린다. 그리기에 사용할 도구를 만들지 못하므로 화면 출력이 제대로 되지 않을 것이다.
이렇게 되는 이유는 생성한 펜을 삭제하지 않았기 때문인데 , DeleteObject 를 넣어 보기로 한다.
case WM_LBUTTONDOWN:
hdc = GetDC( hWnd );
for( i=0; i<1000; i++ ){
hPen = CreatePen ( PS_SOLID , 2, RGB(255,0,0) ); // 펜을 생성 ( 계속해서 생성 )
SelectObject( hdc , hPen );
// 그리기 코드
DeleteObject( hPen );
}
ReleaseDC( hWnd , hdc );
return 0;
생성 후 삭제했으므로 아무 문제가 없을 것 같지만 , 이 코드도 문제점이 있다. 계속 실행해 보면 역시 리소스 누출이 발생한다.
DC 에 선택되어 있는 오브젝트는 삭제할 수 없다는 규칙이 있는데 왜냐하면 사용중인 펜을 삭제해 버리면 이후의 그리기 코드는 선을 그을 수 없기 때문이다.
펜이나 브러시 같은 기본적인 오브젝트는 디폴트이든 만든것이든 어쨋든 하나는 꼭 DC 에 선택되어 있어야 한다.
DC 에 펜 , 브러시 등의 오브젝트가 없는 상황을 가정할 수 없기 때문에 UnSelectObject 따위의 함수는 존재할 수 없으며 DC 에는 항상 선택된 오브젝트가 있어야 한다.
사용중인 펜을 삭제하려면 먼저 선택을 해제해야 하는데 , 그 방법은 다른 펜을 선택하는 것이다.
어쨋든 DC 에 하나의 펜은 꼭 있어야 하므로 ,다른 펜을 DC 에 던져 주어야 한다.
펜 뿐만 아니라 브러시나 폰트 등도 마찬가지 규칙이 적용된다.
일반적으로 표현하자면 DC 에 선택된 오브젝트를 해제하려면 같은 종류의 다른 오브젝트로 선택하여 밀어내는 방법밖에 없다.
일단은 같은 종류의 스톡 오브젝트를 대신 선택하는 간편한 방법을 생각할 수 있다.
case WM_LBUTTONDOWN:
hdc = GetDC( hWnd );
for( i=0; i<1000; i++ ){
hPen = CreatePen ( PS_SOLID , 2, RGB(255,0,0) ); // 펜을 생성 ( 계속해서 생성 )
SelectObject( hdc , hPen );
// 그리기 코드
SelectObject( hdc , GetStockObject( BLACK_PEN ) );
DeleteObject( hPen );
}
ReleaseDC( hWnd , hdc );
return 0;
이렇게 하면 검정색 스톡펜이 선택될 때 hPen 이 해제 되므로 리소스 누출은 일단 막을 수 있다.
그러나 이 코드는 범용성이 부족한데 ,hPen 이전에 선택된 펜이 스톡펜이라는 위험한 가정을 하고 있기 때문이다.
만약 이 코드를 다른 함수로 가져간다거나 , 앞 뒤로 다른 오브젝트를 쓰는 코드가 추가 된다면
hPen 이전에 선택된 펜으로 다 시 복구되지 않을 것이다.
초록펜이 선택되어있음 -> 마우스 좌클릭으로 위의 작업 진행 -> 검정색 펜이 선택 되었음 |
이렇게 됨으로써 , 원래 있던 초록펜이 다시 선택 되지 않고 ,
이 후 부터 검정색 펜이 선택되어 있을 것이다.
때문에 , 원래 선택된 오브젝트를 저장했다가 다시 복구하기 위한 Old 오브젝트가 필요하다.
case WM_LBUTTONDOWN:
hdc = GetDC( hWnd );
for( i=0; i<1000; i++ ){
hPen = CreatePen ( PS_SOLID , 2, RGB(255,0,0) ); // 펜을 생성 ( 계속해서 생성 )
OldPen = (HPEN) SelectObject( hdc , hPen ); // 생성한 펜 적용 & 이전 펜 저장
// 그리기 코드
SelectObject( hdc , OldPen ); // 이전 펜 다시 적용
DeleteObject( hPen );
}
ReleaseDC( hWnd , hdc );
return 0;
hdc 가 자신이 직접 만든 것이고여기에 원래 선택된 펜이 검정색 스톡 펜이라는 것을 확실하게 알고 있다면
스톡 오브젝트로 hPen 을 밀어낼 수도 있다.
그러나 이 코드가 항상 이 자리에 있을 것이고 더 이상의 변형이 없을 것이라고 확신하는 태도는 상당히 안좋다.
코드의 일부는 언제든지 다른 함수로 분리될 수 있고
함수 호출문 앞뒤로 다른 오브젝트를 선택해서 사용할 수 있으며
직접 만들지 않은 DC 를 인수로 전달 받을 때는 원래 어떤 오브젝트가 선택되어 있었는지 알 수 없는 상황이 되기도 한다.
코드의 범용성을 위해서는 항상 Old 오브젝트를 사용하여 DC 를 원래대로 돌려놓는 습관을 가지는 것이 좋은데
이는 비단 GDI 오브젝트 뿐만 아니라 , 거의 모든 자원에 대해서도 마찬가지 인다.
할당한 메모리는 해제하고 연 파일은 닫아야 하는 것과 같다.
C 언어보다 좀 더 상위 언어인 C++ 은 객체가 파괴될 때 자동으로 호출되는 파괴자에 의해
스스로를 정리하는 기능이 있다.
그러나 C++ 객체의 파괴자도 범위를 벗어날 때 스스로를 삭제할 수는 있지만 선택을 해제하지는 못한다.
MFC 같은 고급 라이브러리 에서도 Old 오브젝트는 여전히 필요하다.
이제 GDI 오브젝트를 해제하지 않을 때 시스템이 어떤 상태가 될 수 있는지 실험해 본다.
1번은 정상적으로 작동된다.
2번은 펜을 바꾸지 않고 선택되어 있는 펜을 삭제만 하는데 운영체제마다 다르긴 하나
화면이 엉망이 되고 얼어버릴 수도 있다.
3번은 펜도 바꾸지 않고 삭제도 하지 않는다. ( 펜을 계속 만들어서 그리기만 한다 -> 자원고갈 )
2~3번만 실행하면 화면이 엉망이 되어 버리고 어떠한 입력도 받지 못하며 [ 작업관리자 ] 도 띄우지 못하게 된다.
사실 작업 관리자가 실행되기는 하지만 눈에 보이지 않는다. 왜냐하면 화면을 그리기 위한 GDI 오브젝트 생성 공간이 고갈되어 버렸기 때문이다.전원을 내릴 수 밖에 없다.
[ 결론 ]
Old 변수는 꼭 필요할때도 , 필요하지 않을 때도 있겠지만
그냥 항상 Old 변수를 사용하는 것이 합리적인 방법이다.
이 글을 공유하기