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

CD 아무거나 자동실행

Posted at 2009/08/30 12:18 // in Note/Windows // by drDorothy
옆새님 께서 물어본 내용에 대한 답변..
질문 : CD를 집어넣으면 동영상 뜨게 하고 싶어염..
답변 : CD삽입후 재빨리 열어서 동영상 클릭하세요.

아무튼 CD 자동실행을 하려면..
간단한 autorun.inf 파일을 만들어서 CD에 넣어버림 그만이다.
그러나!.. 문제가 있었으니... xxx.avi 나 xxx.bmp 이런건 안된다는거...

그럼 무슨 방법이 있을까!?..
간단한 Launcher 만들면 그만이졈..
(but.. How??..)

우리에겐 윈도님께서 허락하신 ShellExecuteEx 라는 축복이 있으니..
이 함수로 실행하면 그만임..호호
그러면 알아서 연결된 응용프로그램으로 실행 가능하졈~
(폴더옵션 파일형식의 동작에 있는거면 다 되지만.. 깊은 내용은 패스~)

축복의 Launcher.c 파일 내용
#include 

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdParam, int nCmdShow)
{
	HANDLE hInfo;
	DWORD size, ret;
	char* file;
	char path[MAX_PATH];
	SHELLEXECUTEINFO sei = {0,};

	hInfo = CreateFile("Launcher.dat", GENERIC_READ, FILE_SHARE_READ, NULL,
				OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hInfo == INVALID_HANDLE_VALUE)
		return 0;

	if ((size = GetFileSize(hInfo, NULL)) == 0)
		return CloseHandle(hInfo), 0;

	file = (char*) LocalAlloc(LPTR, size+1);

	if (!ReadFile(hInfo, file, size, &ret, NULL))
		goto fin;

	if ((DWORD) FindExecutable(file, NULL, path) > 32)
	{
		sei.cbSize = sizeof(sei);
		sei.lpVerb = TEXT("Open");
		sei.lpFile = file;
		sei.nShow  = SW_SHOWNORMAL;

		ShellExecuteEx(&sei);
	}
fin:
	CloseHandle(hInfo);
	LocalFree(file);
	return 0;
}


그럼 간략 사용법..
(매번 파일명 바꿔 컴파일 하기 귀찮아서 Launcer.dat 에서 파일명 읽어옴)

  1. 실행시키고픈 파일명을 dat 파일에 쓰기~
  2. autorun.infLauncher.exe 를 실행하도록 표시
  3. 3종 파일(inf, dat, Launcher) 을 CD에 넣고, 필요한 파일들 추가
  4. CD 굽고, 다시 넣어보고 테스트 해보고 끝내기

초 간단한듯..ㅋㅋ (참고 : CD의 아이콘도 넣고싶다면.. Autorun.inf 수정하세염)
첨부파일의 Launcher.exe 는 UPX 압축이며.. 잡다 DLL 디펜던시 없음..!
2009/08/30 12:18 2009/08/30 12:18