CHtmlView에 Multi-Thread로 접근해서 Html 코드 바로 출력하기

Posted at 2010/10/29 12:59 // in Note/Windows // by drDorothy
이번에 프로그램 만들면서 보고서 형식의 출력을 만드는 과정에서,
직접 뷰를 하나씩 디자인하는게 너무 힘들어서 처음으로 CHtmlView를 써봤다.

작업중 몇가지 난관이 있었는데,
  1. HTML Page가 아닌 HTML Code를 직접 출력하는 방법
  2. Multi-Thread에서 접근시 에러
위의 2가지를 해결하는데 자료가 많지 않아서 힘들었다.
특히 멀티 스레드 문제는 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+ 어쩌고가 난무하면서 머리가 아파오자 그냥 때려쳤다.

하지만 확실한건 멀티 스레드라도 자신의 스레드에서 호출하면 이상없이 돌아가기에,

편법으로 처리했더니 잘 돌아갔다.

그 방법은
  1. 새로운 유져 메세지를 만든다
  2. 윈도우 프로시져에 방금만든 메세지를 처리하는 코드를 넣는다
  3. 프로시져가 위의 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의 경우 메뉴의 테스트코드 포함
2010/10/29 12:59 2010/10/29 12:59

http://d-story.net/tc/lab/trackback/344

1 2 3 4 5 6 7 8 ... 74