본문 바로가기

3.구현/VC++

[리소스이야기] CImageList에 대해

MFC의 CImageList는 말 그대로 "image list"로 이미지 목록을 관리하는 클래스이다. 단 조건이 있다. 같은 크기어야 한다. 그리고 일반 배열(zero-based index)처럼 접근이 가능하다.

이 클래스는 주로 CListCtrl 혹은 CTabCtrl에 등에서 같이 사용이 된다. 여기서는 사용법을 살펴보는 것이 아니라, 리소스에 관련한 내용을 다룰려고한다. 그러므로 이 클래스에 대해서 어느 정도 알고 글을 읽은게 이해하기 쉬울 것이다.

리소스 관리라고 하면 다른게 아니다.
"정확히 할당하고, 정확히 해제한다."

글쓴이: 박재성(ospace114@empal.com) http://ospace.tistory.com/, 2007.07.04

CImgeList 생성하기

먼저 리소스를 할당하는 것 부터 간략하게 살펴보겠다.

CImageList *pImageList = new CImageList;

이때까지 CImageList 멤버변수인 m_hImageList는 NULL 값을 유지한다. 즉, 실제 이미지는 없다.
이는 멈버변수는 실제 이미지 목록을 다루기 위한 핸들러이다. 그러면 이 핸들러를 생성해보자.

int width = 64;
int height = 64;
pImageList->Create(width, height, ILC_COLOR24, 1, 1);

위와 같이 Create()를 호출하면 멤버변수 m_hImageList는 NULL이 아닌 값이 할당된다.
즉, 실제 이미지 목록이 생성되었다. 이제 이미지를 추가 관리하면 된다.

이후에 이미지 추가는 다음 과 같다.

pImageList->Add(...);

이미지 크기 64x64 크기의 이미지가 추가된다. 이전까지는 이미지가 추가안되며 에러가 발생한다.

CImageList 리소스 제거하기

앞의 리소스 할당은 CImageList를 조금만 살펴보면 쉽게 알 수 있는 내용이다.
여기서 다루고 중요하게 다루고 싶은 것은 리소스 해제이다.

"화장은 하는게 중요하는게 아니라, 지우는게 더 중요합니다." - 광고카피

위의 광고 카피처럼 리소스도 할당하는게 중요하는게 아니라 해제하는게 더 중요합니다. 물론 리소스 할당도 중요하다고 합니다. 할당하는 경우는 에러는 쉽게 잡을 수 있습니다. 바로 해당 코드에서 에러가 뜨니깐요.

그러나 리소스를 해제하지 않을 경우는 어디서 리소스가 새고 있는지 모릅니다. 정말 답답하고 미칠 노릇이죠. 물론 당장은 문제가 없을 수도 있지만, 결국 나중에 크게 문제가 발생합니다.

CImageList리소스 해제는 보통 다음과 같이 합니다.

pImageList->DeleteImageList();

이렇게 되면 내부에 할당 관리되었던 모든 이미지 목록이 해제되고, 멤버면수인 m_hImageList도 NULL로 바뀝니다. 그리고 자체 할당되었던 리소스도 해제하면 끝이 나죠.

delete pImageList;
pImageList = NULL;

CImageList 재활용 문제점

여기까지는 문제가 없죠. 그러나 다음과 같은 경우에서 문제가 간혹 발생합니다. 저도 다른 분이 작성한 코드를 보면서 수정하다가 저도 오해를 해서 잘못을 저지르게 되었죠. 그분이 작성한 걸 기준으로 하다보니 그렇게하는구나 라고 그냥 갔다가 쓴게 화근이었습니다.

경우) 이미지 리스트 객체를 해제하지 않고 내부에 새로운 이미지 목록을 새로 할당해서 사용.

바로 "새로 할당해서"라는 문구에서 잘못된 실수를 저지르게됩니다. 한번 예제 코드를 보겠습니다.

CImageList pImageList = new CImageList;

int width = 64;
int height = 64;
pImageList->Create(width, height, ILC_COLOR24, 1, 1);
pImageList->Add(...);
// ...(중략) 
pImageList->DeleteImageList();
//이부분은 필요없겠죠. 해제가 아니고 재활용이니깐요.
//delete pImageList; 
//pImageList = NULL;
pImageList->Add(...); // 에러 발생.

