직접 뷰를 하나씩 디자인하는게 너무 힘들어서 처음으로 CHtmlView를 써봤다.
작업중 몇가지 난관이 있었는데,
- HTML Page가 아닌 HTML Code를 직접 출력하는 방법
- Multi-Thread에서 접근시 에러
특히 멀티 스레드 문제는 COM+를 시작으로 별 희안한게 다 나와서 편법(?)으로 처리했다.
그럼 하나씩 해결방법을 살펴보자!!
HTML 코드 직접 출력하는건 구글에서 비슷한 코드를 찾아서 문제가 없었다.
단지 내가 쓰기 편하게 커스터마이징 했다.
따로 함수를 만들어서 HTML 코드를 넘기면 화면에 보여주는 형식으로 만들었다.
40 // HTML코드를 출력하는 부분
41 DWORD CHtmlViewView::DoShowHtml(BSTR html)
42 {
43 HRESULT ret;
44
45 // 최소한의 포인터검사
46 if (html == NULL)
47 return FALSE;
48
49 // 현재 도큐먼트자체에 대한 인스턴스획득
50 LPDISPATCH pIDispatch = GetHtmlDocument();
51 if (pIDispatch == NULL)
52 return FALSE;
53
54 // 실제 HTML트리를 가져와서
55 IHTMLDocument2* pIHTMLDocument2;
56 ret = pIDispatch->QueryInterface(IID_IHTMLDocument2, (void**) &pIHTMLDocument2);
57 if (!SUCCEEDED(ret) || pIHTMLDocument2 == NULL)
58 goto DoShowHtml_fail1;
59
60 // <BODY> 객체를 가져옴 (이곳에 동적으로 코드를 삽입)
61 IHTMLElement *pIHTMLElement;
62 ret = pIHTMLDocument2->get_body(&pIHTMLElement);
63 if (!SUCCEEDED(ret) || pIHTMLElement == NULL)
64 goto DoShowHtml_fail2;
65
66 // HTML 코드를 반영
67 ret = pIHTMLElement->put_innerHTML(html);
68 if (!SUCCEEDED(ret))
69 goto DoShowHtml_fail3;
70
71 // 사용한거 반환
72 pIHTMLElement->Release();
73 pIHTMLDocument2->Release();
74 pIDispatch->Release();
75 return TRUE;
76
77 // 에러시의 리턴설정
78 DoShowHtml_fail3: pIHTMLElement->Release();
79 DoShowHtml_fail2: pIHTMLDocument2->Release();
80 DoShowHtml_fail1: pIDispatch->Release();
81 return FALSE;
82 }
이제 이 함수를 호출하면 단일 스레드 환경에서는 아무런 문제가 없이 작동한다.
하지만 자료 처리부와 UI의 스레드를 분리해서 작업하는게 버릇이라,
아무런 생각없이 스레드를 나눠서 작업을 했더니 계속 에러를 내뱉었다.
디버깅을 하면 GetHtmlDocument()의 반환값이 NULL이 나왔다.
잠깐의 구글링을 통해서 위의 코드를 멀티 스레드에서 사용하기 위해서는
이상한(?) 작업이 필요하다는 것을 알게 되었고,
COM+ 어쩌고가 난무하면서 머리가 아파오자 그냥 때려쳤다.
하지만 확실한건 멀티 스레드라도 자신의 스레드에서 호출하면 이상없이 돌아가기에,
편법으로 처리했더니 잘 돌아갔다.
그 방법은- 새로운 유져 메세지를 만든다
- 윈도우 프로시져에 방금만든 메세지를 처리하는 코드를 넣는다
- 프로시져가 위의 HTML 출력함수를 호출한다
호출하는 부분이 자연스럽게 오리지날 스레드로 위임된다.
(별 탈없이 자신의 스레드에서 HTML 출력을 요청한게 된다!!)
83
84 // WindowProc에 HTML출력함수를 호출하는 메세지를 추가 (멀티스레드 우회)
85 #define WM_USER_SHOWHTML (WM_USER+1)
86 LRESULT CHtmlViewView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
87 {
88 if (message == WM_USER_SHOWHTML)
89 {
90 // Free를 여기서해야 실제로 화면에 출력할 코드를 모두 반영한 다음 해제함
91 BSTR bstr = (BSTR) lParam;
92 DoShowHtml(bstr); // 실제 출력함수 호출
93 ::SysFreeString(bstr); // 사용한 시스템스트링 해제
94 }
95
96 return CHtmlView::WindowProc(message, wParam, lParam);
97 }
98
99 // 외부에서 접근시 사용되는 엔트리포인트
100 DWORD CHtmlViewView::ShowHtml(TCHAR* html)
101 {
102 USES_CONVERSION;
103 BSTR bstr = ::SysAllocString(T2COLE(html)); // 해제는 메세지처리부에서 알아서 해줌
104 if (bstr == NULL)
105 return FALSE;
106
107 // 실제 인스턴스를 가진 스레드로 전달
108 SendMessage(WM_USER_SHOWHTML, NULL, (LPARAM) bstr);
109 return TRUE;
110 }
이렇게 하니 간단히 해결되었다.
위의 방식에서 <BODY>에 html코드를 바꾸기 때문에,
Navigate2(_T("about:blank"),NULL,NULL); 같이 빈 페이지로 이동하기 보다는,
미리 HTML 더미페이지를 만들고 미리 <STYLE>을 정의해두면 편리하게쓸수 있다.
실제로 내용이 추가된 파일:
HtmlViewView.cpp, HtmlViewView.h, HtmlViewDoc.cpp, HtmlViewDoc.h
Doc의 경우 메뉴의 테스트코드 포함
HtmlView.7z