개발자 포럼 홈페이지에 있는
샘플 프로젝트(HncUserAction.zip)를 참조하여
몇 가지 Add-On을 만들어 보고 있는데… 자꾸 잊어버리는 게 많습니다.
Add-On 툴바에 대한 계층 구조와 흐름 정리 (ygyoun님의 피드백 내용 반영)
안녕하세요
Add-On 툴바에 대한 계층 구조와 흐름을 잘 정리하신 것 같습니다. 아래 4가지 사항만 확인부탁드립니다.
감사합니다.
1. 3-⑤ pDisp->Release() — 삭제하셔야 합니다
COleDispatchDriver::AttachDispatch()는 AddRef()를 호출하지 않고 전달받은 참조의 소유권을 그대로 넘겨받습니다. 기본값이 bAutoRelease = TRUE이므로 wrapper 소멸 시점에 자동으로 Release()됩니다.
따라서 AttachDispatch() 뒤에 다시 Release()를 호출하면 참조 카운트가 하나 모자라게 되어, 이후 wrapper 소멸자에서 이중 해제가 발생하기 때문에 삭제하셔야 합니다.
2. 서브메뉴 추가는 InsertItem이 아니라 InsertMenuItem입니다
2 - 5 하단의 “각 Sub Button 항목은 xMenu.InsertItem(-1, pDispatch) 형식” 부분인데, InsertItem()은 CXHncToolBoxGroup에만 있는 메서드입니다. 메뉴 하위 항목은 CXHncToolBoxItemMenu::InsertMenuItem()을 사용합니다.
3. 아이템을 만드는 주체는 Group이 아니라 ToolBoxToolbar입니다
CreateToolBoxItemButtonEx() / CreateToolBoxItemMenu()는 모두 최상단의 CXHncToolBoxToolbar 객체가 제공합니다. 4 요약 체인이 … → CXHncToolBoxGroup → [InsertItem()] → Item 한 줄로 되어 있어 그룹이 아이템을 생성하는 것처럼 읽힙니다.
4. CXHncToolBoxLayout 계층의 의미
“툴박스 내부의 레이아웃(배치 방식, 정렬 관리)” 이라고 설명하셨는데, 현재 버전에서는 실질적인 배치 정보를 갖지 않는 ToolBox와 사실상 1:1로 대응되는 호환 계층 계층에 가깝습니다.
실제 배치는 Group 생성 시 InsertGroup(index, uid, type, rowCount, columnCount) 인자로 결정됩니다.
안녕하세요.
부족한 문서 봐주시고, 검토해 주셔서 정말 감사합니다.
말씀해 주신 것들을 잘 숙지해서 적극 활용해 보겠습니다.
언급해주신 AttachDispatch()와 Wrapper 소멸자를 통한 자동 Release() 원칙은
MFC COM 프로그래밍에서 이중 해제를 방지하기 위한 정석적인 가이드로 잘 이해했습니다.
아래 코드는 개발자 포럼에서 받은 샘플 프로젝트에서 발췌한 코드입니다.
if(xToolBoxToolbar)
{
CXHncToolBoxTab xToolBoxTab;
CXHncToolBox xToolBox;
CXHncToolBoxLayout xToolBoxLayout;
xToolBoxTab.AttachDispatch(xToolBoxToolbar.InsertToolBoxTab(-1, _T(USERTOOLBOXTAB_UID), _T("추가 기능")));
xToolBox.AttachDispatch(xToolBoxTab.InsertToolBox(-1, _T(USERTOOLBOX_UID), _T("멀티미디어")));
xToolBoxLayout.AttachDispatch(xToolBox.GetLayout(0));
CXHncToolBoxGroup xToolBoxGroup;
xToolBoxGroup.AttachDispatch(xToolBoxLayout.InsertGroup(-1, _T(USERTOOLBOXGROUP_UID), HFTBDT_GROUP_LARGEICON, 1, 4));
LPDISPATCH pDispatch = xToolBoxToolbar.CreateToolBoxItemButtonEx(_T("툴박스버튼"), _T(UUIDSTR_ON_USERACTION_TOOLBOX), STDTB_BTN, 0x01 | 0x02);
xToolBoxGroup.InsertItem(-1, pDispatch);
pDispatch->Release();
}
위 코드에서
수동으로 ->Release()를 호출하는 라인들은 Wrapper 객체가 아닌,
CreateToolBoxItemButtonEx 메서드에서 반환된
LPDISPATCH 포인터들입니다.
인터페이스 포인터를 반환 받을 때는 AddRef가 되어 넘어오므로,
호출자가 직접 Release()를 해주지 않으면 메모리 누수가 발생하게 됩니다.
따라서 해당 Release() 호출은 누수 방지를 위한 필수 코드로 판단됩니다.
하지만 ygyoun님의 피드백 취지(Wrapper를 활용한 안전하고 깔끔한 자원 관리)에
깊이 공감하여,
포인터(LPDISPATCH)와 수동 Release()를 모두 제거했습니다.
더불어 한글(HWP) API 헤더에 COleDispatchDriver를 상속받고있는
전용 Wrapper 클래스가 있으므로 기본 COleDispatchDriver 대신
제공된 헤더의 전용 클래스를 사용하는 것으로 아래와 같이 개선했습니다.
if (xToolBoxToolbar)
{
CXHncToolBoxTab xToolBoxTab;
CXHncToolBox xToolBox;
CXHncToolBoxLayout xToolBoxLayout;
CXHncToolBoxGroup xToolBoxGroup;
xToolBoxTab.AttachDispatch(xToolBoxToolbar.InsertToolBoxTab(-1, _T(TOOLBOXTAB_UID), _T("KOSOHN")));
xToolBox.AttachDispatch(xToolBoxTab.InsertToolBox(-1, _T(TOOLBOX_UID), _T("Automation")));
xToolBoxLayout.AttachDispatch(xToolBox.GetLayout(0));
xToolBoxGroup.AttachDispatch(xToolBoxLayout.InsertGroup(-1, _T(TOOLBOXGROUP_UID), HFTBDT_GROUP_LARGEICON, 1, 4));
//금액 한글 변환 버튼
{
CXHncToolBoxItemButton itemKorMoney;
itemKorMoney.AttachDispatch(xToolBoxToolbar.CreateToolBoxItemButtonEx(_T("금액한글변환"), _T(UUID_KORMONEY), STDTB_BTN, 0x01 | 0x02));
if (itemKorMoney.m_lpDispatch != NULL)
{
xToolBoxGroup.InsertItem(-1, itemKorMoney.m_lpDispatch);
}
}
}
좋은 피드백 덕분에 코드 품질을 한 단계 올릴 수 있었습니다. 감사합니다!