다행이 리소스 사용에서 문제가 발생하여 쉽게 확인할 수 있네요. 그러나 상식적으로 이미지 리스트를 메모리 할당해서 쓰는데 왜 Add()에서 에러가 발생할까라고 의문이 생기실겁니다.
그러나 그건 상식이 아니고 지식의 착각에서 오는 거죠. 저도 그랬으니깐요.

CImageList는 자신이 할당하는 것 외에 내부 이미지 관리위한 핸들러(m_hImageList)도 따로 할당해야합니다. 그럼 메모리 할당할때 이미지 관리위한 핸들러도 같이하면 되지 않은가라고 생각할 수 있지만, 이미지 종류가 무한하기 때문에 생성시 이를 전부 다루기 힘듭니다. 이미지는 여러 인자에 의한 조합에 의해 무수히 많은 경우의 수를 조합하여 생성하고 있죠.

정리를 하면 CImageList에서는 두번의 리소스 할당을 하고 있습니다.

  • 메모리 할당: new 연산자 사용
  • 이미지 목록 핸들러 할당: Create() 멤버함수 사용

앞에서 DeleteImageList()가 Create()의해 할당된 이미지 목록 핸들러(m_hImageList)를 해제하고 NULL로 초기화한다. 그러니 새로운 이미지를 추가할려고 하니 되지 않는다. NULL접근을 하니 문제가 발생하게 된다.

즉, 재활용하려고 하면 위에서 할당한 두가지 리소스를 유지해야만한다. 그러면 어떻게 할 것인가?

CImageList를 재활용위한 내부 데이터 삭제

내부 이미지 목록 데이터만 삭제하고 리소스를 그대로 유지하려면 다른 멤버함수를 사용해야한다.

BOOL CImageList::Remove(int nImage);

위 멤버함수는 해당 순번에 있는 이미지를 삭제하라는 의미이다. 실제 사용은 다음과 같다.

int idx = 2;
pImageList->Remove(idx);

이는 이미지 목록에서 3번째 이미지를 삭제하라는 의미이다.
근데 왜 3인지는 알죠? zero-based index!! 인덱스가 0부터 시작합니다.

그럼 전체 이미지만 삭제하려면,

for (int i=0; i < pImageList->GetImageCount(); ++i) {
  pImageList->Remove(i);
}

위와 같이하고 다시 새로운 이미지를 Add()를 이용해서 추가하면 된다.

그러나 위와 같은 방법이 문제가 있습니다. 지나가다님의 지적해주신 내용입니다. 실제 MSDN에서 예제에는 위와 같이 되어 있습니다. 그런데 위의 Remarks을 읽어 보면 뭐가 이상합니다.

Remarks에 2개의 아이템이 있을 경우 첫번째 아이템(nImage=0)을 삭제했다면, 나머지 아이템이 첫번 째 위치(nImage=0)가 된다고 합니다. 뭐~ 이런 (ㅡ.ㅡ;)

이를 반영한 내용이 아래와 같습니다.

while (pImageList->GetImageCount()) pImageList->Remove(0);

즉, GetImageCount()가 0(아이템이 없음)이라면 거짓이 되기 때문에 while문을 빠져나오게 됩니다.
위의 방법이 귀찮다면, DeleteImageList()하고 다시 Create()를 생성해서 이미지를 추가하면 됩니다.
이미지 목록에서 원하는 인덱스의 이미지만 교체하려면 Replace()를 사용하면 된다.

결론

사실 알고 보면 별거 아닌데, 해당 클래스의 제작 의도를 잘 알고 있지 못하면 헤매게 되는 심각한 문제라고 볼 수 있죠. 물론 잘 공부하신 분들은 문제가 없겠지만, 실제 레퍼런스 문서 꼼꼼히 읽고 적용하는게 아니라 예제를 보고 바로 적용하기때문에 해당 클래스용법을 몰라 문제가 발생하게되죠.

모두 즐프하세요~

참조

CImageList::Remove, MSDN, http://msdn.microsoft.com/en-us/library/1741dtwz(v=vs.80).aspx

History

2009.01.15 Ospace 오탈자 수정, 서식 수정, 내용 약간 보강, 결론 분리 및 내용 추가.

2012.09.03 Ospace 지나가다님(?)의 지적하신 내용 보완

반응형